diff --git a/mattermost.el b/mattermost.el index b8895c5..ead6f05 100644 --- a/mattermost.el +++ b/mattermost.el @@ -9,13 +9,20 @@ ;;; Code: +(require 'websocket) + (defgroup mattermost nil "Mattermost chat client" :prefix "mattermost-" :group 'applications) -(defcustom mattermost-server-url "" - "The URL of the Mattermost server" +(defcustom mattermost-login-id "" + "The username or email to log in the Mattermost server" + :group 'mattermost + :type 'string) + +(defcustom mattermost-host "" + "The hostname of the Mattermost server" :group 'mattermost :type 'string) @@ -30,6 +37,9 @@ (defvar mattermost-user-id nil "The ID of the authenticated user") +(defvar mattermost-websocket nil + "The websocket connected to the server") + ;; ToDo: probably not the best way to get a keyword from a string (defun mattermost-string->keyword (str) "Returns a keyword from a string" @@ -40,9 +50,20 @@ (let ((json-object-type 'plist) (json-array-type 'list) (json-key-type 'keyword)) + (with-current-buffer (get-buffer-create "asdf") + (insert (format "%s" (buffer-string)))) (goto-char url-http-end-of-headers) (json-read))) +(defun mattermost-read-password () + "Reads the password from the auth-source" + (let ((res (auth-source-search :max 1 :host mattermost-host :user mattermost-login-id))) + (if (and (listp res) (= (length res) 1)) + (let ((secret (plist-get (car res) :secret))) + (if (functionp secret) + (funcall secret) + secret))))) + (defun mattermost-print-post (post) "Prints the post in the current buffer" (let* ((msg (plist-get post :message)) @@ -51,7 +72,7 @@ (user-id (plist-get post :user_id)) (user (mattermost--get-user user-id)) (username (plist-get user :username))) - (insert (format "[%s] %s\n" username msg)))) + (insert (format "[%s] %s\n" username (encode-coding-string msg 'utf-8))))) (defun mattermost--get-user (user-id) "Returns the user from the internal cache and fills the cache @@ -64,6 +85,25 @@ if the user is not present" user) user))) +(defun mattermost--get-auth-challenge () + "Returns the JSON representation of the websocket authentication challenge" + (json-encode `(:seq 1 + :action "authentication_challenge" + :data (:token ,mattermost-token)))) + +(defun mattermost--connect-websocket () + "Returns a websocket configured to connect to the Mattermost + host and to dispatch and process the incoming messages" + (websocket-open (format "wss://%s/api/v4/websocket" mattermost-host) + :on-open (lambda (ws) + (websocket-send-text ws (mattermost--get-auth-challenge))) + :on-message (lambda (ws frame) + (let* ((text (websocket-frame-text frame)) + (msg (json-parse-string text))) + (message "INCOMING> %s" msg))) + :on-close (lambda (ws) + (lwarn 'mattermost :error "websocket connection closed")))) + ;; ToDo: once mattermost-request parses headers, use it to fetch both ;; the user (set the id to a local var) and the headers (set the ;; token) @@ -79,6 +119,7 @@ if the user is not present" (response (mattermost-parse-json))) (setq mattermost-token token) (setq mattermost-user-id (plist-get response :id)) + (mattermost--connect-websocket) token)))) ;; ToDo: update to parse headers as well @@ -103,7 +144,6 @@ if the user is not present" (defun mattermost-get-channels (team-id) "Returns the user's channel for a given team" (let ((url (format "/users/%s/teams/%s/channels" mattermost-user-id team-id))) - (message "Channels url: %s" url) (mattermost-request "GET" url))) (defun mattermost-get-channel-messages (channel-id &optional page per-page) @@ -111,21 +151,6 @@ if the user is not present" (let ((url (format "/channels/%s/posts" channel-id))) (mattermost-request "GET" url))) -(with-current-buffer (get-buffer-create "*Mattermost Teams*") - (let ((teams (mattermost-get-teams))) - (erase-buffer) - (dolist (team teams) - (let* ((team-id (plist-get team :id)) - (team-display-name (plist-get team :display_name)) - (channels (mattermost-get-channels team-id))) - (insert (format "%s\n" team-display-name)) - (dolist (channel channels) - (let ((channel-id (plist-get channel :id)) - (channel-display-name (plist-get channel :display_name)) - (channel-name (plist-get channel :name))) - (insert (format "> [%s] %s\n" channel-id (if (string= channel-display-name "") - channel-name - channel-display-name))))))))) ;; ToDo: define this better (defvar mattermost-mode-map (let ((map (make-sparse-keymap))) @@ -133,27 +158,50 @@ if the user is not present" map) "The keymap for mattermost-mode") -(define-derived-mode mattermost-mode fundamental-mode "Mattermost" +(defun show-root () + (interactive) + (let ((rootb (get-buffer-create "*Mattermost Root*"))) + (with-current-buffer rootb + (let ((teams (mattermost-get-teams))) + (erase-buffer) + (dolist (team teams) + (let* ((team-id (plist-get team :id)) + (team-display-name (plist-get team :display_name)) + (channels (mattermost-get-channels team-id))) + (insert (format "%s\n" team-display-name)) + (dolist (channel channels) + (let ((channel-id (plist-get channel :id)) + (channel-display-name (plist-get channel :display_name)) + (channel-name (plist-get channel :name))) + (insert (format "> [%s] %s\n" channel-id (if (string= channel-display-name "") + channel-name + channel-display-name))))))))) + (set-buffer rootb))) + +(define-derived-mode mattermost-root-mode fundamental-mode "Mattermost Root" "Mode to list the Mattermost teams and channels to allow the user to check their status and select between them") (defun mattermost () "Connect to a Mattermost instance" (interactive) - ) + (if (string-empty-p mattermost-login-id) + ;; ToDo: throw error here + (lwarn 'mattermost :error "`mattermost-login-id` cannot be empty")) + (if (string-empty-p mattermost-host) + ;; ToDo: throw error here + (lwarn 'mattermost :error "`mattermost-host` cannot be empty")) + (mattermost-close) + (let ((password (mattermost-read-password))) + (mattermost-login mattermost-login-id password)) + (show-root)) -;; ToDo: remove -(setq msgs (mattermost-get-channel-messages "68w17u1da7yg7enayudjjqqwse")) -(car (plist-get msgs :posts)) - -(with-current-buffer (get-buffer-create "*Town Square*") - (erase-buffer) - (let ((posts (plist-get msgs :posts)) - (order (plist-get msgs :order))) - (dolist (msgid order) - (let* ((msgid-keyword (mattermost-string->keyword msgid)) - (post (plist-get posts msgid-keyword))) - (mattermost-print-post post))))) +(defun mattermost-close () + "Closes the connection with the Mattermost server" + (unless (null mattermost-websocket) + (websocket-close mattermost-websocket)) + (setq mattermost-token nil + mattermost-user-id nil)) (provide 'mattermost) ;;; mattermost.el ends here