;;; slack-room.el --- slack generic room interface -*- lexical-binding: t; -*- ;; Copyright (C) 2015 南優也 ;; Author: 南優也 ;; 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 . ;;; Commentary: ;; ;;; Code: (require 'eieio) (require 'lui) (require 'slack-util) (require 'slack-request) (require 'slack-message) (require 'slack-pinned-item) (defvar slack-buffer-function) (defconst slack-room-pins-list-url "https://slack.com/api/pins.list") (defclass slack-room () ((name :initarg :name :type (or null string) :initform nil) (id :initarg :id) (created :initarg :created) (latest :initarg :latest) (unread-count :initarg :unread_count :initform 0 :type integer) (unread-count-display :initarg :unread_count_display :initform 0 :type integer) (messages :initarg :messages :initform ()) (team-id :initarg :team-id) (last-read :initarg :last_read :type string :initform "0"))) (defgeneric slack-room-name (room)) (defgeneric slack-room-history (room team &optional oldest after-success sync)) (defgeneric slack-room-update-mark-url (room)) (defmethod slack-equalp ((this slack-room) other) (string= (oref this id) (oref other id))) (defmethod slack-merge ((this slack-room) other) "except MESSAGES" (oset this name (oref other name)) (oset this id (oref other id)) (oset this created (oref other created)) (oset this latest (oref other latest)) (oset this unread-count (oref other unread-count)) (oset this unread-count-display (oref other unread-count-display)) (oset this team-id (oref other team-id)) (unless (string= "0" (oref other last-read)) (oset this last-read (oref other last-read)))) (defun slack-room-create (payload team class) (cl-labels ((prepare (p) (plist-put p :members (append (plist-get p :members) nil)) (plist-put p :team-id (oref team id)) p)) (let* ((attributes (slack-collect-slots class (prepare payload))) (room (apply #'make-instance class attributes))) (oset room latest (slack-message-create (plist-get payload :latest) team :room room)) room))) (defmethod slack-room-subscribedp ((_room slack-room) _team) nil) (defmethod slack-room-buffer-name ((room slack-room)) (concat "*Slack*" " : " (slack-room-display-name room))) (cl-defmacro slack-select-from-list ((alist prompt &key initial) &body body) "Bind candidates from selected." (declare (indent 2) (debug t)) (let ((key (cl-gensym))) `(let* ((,key (let ((completion-ignore-case t)) (funcall slack-completing-read-function (format "%s" ,prompt) ,alist nil t ,initial))) (selected (cdr (cl-assoc ,key ,alist :test #'string=)))) ,@body selected))) (defmethod slack-room-hidden-p ((room slack-room)) (slack-room-hiddenp room)) (defun slack-room-hiddenp (room) (or (not (slack-room-member-p room)) (slack-room-archived-p room) (not (slack-room-open-p room)))) (defmacro slack-room-names (rooms &optional filter) `(cl-labels ((latest-ts (room) (with-slots (latest) room (if latest (oref latest ts) "0"))) (sort-rooms (rooms) (nreverse (cl-sort rooms #'string< :key #'(lambda (name-with-room) (latest-ts (cdr name-with-room))))))) (sort-rooms (cl-loop for room in (if ,filter (funcall ,filter ,rooms) ,rooms) collect (cons (slack-room-label room) room))))) (defun slack-room-select (rooms) (let* ((alist (slack-room-names rooms #'(lambda (rs) (cl-remove-if #'slack-room-hidden-p rs))))) (slack-select-from-list (alist "Select Channel: ")))) (cl-defun slack-room-list-update (url success team &key (sync t)) (slack-request (slack-request-create url team :success success))) (defun slack-room-find-message (room ts) (cl-find-if #'(lambda (m) (string= ts (oref m ts))) (oref room messages) :from-end t)) (defun slack-room-find-thread-parent (room thread-message) (slack-room-find-message room (oref thread-message thread-ts))) (defmethod slack-message-thread ((this slack-message) _room) (oref this thread)) (defmethod slack-message-thread ((this slack-reply-broadcast-message) room) (let ((message (slack-room-find-message room (or (oref this broadcast-thread-ts) (oref this thread-ts))))) (slack-message-thread message room))) (defun slack-room-find-thread (room ts) (let ((message (slack-room-find-message room ts))) (when message (slack-message-thread message room)))) (defmethod slack-room-team ((room slack-room)) (slack-team-find (oref room team-id))) (defmethod slack-room-display-name ((room slack-room)) (let ((room-name (slack-room-name room))) (if slack-display-team-name (format "%s - %s" (oref (slack-room-team room) name) room-name) room-name))) (defmethod slack-room-label-prefix ((_room slack-room)) " ") (defmethod slack-room-unread-count-str ((room slack-room)) (with-slots (unread-count-display) room (if (< 0 unread-count-display) (concat " (" (number-to-string unread-count-display) ")") ""))) (defmethod slack-room-label ((room slack-room)) (format "%s%s%s" (slack-room-label-prefix room) (slack-room-display-name room) (slack-room-unread-count-str room))) (defmethod slack-room-name ((room slack-room)) (oref room name)) (defun slack-room-sort-messages (messages) (cl-sort messages #'string< :key #'(lambda (m) (oref m ts)))) (defun slack-room-reject-thread-message (messages) (cl-remove-if #'(lambda (m) (and (not (eq (eieio-object-class-name m) 'slack-reply-broadcast-message)) (slack-thread-message-p m))) messages)) (defmethod slack-room-sorted-messages ((room slack-room)) (with-slots (messages) room (slack-room-sort-messages (copy-sequence messages)))) (defmethod slack-room-set-prev-messages ((room slack-room) prev-messages) (slack-room-set-messages room (append (oref room messages) prev-messages))) (defmethod slack-room-append-messages ((room slack-room) messages) (slack-room-set-messages room (append messages (oref room messages)))) (defmethod slack-room-update-latest ((room slack-room) message) (when (and message (not (slack-thread-message-p message))) (with-slots (latest) room (if (or (null latest) (string< (oref latest ts) (oref message ts))) (setq latest message))))) (defmethod slack-room-push-message ((room slack-room) message) (with-slots (messages) room (setq messages (cl-remove-if #'(lambda (n) (slack-message-equal message n)) messages)) (push message messages))) (defmethod slack-room-set-messages ((room slack-room) messages) (let* ((sorted (slack-room-sort-messages (cl-delete-duplicates messages :test #'slack-message-equal))) (oldest (car sorted)) (latest (car (last sorted)))) (oset room messages sorted) (slack-room-update-latest room latest))) (defmethod slack-room-prev-messages ((room slack-room) from) (with-slots (messages) room (cl-remove-if #'(lambda (m) (or (string< from (oref m ts)) (string= from (oref m ts)))) (slack-room-sort-messages (copy-sequence messages))))) (defmethod slack-room-update-mark ((room slack-room) team ts) (cl-labels ((on-update-mark (&key data &allow-other-keys) (slack-request-handle-error (data "slack-room-update-mark")))) (with-slots (id) room (slack-request (slack-request-create (slack-room-update-mark-url room) team :type "POST" :params (list (cons "channel" id) (cons "ts" ts)) :success #'on-update-mark))))) (defun slack-room-pins-list () (interactive) (slack-if-let* ((buf slack-current-buffer)) (slack-buffer-display-pins-list buf))) (defun slack-select-rooms () (interactive) (let* ((team (slack-team-select)) (room (slack-room-select (cl-loop for team in (list team) append (with-slots (groups ims channels) team (append ims groups channels)))))) (slack-room-display room team))) (defun slack-create-room (url team success) (slack-request (slack-request-create url team :type "POST" :params (list (cons "name" (read-from-minibuffer "Name: "))) :success success))) (defun slack-room-rename (url room-alist-func) (cl-labels ((on-rename-success (&key data &allow-other-keys) (slack-request-handle-error (data "slack-room-rename")))) (let* ((team (slack-team-select)) (room-alist (funcall room-alist-func team)) (room (slack-select-from-list (room-alist "Select Channel: "))) (name (read-from-minibuffer "New Name: "))) (slack-request (slack-request-create url team :params (list (cons "channel" (oref room id)) (cons "name" name)) :success #'on-rename-success))))) (defmacro slack-current-room-or-select (room-alist-func &optional select) `(if (and (not ,select) (bound-and-true-p slack-current-buffer) (slot-boundp slack-current-buffer 'room)) (oref slack-current-buffer room) (let* ((room-alist (funcall ,room-alist-func))) (slack-select-from-list (room-alist "Select Channel: "))))) (defmacro slack-room-invite (url room-alist-func) `(cl-labels ((on-group-invite (&key data &allow-other-keys) (slack-request-handle-error (data "slack-room-invite") (if (plist-get data :already_in_group) (message "User already in group") (message "Invited!"))))) (let* ((team (slack-team-select)) (room (slack-current-room-or-select #'(lambda () (funcall ,room-alist-func team #'(lambda (rooms) (cl-remove-if #'slack-room-archived-p rooms)))))) (user-id (plist-get (slack-select-from-list ((slack-user-names team) "Select User: ")) :id))) (slack-request (slack-request-create ,url team :params (list (cons "channel" (oref room id)) (cons "user" user-id)) :success #'on-group-invite))))) (defmethod slack-room-member-p ((_room slack-room)) t) (defmethod slack-room-archived-p ((_room slack-room)) nil) (defmethod slack-room-open-p ((_room slack-room)) t) (defmethod slack-room-equal-p ((room slack-room) other) (string= (oref room id) (oref other id))) (defun slack-room-deleted (id team) (let ((room (slack-room-find id team))) (cond ((object-of-class-p room 'slack-channel) (with-slots (channels) team (setq channels (cl-delete-if #'(lambda (c) (slack-room-equal-p room c)) channels))) (message "Channel: %s deleted" (slack-room-display-name room)))))) (cl-defun slack-room-request-with-id (url id team success) (slack-request (slack-request-create url team :params (list (cons "channel" id)) :success success))) (defmethod slack-room-inc-unread-count ((room slack-room)) (cl-incf (oref room unread-count-display))) (defun slack-room-find-by-name (name team) (cl-labels ((find-by-name (rooms name) (cl-find-if #'(lambda (e) (string= name (slack-room-name e))) rooms))) (or (find-by-name (oref team groups) name) (find-by-name (oref team channels) name) (find-by-name (oref team ims) name)))) (defmethod slack-room-info-request-params ((room slack-room)) (list (cons "channel" (oref room id)))) (defmethod slack-room-create-info-request ((room slack-room) team) (cl-labels ((on-success (&key data &allow-other-keys) (slack-request-handle-error (data "slack-room-info-request" #'(lambda (e) (if (not (string= e "user_disabled")) (message "Failed to request slack-room-info-request: %s" e)))) (slack-room-update-info room data team)))) (slack-request-create (slack-room-get-info-url room) team :params (slack-room-info-request-params room) :success #'on-success))) (defmethod slack-room-info-request ((room slack-room) team) (slack-request (slack-room-create-info-request room team))) (defmethod slack-room-get-members ((room slack-room)) (oref room members)) (defun slack-room-user-select () (interactive) (slack-if-let* ((buf slack-current-buffer)) (slack-buffer-display-user-profile buf))) (defun slack-select-unread-rooms () (interactive) (let* ((team (slack-team-select)) (room (slack-room-select (cl-loop for team in (list team) append (with-slots (groups ims channels) team (cl-remove-if #'(lambda (room) (not (< 0 (oref room unread-count-display)))) (append ims groups channels))))))) (slack-room-display room team))) (defmethod slack-user-find ((room slack-room) team) (slack-user--find (oref room user) team)) (defun slack-room-find-file-comment-message (room comment-id) (let ((messages (oref room messages))) (cl-find-if #'(lambda (m) (and (object-of-class-p m 'slack-file-message) (or (and (slack-file-share-message-p m) (oref (oref m file) initial-comment) (string= comment-id (oref (oref (oref m file) initial-comment) id))) (and (slot-exists-p m 'comment) (slot-boundp m 'comment) (string= comment-id (oref (oref m comment) id)))))) messages))) (defun slack-room-find-file-share-message (room file-id) (let ((messages (oref room messages))) (cl-find-if #'(lambda (m) (and (slack-file-share-message-p m) (slot-exists-p m 'file) (slot-boundp m 'file) (string= file-id (oref (oref m file) id)))) messages))) (defun slack-room-display (room team) (cl-labels ((open (buf) (slack-buffer-display buf))) (let* ((buf (slack-buffer-find (or (and (eq (eieio-object-class-name room) 'slack-file-room) 'slack-file-list-buffer) 'slack-message-buffer) room team))) (if buf (open buf) (message "No Message in %s, fetching from server..." (slack-room-name room)) (slack-room-history-request room team :after-success #'(lambda (&rest _ignore) (open (slack-create-message-buffer room team)))))))) (defmethod slack-room-update-buffer ((this slack-room) team message replace) (slack-if-let* ((buffer (slack-buffer-find 'slack-message-buffer this team))) (slack-buffer-update buffer message :replace replace) (and slack-buffer-create-on-notify (slack-room-history-request this team :after-success #'(lambda (&rest _ignore) (tracking-add-buffer (slack-buffer-buffer (slack-create-message-buffer this team)))))))) (cl-defmethod slack-room-history-request ((room slack-room) team &key oldest latest count after-success async) (cl-labels ((on-request-update (&key data &allow-other-keys) (slack-request-handle-error (data "slack-room-request-update") (let ((messages (cl-loop for message in (plist-get data :messages) collect (slack-message-create message team :room room))) (has-more (not (eq :json-false (plist-get data :has_more))))) (if oldest (slack-room-set-prev-messages room messages) (if latest (slack-room-append-messages room messages) (slack-room-set-messages room messages))) (if (and after-success (functionp after-success)) (funcall after-success has-more)))))) (slack-request (slack-request-create (slack-room-history-url room) team :params (list (cons "channel" (oref room id)) (if oldest (cons "latest" oldest)) (if latest (cons "oldest" latest)) (cons "count" (number-to-string (or count 100)))) :success #'on-request-update)))) (defmethod slack-room-member-p ((this slack-room)) t) (provide 'slack-room) ;;; slack-room.el ends here