about summary refs log tree commit diff
path: root/configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-websocket.el
diff options
context:
space:
mode:
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-websocket.el')
-rw-r--r--configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-websocket.el1018
1 files changed, 1018 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-websocket.el b/configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-websocket.el
new file mode 100644
index 0000000000..37e395d820
--- /dev/null
+++ b/configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-websocket.el
@@ -0,0 +1,1018 @@
+;;; slack-websocket.el --- slack websocket interface  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015  南優也
+
+;; Author: 南優也 <yuyaminami@minamiyuunari-no-MacBook-Pro.local>
+;; Keywords:
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'websocket)
+(require 'slack-util)
+(require 'slack-request)
+(require 'slack-message)
+(require 'slack-team)
+(require 'slack-reply)
+(require 'slack-file)
+(defconst slack-api-test-url "https://slack.com/api/api.test")
+
+(defclass slack-typing ()
+  ((room :initarg :room :initform nil)
+   (limit :initarg :limit :initform nil)
+   (users :initarg :users :initform nil)))
+
+(defclass slack-typing-user ()
+  ((limit :initarg :limit :initform nil)
+   (user-name :initarg :user-name :initform nil)))
+
+(defun slack-ws-set-connect-timeout-timer (team)
+  (slack-ws-cancel-connect-timeout-timer team)
+  (cl-labels
+      ((on-timeout ()
+                   (slack-log (format "websocket open timeout")
+                              team)
+                   (slack-ws-close team)
+                   (slack-ws-set-reconnect-timer team)))
+    (oset team websocket-connect-timeout-timer
+          (run-at-time (oref team websocket-connect-timeout-sec)
+                       nil
+                       #'on-timeout))))
+
+(defun slack-ws-cancel-connect-timeout-timer (team)
+  (when (timerp (oref team websocket-connect-timeout-timer))
+    (cancel-timer (oref team websocket-connect-timeout-timer))
+    (oset team websocket-connect-timeout-timer nil)))
+
+(cl-defun slack-ws-open (team &key (on-open nil) (ws-url nil))
+  (slack-if-let* ((conn (oref team ws-conn))
+                  (state (websocket-ready-state conn)))
+      (cond ((websocket-openp conn)
+             (slack-log "Websocket is Already Open" team))
+            ((eq state 'connecting)
+             (slack-log "Websocket is connecting" team))
+            ((eq state 'closed)
+             (slack-log "Websocket is closed" team)))
+
+    (progn
+      (slack-ws-set-connect-timeout-timer team)
+      (cl-labels
+          ((on-message (websocket frame)
+                       (slack-ws-on-message websocket frame team))
+           (handle-on-open (_websocket)
+                           (oset team reconnect-count 0)
+                           (oset team connected t)
+                           (slack-log "WebSocket on-open"
+                                      team :level 'debug)
+                           (when (functionp on-open)
+                             (funcall on-open)))
+           (on-close (websocket)
+                     (oset team ws-conn nil)
+                     (oset team connected nil)
+                     (slack-log (format "Websocket on-close: STATE: %s"
+                                        (websocket-ready-state websocket))
+                                team :level 'debug)
+                     (unwind-protect
+                         (progn
+                           (unless (oref team inhibit-reconnection)
+                             (slack-ws-set-reconnect-timer team)))
+                       (oset team inhibit-reconnection nil)))
+           (on-error (_websocket type err)
+                     (slack-log (format "Error on `websocket-open'. TYPE: %s, ERR: %s"
+                                        type err)
+                                team
+                                :level 'error)))
+        (oset team ws-conn
+              (condition-case error-var
+                  (websocket-open (or ws-url (oref team ws-url))
+                                  :on-message #'on-message
+                                  :on-open #'handle-on-open
+                                  :on-close #'on-close
+                                  :on-error #'on-error
+                                  :nowait (oref team websocket-nowait))
+                (error
+                 (slack-log (format "An Error occured while opening websocket connection: %s"
+                                    error-var)
+                            team
+                            :level 'error)
+                 ;; (slack-ws-close team)
+                 ;; (slack-ws-set-reconnect-timer team)
+                 nil)))))))
+
+(cl-defun slack-ws-close (&optional team (close-reconnection nil))
+  (interactive)
+  (unless team
+    (setq team slack-teams))
+  (let ((called-interactively (called-interactively-p 'any)))
+    (cl-labels
+        ((close (team)
+                (slack-ws-cancel-ping-timer team)
+                (slack-ws-cancel-ping-check-timers team)
+                (when (or close-reconnection
+                          called-interactively)
+                  (slack-ws-cancel-reconnect-timer team)
+                  (oset team inhibit-reconnection t))
+                (with-slots (connected ws-conn last-pong) team
+                  (when ws-conn
+                    (websocket-close ws-conn)
+                    (slack-log "Slack Websocket Closed" team)))))
+      (if (listp team)
+          (progn
+            (mapc #'close team)
+            (slack-request-worker-quit))
+        (close team)
+        (slack-request-worker-remove-request team)
+        )
+      )))
+
+(defun slack-ws-send (payload team)
+  (with-slots (waiting-send ws-conn) team
+    (push payload waiting-send)
+    (cl-labels
+        ((reconnect ()
+                    (slack-ws-close team)
+                    (slack-ws-set-reconnect-timer team)))
+      (if (websocket-openp ws-conn)
+          (condition-case err
+              (progn
+                (websocket-send-text ws-conn payload)
+                (setq waiting-send
+                      (cl-remove-if #'(lambda (p) (string= payload p))
+                                    waiting-send)))
+            (error
+             (slack-log (format "Error in `slack-ws-send`: %s" err)
+                        team :level 'debug)
+             (reconnect)))
+        (reconnect)))))
+
+(defun slack-ws-resend (team)
+  (with-slots (waiting-send) team
+    (let ((candidate waiting-send))
+      (setq waiting-send nil)
+      (cl-loop for msg in candidate
+               do (sleep-for 1) (slack-ws-send msg team)))))
+
+;; (:type error :error (:msg Socket URL has expired :code 1))
+(defun slack-ws-handle-error (payload team)
+  (let* ((err (plist-get payload :error))
+         (code (plist-get err :code)))
+    (cond
+     ((eq 1 code)
+      (slack-ws-close team)
+      (slack-ws-set-reconnect-timer team))
+     (t (slack-log (format "Unknown Error: %s, MSG: %s"
+                           code (plist-get err :msg))
+                   team)))))
+
+(defun slack-ws-on-message (_websocket frame team)
+  ;; (message "%s" (slack-request-parse-payload
+  ;;                (websocket-frame-payload frame)))
+  (when (websocket-frame-completep frame)
+    (let* ((payload (slack-request-parse-payload
+                     (websocket-frame-payload frame)))
+           (decoded-payload (and payload (slack-decode payload)))
+           (type (and decoded-payload
+                      (plist-get decoded-payload :type))))
+      ;; (message "%s" decoded-payload)
+      (when (slack-team-event-log-enabledp team)
+        (slack-log-websocket-payload decoded-payload team))
+      (when decoded-payload
+        (cond
+         ((string= type "error")
+          (slack-ws-handle-error decoded-payload team))
+         ((string= type "pong")
+          (slack-ws-handle-pong decoded-payload team))
+         ((string= type "hello")
+          (slack-ws-cancel-connect-timeout-timer team)
+          (slack-ws-cancel-reconnect-timer team)
+          (slack-cancel-notify-adandon-reconnect)
+          (slack-ws-set-ping-timer team)
+          (slack-ws-resend team)
+          (slack-log "Slack Websocket Is Ready!" team :level 'info))
+         ((plist-get decoded-payload :reply_to)
+          (slack-ws-handle-reply decoded-payload team))
+         ((string= type "message")
+          (slack-ws-handle-message decoded-payload team))
+         ((string= type "reaction_added")
+          (slack-ws-handle-reaction-added decoded-payload team))
+         ((string= type "reaction_removed")
+          (slack-ws-handle-reaction-removed decoded-payload team))
+         ((string= type "channel_created")
+          (slack-ws-handle-channel-created decoded-payload team))
+         ((or (string= type "channel_archive")
+              (string= type "group_archive"))
+          (slack-ws-handle-room-archive decoded-payload team))
+         ((or (string= type "channel_unarchive")
+              (string= type "group_unarchive"))
+          (slack-ws-handle-room-unarchive decoded-payload team))
+         ((string= type "channel_deleted")
+          (slack-ws-handle-channel-deleted decoded-payload team))
+         ((or (string= type "channel_rename")
+              (string= type "group_rename"))
+          (slack-ws-handle-room-rename decoded-payload team))
+         ((string= type "channel_joined")
+          (slack-ws-handle-channel-joined decoded-payload team))
+         ((string= type "group_joined")
+          (slack-ws-handle-group-joined decoded-payload team))
+         ((string= type "presence_change")
+          (slack-ws-handle-presence-change decoded-payload team))
+         ((or (string= type "bot_added")
+              (string= type "bot_changed"))
+          (slack-ws-handle-bot decoded-payload team))
+         ((string= type "file_created")
+          (slack-ws-handle-file-created decoded-payload team))
+         ((or (string= type "file_deleted")
+              (string= type "file_unshared"))
+          (slack-ws-handle-file-deleted decoded-payload team))
+         ((string= type "file_comment_added")
+          (slack-ws-handle-file-comment-added decoded-payload team))
+         ((string= type "file_comment_deleted")
+          (slack-ws-handle-file-comment-deleted decoded-payload team))
+         ((string= type "file_comment_edited")
+          (slack-ws-handle-file-comment-edited decoded-payload team))
+         ((or (string= type "im_marked")
+              (string= type "channel_marked")
+              (string= type "group_marked"))
+          (slack-ws-handle-room-marked decoded-payload team))
+         ((string= type "thread_marked")
+          (slack-ws-handle-thread-marked decoded-payload team))
+         ((string= type "thread_subscribed")
+          (slack-ws-handle-thread-subscribed decoded-payload team))
+         ((string= type "im_open")
+          (slack-ws-handle-im-open decoded-payload team))
+         ((string= type "im_close")
+          (slack-ws-handle-im-close decoded-payload team))
+         ((string= type "team_join")
+          (slack-ws-handle-team-join decoded-payload team))
+         ((string= type "user_typing")
+          (slack-ws-handle-user-typing decoded-payload team))
+         ((string= type "user_change")
+          (slack-ws-handle-user-change decoded-payload team))
+         ((string= type "member_joined_channel")
+          (slack-ws-handle-member-joined-channel decoded-payload team))
+         ((string= type "member_left_channel")
+          (slack-ws-handle-member-left_channel decoded-payload team))
+         ((or (string= type "dnd_updated")
+              (string= type "dnd_updated_user"))
+          (slack-ws-handle-dnd-updated decoded-payload team))
+         ((string= type "star_added")
+          (slack-ws-handle-star-added decoded-payload team))
+         ((string= type "star_removed")
+          (slack-ws-handle-star-removed decoded-payload team))
+         ((string= type "reconnect_url")
+          (slack-ws-handle-reconnect-url decoded-payload team)
+          )
+         )))))
+
+(defun slack-ws-handle-reconnect-url (payload team)
+  (oset team reconnect-url (plist-get payload :url)))
+
+(defun slack-user-typing (team)
+  (with-slots (typing typing-timer) team
+    (with-slots (limit users room) typing
+      (let ((current (float-time)))
+        (if (and typing-timer (timerp typing-timer)
+                 (< limit current))
+            (progn
+              (cancel-timer typing-timer)
+              (setq typing-timer nil)
+              (setq typing nil))
+          (if (slack-buffer-show-typing-p
+               (get-buffer (slack-room-buffer-name room)))
+              (let ((team-name (slack-team-name team))
+                    (room-name (slack-room-name room))
+                    (visible-users (cl-remove-if
+                                    #'(lambda (u) (< (oref u limit) current))
+                                    users)))
+                (slack-log
+                 (format "Slack [%s - %s] %s is typing..."
+                         team-name room-name
+                         (mapconcat #'(lambda (u) (oref u user-name))
+                                    visible-users
+                                    ", "))
+                 team
+                 :level 'info))))))))
+
+(defun slack-ws-handle-user-typing (payload team)
+  (let* ((user (slack-user-name (plist-get payload :user) team))
+         (room (slack-room-find (plist-get payload :channel) team)))
+    (if (and user room
+             (slack-buffer-show-typing-p (get-buffer (slack-room-buffer-name room))))
+        (let ((limit (+ 3 (float-time))))
+          (with-slots (typing typing-timer) team
+            (if (and typing (equal room (oref typing room)))
+                (with-slots ((typing-limit limit)
+                             (typing-room room) users) typing
+                  (setq typing-limit limit)
+                  (let ((typing-user (make-instance 'slack-typing-user
+                                                    :limit limit
+                                                    :user-name user)))
+                    (setq users
+                          (cons typing-user
+                                (cl-remove-if #'(lambda (u)
+                                                  (string= (oref u user-name)
+                                                           user))
+                                              users))))))
+            (unless typing
+              (let ((new-typing (make-instance 'slack-typing
+                                               :room room :limit limit))
+                    (typing-user (make-instance 'slack-typing-user
+                                                :limit limit :user-name user)))
+                (oset new-typing users (list typing-user))
+                (setq typing new-typing))
+              (setq typing-timer
+                    (run-with-timer t 1 #'slack-user-typing team))))))))
+
+(defun slack-ws-handle-team-join (payload team)
+  (let ((user (slack-decode (plist-get payload :user))))
+    (slack-user-info-request
+     (plist-get user :id) team
+     :after-success #'(lambda ()
+                        (slack-log (format "User %s Joind Team: %s"
+                                           (plist-get (slack-user--find (plist-get user :id)
+                                                                        team)
+                                                      :name)
+                                           (slack-team-name team))
+                                   team
+                                   :level 'info)))))
+
+(defun slack-ws-handle-im-open (payload team)
+  (cl-labels
+      ((notify
+        (im)
+        (slack-room-history-request
+         im team
+         :after-success #'(lambda (&rest _ignore)
+                            (slack-log (format "Direct Message Channel with %s is Open"
+                                               (slack-user-name (oref im user) team))
+                                       team :level 'info))
+         :async t)))
+    (let ((exist (slack-room-find (plist-get payload :channel) team)))
+      (if exist
+          (progn
+            (oset exist is-open t)
+            (notify exist))
+        (with-slots (ims) team
+          (let ((im (slack-room-create
+                     (list :id (plist-get payload :channel)
+                           :user (plist-get payload :user))
+                     team 'slack-im)))
+            (setq ims (cons im ims))
+            (notify im)))))))
+
+(defun slack-ws-handle-im-close (payload team)
+  (let ((im (slack-room-find (plist-get payload :channel) team)))
+    (when im
+      (oset im is-open nil)
+      (slack-log (format "Direct Message Channel with %s is Closed"
+                         (slack-user-name (oref im user) team))
+                 team :level 'info))))
+
+(defun slack-ws-handle-message (payload team)
+  (let ((subtype (plist-get payload :subtype)))
+    (cond
+     ((and subtype (string= subtype "message_changed"))
+      (slack-ws-change-message payload team))
+     ((and subtype (string= subtype "message_deleted"))
+      (slack-ws-delete-message payload team))
+     ((and subtype (string= subtype "message_replied"))
+      (slack-thread-update-state payload team))
+     (t
+      (slack-ws-update-message payload team)))))
+
+(defun slack-ws-change-message (payload team)
+  (slack-if-let* ((room-id (plist-get payload :channel))
+                  (room (slack-room-find room-id team))
+                  (message-payload (plist-get payload :message))
+                  (ts (plist-get message-payload :ts))
+                  (base (slack-room-find-message room ts))
+                  (new-message-payload (plist-put message-payload :edited-at ts))
+                  (new-message (slack-message-create new-message-payload
+                                                     team
+                                                     :room room)))
+      (slack-message-update base team t
+                            (not (slack-message-changed--copy base new-message)))))
+
+
+(defun slack-ws-delete-message (payload team)
+  (slack-if-let* ((room-id (plist-get payload :channel))
+                  (room (slack-room-find room-id team))
+                  (ts (plist-get payload :deleted_ts))
+                  (message (slack-room-find-message room ts)))
+      (slack-message-deleted message room team)))
+
+(defun slack-ws-update-message (payload team)
+  (let ((m (slack-message-create payload team))
+        (bot_id (plist-get payload :bot_id)))
+    (when m
+      (if (and bot_id (not (slack-find-bot bot_id team)))
+          (slack-bot-info-request bot_id team #'(lambda (team) (slack-message-update m team)))
+        (slack-message-update m team)))))
+
+(defun slack-ws-handle-reply (payload team)
+  (let ((ok (plist-get payload :ok)))
+    (if (eq ok :json-false)
+        (let ((err (plist-get payload :error)))
+          (slack-log (format "Error code: %s msg: %s"
+                             (plist-get err :code)
+                             (plist-get err :msg))
+                     team))
+      (let ((message-id (plist-get payload :reply_to)))
+        (if (integerp message-id)
+            (slack-message-handle-reply
+             (slack-message-create payload team)
+             team))))))
+
+(defun slack-ws-handle-reaction-added (payload team)
+  (let* ((item (plist-get payload :item))
+         (item-type (plist-get item :type))
+         (reaction (make-instance 'slack-reaction
+                                  :name (plist-get payload :reaction)
+                                  :count 1
+                                  :users (list (plist-get payload :user)))))
+    (cl-labels
+        ((update-message (message)
+                         (slack-message-append-reaction message reaction item-type)
+                         (slack-message-update message team t t)))
+      (cond
+       ((string= item-type "file_comment")
+        (let* ((file-id (plist-get item :file))
+               (file (slack-file-find file-id team))
+               (comment-id (plist-get item :file_comment)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-with-file-comment comment-id file
+                           (slack-message-append-reaction file-comment reaction)
+                           (slack-message-update file-comment file team)
+                           (cl-loop for room in (append (oref team channels)
+                                                        (oref team ims)
+                                                        (oref team groups))
+                                    do (slack-if-let*
+                                           ((message
+                                             (if (oref file-comment is-intro)
+                                                 (slack-room-find-file-share-message
+                                                  room file-id)
+                                               (slack-room-find-file-comment-message
+                                                room comment-id))))
+                                           (update-message message)))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "file")
+        (let* ((file-id (plist-get item :file))
+               (file (slack-file-find file-id team)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-message-append-reaction file reaction)
+                         (slack-message-update file team)
+                         (cl-loop for channel in (slack-file-channel-ids file)
+                                  do (slack-if-let*
+                                         ((channel (slack-room-find channel team))
+                                          (message (slack-room-find-file-share-message
+                                                    channel (oref file id))))
+                                         (update-message message))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "message")
+        (slack-if-let* ((room (slack-room-find (plist-get item :channel) team))
+                        (message (slack-room-find-message room (plist-get item :ts))))
+            (progn
+              (update-message message)
+              (slack-reaction-notify payload team room))))))))
+
+(defun slack-ws-handle-reaction-removed (payload team)
+  (let* ((item (plist-get payload :item))
+         (item-type (plist-get item :type))
+         (reaction (make-instance 'slack-reaction
+                                  :name (plist-get payload :reaction)
+                                  :count 1
+                                  :users (list (plist-get payload :user)))))
+    (cl-labels
+        ((update-message (message)
+                         (slack-message-pop-reaction message reaction item-type)
+                         (slack-message-update message team t t)))
+      (cond
+       ((string= item-type "file_comment")
+        (let* ((file-id (plist-get item :file))
+               (file (slack-file-find file-id team))
+               (comment-id (plist-get item :file_comment)))
+
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-with-file-comment comment-id file
+                           (slack-message-pop-reaction file-comment reaction)
+                           (slack-message-update file-comment file team)
+                           (cl-loop for room in (append (oref team channels)
+                                                        (oref team ims)
+                                                        (oref team groups))
+                                    do (slack-if-let*
+                                           ((message
+                                             (if (oref file-comment is-intro)
+                                                 (slack-room-find-file-share-message
+                                                  room file-id)
+                                               (slack-room-find-file-comment-message
+                                                room comment-id))))
+                                           (update-message message)))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "file")
+        (let* ((file-id (plist-get item :file))
+               (file (slack-file-find file-id team)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-message-pop-reaction file reaction)
+                         (slack-message-update file team)
+                         (cl-loop for channel in (slack-file-channel-ids file)
+                                  do (slack-if-let*
+                                         ((channel (slack-room-find channel team))
+                                          (message (slack-room-find-file-share-message
+                                                    channel (oref file id))))
+                                         (update-message message))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "message")
+        (slack-if-let* ((room (slack-room-find (plist-get item :channel) team))
+                        (message (slack-room-find-message room (plist-get item :ts))))
+            (progn
+              (update-message message)
+              (slack-reaction-notify payload team room))))))))
+
+(defun slack-ws-handle-channel-created (payload team)
+  (let ((channel (slack-room-create (plist-get payload :channel)
+                                    team 'slack-channel)))
+    (push channel (oref team channels))
+    (slack-room-info-request channel team)
+    (slack-log (format "Created channel %s"
+                       (slack-room-display-name channel))
+               team :level 'info)))
+
+(defun slack-ws-handle-room-archive (payload team)
+  (let* ((id (plist-get payload :channel))
+         (room (slack-room-find id team)))
+    (oset room is-archived t)
+    (slack-log (format "Channel: %s is archived"
+                       (slack-room-display-name room))
+               team :level 'info)))
+
+(defun slack-ws-handle-room-unarchive (payload team)
+  (let* ((id (plist-get payload :channel))
+         (room (slack-room-find id team)))
+    (oset room is-archived nil)
+    (slack-log (format "Channel: %s is unarchived"
+                       (slack-room-display-name room))
+               team :level 'info)))
+
+(defun slack-ws-handle-channel-deleted (payload team)
+  (let ((id (plist-get payload :channel)))
+    (slack-room-deleted id team)))
+
+(defun slack-ws-handle-room-rename (payload team)
+  (let* ((c (plist-get payload :channel))
+         (room (slack-room-find (plist-get c :id) team))
+         (old-name (slack-room-name room))
+         (new-name (plist-get c :name)))
+    (oset room name new-name)
+    (slack-log (format "Renamed channel from %s to %s"
+                       old-name
+                       new-name)
+               team :level 'info)))
+(defun slack-ws-handle-group-joined (payload team)
+  (let ((group (slack-room-create (plist-get payload :channel) team 'slack-group)))
+    (push group (oref team groups))
+    (slack-room-info-request group team)
+    (slack-log (format "Joined group %s"
+                       (slack-room-display-name group))
+               team :level 'info)))
+
+(defun slack-ws-handle-channel-joined (payload team)
+  (let ((channel (slack-room-find (plist-get (plist-get payload :channel) :id) team)))
+    (slack-room-info-request channel team)
+    (slack-log (format "Joined channel %s"
+                       (slack-room-display-name channel))
+               team :level 'info)))
+
+(defun slack-ws-handle-presence-change (payload team)
+  (let* ((id (plist-get payload :user))
+         (user (slack-user--find id team))
+         (presence (plist-get payload :presence)))
+    (plist-put user :presence presence)))
+
+(defun slack-ws-handle-bot (payload team)
+  (let ((bot (plist-get payload :bot)))
+    (with-slots (bots) team
+      (push bot bots))))
+
+(defun slack-ws-handle-file-created (payload team)
+  (slack-if-let* ((file-id (plist-get (plist-get payload :file) :id))
+                  (room (slack-file-room-obj team))
+                  (buffer (slack-buffer-find 'slack-file-list-buffer
+                                             room
+                                             team)))
+      (slack-file-request-info file-id 1 team
+                               #'(lambda (file _team)
+                                   (slack-buffer-update buffer file)))))
+
+(defun slack-ws-handle-file-deleted (payload team)
+  (let ((file-id (plist-get payload :file_id))
+        (room (slack-file-room-obj team)))
+    (with-slots (messages) room
+      (setq messages (cl-remove-if #'(lambda (f)
+                                       (string= file-id (oref f id)))
+                                   messages)))))
+
+(defun slack-ws-cancel-ping-timer (team)
+  (with-slots (ping-timer) team
+    (if (timerp ping-timer)
+        (cancel-timer ping-timer))
+    (setq ping-timer nil)))
+
+(defun slack-ws-set-ping-timer (team)
+  (slack-ws-cancel-ping-timer team)
+  (cl-labels ((ping ()
+                    (slack-ws-ping team)))
+    (oset team ping-timer (run-at-time 10 nil #'ping))))
+
+(defun slack-ws-current-time-str ()
+  (number-to-string (time-to-seconds (current-time))))
+
+(defun slack-ws-ping (team)
+  (slack-message-inc-id team)
+  (with-slots (message-id) team
+    (let* ((time (slack-ws-current-time-str))
+           (m (list :id message-id
+                    :type "ping"
+                    :time time))
+           (json (json-encode m)))
+      (slack-ws-set-check-ping-timer team time)
+      (slack-ws-send json team)
+      (slack-log (format "Send PING: %s" time)
+                 team :level 'trace))))
+
+(defun slack-ws-set-check-ping-timer (team time)
+  (puthash time (run-at-time (oref team check-ping-timeout-sec)
+                             nil #'slack-ws-ping-timeout team)
+           (oref team ping-check-timers)))
+
+
+(defun slack-ws-ping-timeout (team)
+  (slack-log "Slack Websocket PING Timeout." team :level 'warn)
+  (slack-ws-close team)
+  (slack-ws-set-reconnect-timer team))
+
+(defun slack-ws-cancel-ping-check-timers (team)
+  (maphash #'(lambda (key value)
+               (if (timerp value)
+                   (cancel-timer value)))
+           (oref team ping-check-timers))
+  (slack-team-init-ping-check-timers team))
+
+(defvar slack-disconnected-timer nil)
+(defun slack-notify-abandon-reconnect (team)
+  (unless slack-disconnected-timer
+    (setq slack-disconnected-timer
+          (run-with-idle-timer 5 t
+                               #'(lambda ()
+                                   (slack-log
+                                    "Reconnect Count Exceeded. Manually invoke `slack-start'."
+                                    team :level 'error))))))
+
+(defun slack-cancel-notify-adandon-reconnect ()
+  (if (and slack-disconnected-timer
+           (timerp slack-disconnected-timer))
+      (progn
+        (cancel-timer slack-disconnected-timer)
+        (setq slack-disconnected-timer nil))))
+
+(defun slack-request-api-test (team &optional after-success)
+  (cl-labels
+      ((on-success (&key data &allow-other-keys)
+                   (slack-request-handle-error
+                    (data "slack-request-api-test")
+                    (if after-success
+                        (funcall after-success)))))
+    (slack-request
+     (slack-request-create
+      slack-api-test-url
+      team
+      :type "POST"
+      :success #'on-success))))
+
+(defun slack-on-authorize-for-reconnect (data team)
+  (let ((team-data (plist-get data :team))
+        (self-data (plist-get data :self)))
+    (oset team ws-url (plist-get data :url))
+    (oset team domain (plist-get team-data :domain))
+    (oset team id (plist-get team-data :id))
+    (oset team name (plist-get team-data :name))
+    (oset team self self-data)
+    (oset team self-id (plist-get self-data :id))
+    (oset team self-name (plist-get self-data :name))
+    (cl-labels
+        ((on-open ()
+                  (slack-channel-list-update team)
+                  (slack-group-list-update team)
+                  (slack-im-list-update team)
+                  (slack-bot-list-update team)
+                  (cl-loop for buffer in (oref team slack-message-buffer)
+                           do (slack-if-let*
+                                  ((live-p (buffer-live-p buffer))
+                                   (slack-buffer (with-current-buffer buffer
+                                                   (and (bound-and-true-p
+                                                         slack-current-buffer)
+                                                        slack-current-buffer))))
+                                  (slack-buffer-load-missing-messages
+                                   slack-buffer)))
+                  (slack-team-kill-buffers
+                   team :except '(slack-message-buffer
+                                  slack-message-edit-buffer
+                                  slack-message-share-buffer
+                                  slack-room-message-compose-buffer))))
+      (slack-ws-open team :on-open #'on-open))))
+
+(defun slack-authorize-for-reconnect (team)
+  (cl-labels
+      ((on-error (&key error-thrown symbol-status &allow-other-keys)
+                 (slack-log (format "Slack Reconnect Failed: %s, %s"
+                                    error-thrown
+                                    symbol-status)
+                            team)
+                 (slack-ws-set-reconnect-timer team))
+       (on-success (data)
+                   (slack-on-authorize-for-reconnect data team)))
+    (slack-authorize team #'on-error #'on-success)))
+
+(defun slack-ws-reconnect (team &optional force)
+  "Reconnect if `reconnect-count' is not exceed `reconnect-count-max'.
+if FORCE is t, ignore `reconnect-count-max'.
+TEAM is one of `slack-teams'"
+  (cl-labels ((abort (team)
+                     (slack-notify-abandon-reconnect team)
+                     (slack-ws-close team t))
+              (use-reconnect-url ()
+                                 (slack-log "Reconnect with reconnect-url" team)
+                                 (slack-ws-open team
+                                                :ws-url (oref team reconnect-url)))
+              (do-reconnect (team)
+                            (cl-incf (oref team reconnect-count))
+                            (slack-ws-close team)
+                            (if (< 0 (length (oref team reconnect-url)))
+                                (slack-request-api-test team
+                                                        #'use-reconnect-url)
+                              (slack-authorize-for-reconnect team))))
+    (with-slots
+        (reconnect-count (reconnect-max reconnect-count-max)) team
+      (if (and (not force) reconnect-max (< reconnect-max reconnect-count))
+          (abort team)
+        (do-reconnect team)
+        (slack-log (format "Slack Websocket Try To Reconnect %s/%s"
+                           reconnect-count
+                           reconnect-max)
+                   team
+                   :level 'warn
+                   )))))
+
+(defun slack-ws-set-reconnect-timer (team)
+  (slack-ws-cancel-reconnect-timer team)
+  (cl-labels
+      ((on-timeout ()
+                   (slack-ws-reconnect team)))
+    (oset team reconnect-timer
+          (run-at-time (oref team reconnect-after-sec)
+                       nil
+                       #'on-timeout))))
+
+(defun slack-ws-cancel-reconnect-timer (team)
+  (with-slots (reconnect-timer) team
+    (if (timerp reconnect-timer)
+        (cancel-timer reconnect-timer))
+    (setq reconnect-timer nil)))
+
+(defun slack-ws-handle-pong (payload team)
+  (let* ((key (plist-get payload :time))
+         (timer (gethash key (oref team ping-check-timers))))
+    (slack-log (format "Receive PONG: %s" key)
+               team :level 'trace)
+    (slack-ws-set-ping-timer team)
+    (when timer
+      (cancel-timer timer)
+      (remhash key (oref team ping-check-timers))
+      (slack-log (format "Remove PING Check Timer: %s" key)
+                 team :level 'trace))))
+
+(defun slack-ws-handle-room-marked (payload team)
+  (let* ((room (slack-room-find (plist-get payload :channel) team))
+         (ts (plist-get payload :ts))
+         (message (and room (slack-room-find-message room ts))))
+    (when room
+      (with-slots (unread-count-display last-read) room
+        (setq unread-count-display
+              (plist-get payload :unread_count_display))
+        (when (and message
+                   (not (slack-thread-message-p message)))
+          (setq last-read ts)))
+      (slack-update-modeline))))
+
+(defun slack-ws-handle-file-comment-added (payload team)
+  (let* ((file-id (plist-get payload :file_id))
+         (file (slack-file-find file-id team))
+         (comment (slack-file-comment-create (plist-get payload :comment)
+                                             file-id)))
+    (slack-with-file file-id team
+      (slack-add-comment file comment)
+      (slack-file-insert-comment file (oref comment id) team))))
+
+(defun slack-ws-handle-file-comment-deleted (payload team)
+  (let* ((file-id (plist-get (plist-get payload :file)
+                             :id))
+         (comment-id (plist-get payload :comment)))
+    (slack-with-file file-id team
+      (slack-delete-comment file comment-id)
+      (slack-file-delete-comment file comment-id team))))
+
+(defun slack-ws-handle-thread-marked (payload team)
+  (let* ((subscription (plist-get payload :subscription))
+         (thread-ts (plist-get subscription :thread_ts))
+         (channel (plist-get subscription :channel))
+         (room (slack-room-find channel team))
+         (parent (and room (slack-room-find-message room thread-ts))))
+    (when (and parent (oref parent thread))
+      (slack-thread-marked (oref parent thread) subscription))))
+
+(defun slack-ws-handle-thread-subscribed (payload team)
+  (let* ((thread-data (plist-get payload :subscription))
+         (room (slack-room-find (plist-get thread-data :channel) team))
+         (message (and (slack-room-find-message room (plist-get thread-data :thread_ts))))
+         (thread (and message (oref message thread))))
+    (when thread
+      (slack-thread-marked thread thread-data))))
+
+(defun slack-ws-handle-user-change (payload team)
+  (let* ((user (plist-get payload :user))
+         (id (plist-get user :id)))
+    (with-slots (users) team
+      (setq users
+            (cons user
+                  (cl-remove-if #'(lambda (u)
+                                    (string= id (plist-get u :id)))
+                                users))))))
+
+(defun slack-ws-handle-member-joined-channel (payload team)
+  (let ((user (plist-get payload :user))
+        (channel (slack-room-find (plist-get payload :channel) team)))
+    (when channel
+      (cl-pushnew user (oref channel members)
+                  :test #'string=))))
+
+(defun slack-ws-handle-member-left_channel (payload team)
+  (let ((user (plist-get payload :user))
+        (channel (slack-room-find (plist-get payload :channel) team)))
+    (when channel
+      (oset channel members
+            (cl-remove-if #'(lambda (e) (string= e user))
+                          (oref channel members))))))
+
+(defun slack-ws-handle-dnd-updated (payload team)
+  (let* ((user (slack-user--find (plist-get payload :user) team))
+         (updated (slack-user-update-dnd-status user (plist-get payload :dnd_status))))
+    (oset team users
+          (cons updated (cl-remove-if #'(lambda (user) (string= (plist-get user :id)
+                                                                (plist-get updated :id)))
+                                      (oref team users))))))
+
+;; [star_added event | Slack](https://api.slack.com/events/star_added)
+(defun slack-ws-handle-star-added (payload team)
+  (let* ((item (plist-get payload :item))
+         (item-type (plist-get item :type)))
+    (cl-labels
+        ((update-message (message)
+                         (slack-message-star-added message)
+                         (slack-message-update message team t t)))
+      (cond
+       ((string= item-type "file_comment")
+        (let* ((file-id (plist-get (plist-get item :file) :id))
+               (comment-id (plist-get (plist-get item :comment) :id))
+               (file (slack-file-find file-id team)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-with-file-comment comment-id file
+                           (slack-message-star-added file-comment)
+                           (slack-message-update file-comment file team))
+
+                         (cl-loop for channel in (slack-file-channel-ids file)
+                                  do (slack-if-let* ((channel (slack-room-find channel team))
+                                                     (message (slack-room-find-file-comment-message
+                                                               channel comment-id)))
+                                         (update-message message))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "file")
+        (let* ((file-id (plist-get (plist-get item :file) :id))
+               (file (slack-file-find file-id team)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-message-star-added file)
+                         (slack-message-update file team)
+
+                         (cl-loop for channel in (slack-file-channel-ids file)
+                                  do (slack-if-let* ((channel (slack-room-find channel team))
+                                                     (message (slack-room-find-file-share-message
+                                                               channel (oref file id))))
+                                         (update-message message))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "message")
+        (slack-if-let* ((room (slack-room-find (plist-get item :channel) team))
+                        (ts (plist-get (plist-get item :message) :ts))
+                        (message (slack-room-find-message room ts)))
+            (update-message message)))))
+    (slack-if-let* ((star (oref team star)))
+        (slack-star-add star item team))))
+
+
+(defun slack-ws-handle-star-removed (payload team)
+  (let* ((item (plist-get payload :item))
+         (item-type (plist-get item :type)))
+    (cl-labels
+        ((update-message (message)
+                         (slack-message-star-removed message)
+                         (slack-message-update message team t t)))
+      (cond
+       ((string= item-type "file_comment")
+        (let* ((file-id (plist-get (plist-get item :file) :id))
+               (comment-id (plist-get (plist-get item :comment) :id))
+               (file (slack-file-find file-id team)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-with-file-comment comment-id file
+                           (slack-message-star-removed file-comment)
+                           (slack-message-update file-comment file team))
+
+                         (cl-loop for channel in (slack-file-channel-ids file)
+                                  do (slack-if-let* ((channel (slack-room-find channel team))
+                                                     (message (slack-room-find-file-comment-message
+                                                               channel comment-id)))
+                                         (update-message message))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "file")
+        (let* ((file-id (plist-get (plist-get item :file) :id))
+               (file (slack-file-find file-id team)))
+          (cl-labels
+              ((update (&rest _args)
+                       (slack-with-file file-id team
+                         (slack-message-star-removed file)
+                         (slack-message-update file team)
+
+                         (cl-loop for channel in (slack-file-channel-ids file)
+                                  do (slack-if-let* ((channel (slack-room-find channel team))
+                                                     (message (slack-room-find-file-share-message
+                                                               channel (oref file id))))
+                                         (update-message message))))))
+            (if file (update)
+              (slack-file-request-info file-id 1 team #'update)))))
+       ((string= item-type "message")
+        (slack-if-let* ((room (slack-room-find (plist-get item :channel) team))
+                        (ts (plist-get (plist-get item :message) :ts))
+                        (message (slack-room-find-message room ts)))
+            (update-message message)))))
+
+    (slack-if-let* ((star (oref team star)))
+        (slack-star-remove star item team))))
+
+(defun slack-ws-handle-file-comment-edited (payload team)
+  (let* ((file-id (plist-get (plist-get payload :file) :id))
+         (comment (slack-file-comment-create (plist-get payload :comment) file-id))
+         (file (slack-file-find file-id team))
+         (edited-at (plist-get payload :event_ts)))
+    (cl-labels
+        ((update (file)
+                 (slack-file-update-comment file comment team edited-at)
+                 (slack-with-file-comment (oref comment id) file
+                   (slack-message-update file-comment file team))))
+      (if file (update file)
+        (slack-file-request-info file-id 1 team #'update)))))
+
+(provide 'slack-websocket)
+;;; slack-websocket.el ends here
+