Adding WS and managing the state better

This commit is contained in:
Miguel de la Cruz 2022-05-31 23:15:24 +02:00
parent 39e77a80e9
commit 23c1d56799

View file

@ -9,13 +9,20 @@
;;; Code: ;;; Code:
(require 'websocket)
(defgroup mattermost nil (defgroup mattermost nil
"Mattermost chat client" "Mattermost chat client"
:prefix "mattermost-" :prefix "mattermost-"
:group 'applications) :group 'applications)
(defcustom mattermost-server-url "" (defcustom mattermost-login-id ""
"The URL of the Mattermost server" "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 :group 'mattermost
:type 'string) :type 'string)
@ -30,6 +37,9 @@
(defvar mattermost-user-id nil (defvar mattermost-user-id nil
"The ID of the authenticated user") "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 ;; ToDo: probably not the best way to get a keyword from a string
(defun mattermost-string->keyword (str) (defun mattermost-string->keyword (str)
"Returns a keyword from a string" "Returns a keyword from a string"
@ -40,9 +50,20 @@
(let ((json-object-type 'plist) (let ((json-object-type 'plist)
(json-array-type 'list) (json-array-type 'list)
(json-key-type 'keyword)) (json-key-type 'keyword))
(with-current-buffer (get-buffer-create "asdf")
(insert (format "%s" (buffer-string))))
(goto-char url-http-end-of-headers) (goto-char url-http-end-of-headers)
(json-read))) (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) (defun mattermost-print-post (post)
"Prints the post in the current buffer" "Prints the post in the current buffer"
(let* ((msg (plist-get post :message)) (let* ((msg (plist-get post :message))
@ -51,7 +72,7 @@
(user-id (plist-get post :user_id)) (user-id (plist-get post :user_id))
(user (mattermost--get-user user-id)) (user (mattermost--get-user user-id))
(username (plist-get user :username))) (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) (defun mattermost--get-user (user-id)
"Returns the user from the internal cache and fills the cache "Returns the user from the internal cache and fills the cache
@ -64,6 +85,25 @@ if the user is not present"
user) user)
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 ;; 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 ;; the user (set the id to a local var) and the headers (set the
;; token) ;; token)
@ -79,6 +119,7 @@ if the user is not present"
(response (mattermost-parse-json))) (response (mattermost-parse-json)))
(setq mattermost-token token) (setq mattermost-token token)
(setq mattermost-user-id (plist-get response :id)) (setq mattermost-user-id (plist-get response :id))
(mattermost--connect-websocket)
token)))) token))))
;; ToDo: update to parse headers as well ;; ToDo: update to parse headers as well
@ -103,7 +144,6 @@ if the user is not present"
(defun mattermost-get-channels (team-id) (defun mattermost-get-channels (team-id)
"Returns the user's channel for a given team" "Returns the user's channel for a given team"
(let ((url (format "/users/%s/teams/%s/channels" mattermost-user-id team-id))) (let ((url (format "/users/%s/teams/%s/channels" mattermost-user-id team-id)))
(message "Channels url: %s" url)
(mattermost-request "GET" url))) (mattermost-request "GET" url)))
(defun mattermost-get-channel-messages (channel-id &optional page per-page) (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))) (let ((url (format "/channels/%s/posts" channel-id)))
(mattermost-request "GET" url))) (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 ;; ToDo: define this better
(defvar mattermost-mode-map (defvar mattermost-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
@ -133,27 +158,50 @@ if the user is not present"
map) map)
"The keymap for mattermost-mode") "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 "Mode to list the Mattermost teams and channels to allow the
user to check their status and select between them") user to check their status and select between them")
(defun mattermost () (defun mattermost ()
"Connect to a Mattermost instance" "Connect to a Mattermost instance"
(interactive) (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 (defun mattermost-close ()
(setq msgs (mattermost-get-channel-messages "68w17u1da7yg7enayudjjqqwse")) "Closes the connection with the Mattermost server"
(car (plist-get msgs :posts)) (unless (null mattermost-websocket)
(websocket-close mattermost-websocket))
(with-current-buffer (get-buffer-create "*Town Square*") (setq mattermost-token nil
(erase-buffer) mattermost-user-id nil))
(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)))))
(provide 'mattermost) (provide 'mattermost)
;;; mattermost.el ends here ;;; mattermost.el ends here