diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/slack-20180913.651/slack-websocket.el')
-rw-r--r-- | configs/shared/emacs/.emacs.d/elpa/slack-20180913.651/slack-websocket.el | 1073 |
1 files changed, 1073 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/slack-20180913.651/slack-websocket.el b/configs/shared/emacs/.emacs.d/elpa/slack-20180913.651/slack-websocket.el new file mode 100644 index 000000000000..b81d72b268b9 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/slack-20180913.651/slack-websocket.el @@ -0,0 +1,1073 @@ +;;; 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) +(require 'slack-dialog) +(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 + (websocket-send-text ws-conn (json-encode payload)) + (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 (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)) + ((or (string= type "channel_left") + (string= type "group_left")) + (slack-ws-handle-room-left 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)) + ((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)) + ((string= type "app_conversation_invite_request") + (slack-ws-handle-app-conversation-invite-request decoded-payload team)) + ((string= type "commands_changed") + (slack-ws-handle-commands-changed decoded-payload team)) + ((string= type "dialog_opened") + (slack-ws-handle-dialog-opened 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 team))) + (let ((team-name (slack-team-name team)) + (room-name (slack-room-name room team)) + (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 team)))) + (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 (slack-message-create 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 ((subtype (plist-get payload :subtype))) + (if (string= subtype "bot_message") + (slack-ws-update-bot-message payload team) + (slack-message-update (slack-message-create payload team) + team)))) + +(defun slack-ws-update-bot-message (payload team) + (let* ((bot-id (plist-get payload :bot_id)) + (username (plist-get payload :username)) + (user (plist-get payload :user)) + (bot (or (slack-find-bot bot-id team) + (slack-find-bot-by-name username team) + (slack-user--find user team))) + (message (slack-message-create payload team))) + (if bot + (slack-message-update message team) + (slack-bot-info-request bot-id + team + #'(lambda (team) + (slack-message-update message team)))))) + +(defun slack-ws-remove-from-resend-queue (payload team) + (with-slots (waiting-send) team + (slack-log (format "waiting-send: %s" (length waiting-send)) + team :level 'trace) + (setq waiting-send + (cl-remove-if #'(lambda (e) (eq (plist-get e :id) + (plist-get payload :reply_to))) + waiting-send)) + (slack-log (format "waiting-send: %s" (length waiting-send)) + team :level 'trace))) + +(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))) + (when (integerp message-id) + (slack-message-handle-reply + (slack-message-create payload team) + team) + (slack-ws-remove-from-resend-queue payload team)))))) + +(defun slack-ws-handle-reaction-added-to-file (file-id reaction team) + (let* ((file (slack-file-find file-id team)) + (item-type "file")) + (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)))) + + (progn + (slack-message-append-reaction message + reaction + item-type + file-id) + (slack-message-update message + team t t))))))) + (if file (update) + (slack-file-request-info file-id 1 team #'update))))) + +(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") + (slack-ws-handle-reaction-added-to-file (plist-get item :file) + reaction + team)) + ((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-from-file (file-id reaction team) + (let* ((file (slack-file-find file-id team)) + (item-type "file")) + (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)))) + (progn + (slack-message-pop-reaction message + reaction + item-type + file-id) + (slack-message-update message team t t))))))) + (if file (update) + (slack-file-request-info file-id 1 team #'update))))) + +(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") + (slack-ws-handle-reaction-removed-from-file (plist-get item :file) + reaction + team)) + ((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)) + 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)) + 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)) + 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 team)) + (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)) + 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)) + 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))) + (slack-ws-set-check-ping-timer team time) + (slack-ws-send m 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) + (slack-ws-remove-from-resend-queue 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) + (slack-if-let* ((channel (plist-get payload :channel)) + (room (slack-room-find channel team)) + (ts (plist-get payload :ts)) + (unread-count-display (plist-get payload :unread_count_display))) + (progn + (oset room unread-count-display unread-count-display) + (oset room last-read ts) + (slack-update-modeline)))) + +(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) + (slack-if-let* ((user (plist-get payload :user)) + (channel (slack-room-find (plist-get payload :channel) team))) + (progn + (cl-pushnew user (oref channel members) + :test #'string=) + (slack-log (format "%s joined %s" + (slack-user-name user team) + (slack-room-name channel team)) + team + :level 'info)))) + +(defun slack-ws-handle-member-left_channel (payload team) + (slack-if-let* ((user (plist-get payload :user)) + (channel (slack-room-find (plist-get payload :channel) team))) + (progn + (oset channel members + (cl-remove-if #'(lambda (e) (string= e user)) + (oref channel members))) + (slack-log (format "%s left %s" + (slack-user-name user team) + (slack-room-name channel team)) + team + :level 'info)))) + +(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-to-file (file-id team) + (let ((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)))) + (if file (update) + (slack-file-request-info file-id 1 team #'update))))) + +(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") + (slack-ws-handle-star-added-to-file (plist-get (plist-get item :file) :id) + team)) + ((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)))) + + +;; [2018-07-25 16:30:03] (:type star_added :user U1013370U :item (:type file :file (:id FBWDT9VH8) :date_create 1532503802 :file_id FBWDT9VH8 :user_id USLACKBOT) :event_ts 1532503802.000378) +(defun slack-ws-handle-star-removed-from-file (file-id team) + (let ((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)))) + (if file (update) + (slack-file-request-info file-id 1 team #'update))))) + +(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") + (slack-ws-handle-star-removed-from-file (plist-get (plist-get item :file) :id) + team)) + ((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-app-conversation-invite-request (payload team) + (let* ((app-user (plist-get payload :app_user)) + (channel-id (plist-get payload :channel_id)) + (invite-message-ts (plist-get payload :invite_message_ts)) + (scope-info (plist-get payload :scope_info)) + (room (slack-room-find channel-id team))) + (if (yes-or-no-p (format "%s\n%s\n" + (format "%s would like to do following in %s" + (slack-user-name app-user team) + (slack-room-name room team)) + (mapconcat #'(lambda (scope) + (format "* %s" + (plist-get scope + :short_description))) + scope-info + "\n"))) + (slack-app-conversation-allow-invite-request team + :channel channel-id + :app-user app-user + :invite-message-ts invite-message-ts) + (slack-app-conversation-deny-invite-request team + :channel channel-id + :app-user app-user + :invite-message-ts invite-message-ts)))) + +(cl-defun slack-app-conversation-allow-invite-request (team &key channel + app-user + invite-message-ts) + (let ((url "https://slack.com/api/apps.permissions.internal.add") + (params (list (cons "channel" channel) + (cons "app_user" app-user) + (cons "invite_message_ts" invite-message-ts) + (cons "did_confirm" "true") + (cons "send_ephemeral_error_message" "true")))) + (cl-labels + ((log-error (err) + (slack-log (format "Error: %s, URL: %s, PARAMS: %s" + err + url + params) + team :level 'error)) + (on-success (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-app-conversation-allow-invite-request" + #'log-error) + (message "DATA: %s" data)))) + (slack-request + (slack-request-create + url + team + :type "POST" + :params params + :success #'on-success))))) + +(cl-defun slack-app-conversation-deny-invite-request (team &key channel + app-user + invite-message-ts) + (let ((url "https://slack.com/api/apps.permissions.internal.denyAdd") + (params (list (cons "channel" channel) + (cons "app_user" app-user) + (cons "invite_message_ts" invite-message-ts)))) + (cl-labels + ((log-error (err) + (slack-log (format "Error: %s, URL: %s, PARAMS: %s" + err + url + params) + team :level 'error)) + (on-success (&key data &allow-other-keys) + (slack-request-handle-error + (data "slack-app-conversation-deny-invite-request" + #'log-error) + (message "DATA: %s" data)))) + (slack-request + (slack-request-create + url + team + :type "POST" + :params params + :success #'on-success))))) + +(defun slack-ws-handle-commands-changed (payload team) + (let ((commands-updated (mapcar #'slack-command-create + (plist-get payload :commands_updated))) + (commands-removed (mapcar #'slack-command-create + (plist-get payload :commands_removed))) + (commands '())) + (cl-loop for command in (oref team commands) + if (and (not (cl-find-if #'(lambda (e) (slack-equalp command e)) + commands-removed)) + (not (cl-find-if #'(lambda (e) (slack-equalp command e)) + commands-updated))) + do (push command commands)) + (cl-loop for command in commands-updated + do (push command commands)) + (oset team commands commands))) + +(defun slack-ws-handle-dialog-opened (payload team) + (slack-if-let* + ((dialog-id (plist-get payload :dialog_id)) + (client-token (plist-get payload :client_token)) + (valid-client-tokenp (string= (slack-team-client-token team) + client-token))) + (slack-dialog-get dialog-id team))) + +(defun slack-ws-handle-room-left (payload team) + (slack-if-let* ((room (slack-room-find (plist-get payload :channel) + team))) + (progn + (when (slot-exists-p room 'is-member) + (oset room is-member nil)) + (when (and (not (slack-channel-p room)) (slack-group-p room)) + (oset team groups + (cl-remove-if #'(lambda (e) + (slack-room-equal-p e room)) + (oref team groups)))) + (slack-log (format "You left %s" (slack-room-name room team)) + team :level 'info)))) + + +(provide 'slack-websocket) +;;; slack-websocket.el ends here + |