227 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
| ;;; private/grfn/slack-snippets.el -*- lexical-binding: t; -*-
 | |
| 
 | |
| (require 'dash)
 | |
| (require 'dash-functional)
 | |
| (require 'request)
 | |
| 
 | |
| ;;;
 | |
| ;;; Configuration
 | |
| ;;;
 | |
| 
 | |
| (defvar slack/token nil
 | |
|   "Legacy (https://api.slack.com/custom-integrations/legacy-tokens) access token")
 | |
| 
 | |
| (defvar slack/include-public-channels 't
 | |
|   "Whether or not to inclue public channels in the list of conversations")
 | |
| 
 | |
| (defvar slack/include-private-channels 't
 | |
|   "Whether or not to inclue public channels in the list of conversations")
 | |
| 
 | |
| (defvar slack/include-im 't
 | |
|   "Whether or not to inclue IMs (private messages) in the list of conversations")
 | |
| 
 | |
| (defvar slack/include-mpim nil
 | |
|   "Whether or not to inclue multi-person IMs (multi-person private messages) in
 | |
|   the list of conversations")
 | |
| 
 | |
| ;;;
 | |
| ;;; Utilities
 | |
| ;;;
 | |
| 
 | |
| (defmacro comment (&rest _body)
 | |
|   "Comment out one or more s-expressions"
 | |
|   nil)
 | |
| 
 | |
| (defun ->list (vec) (append vec nil))
 | |
| 
 | |
| (defun json-truthy? (x) (and x (not (equal :json-false x))))
 | |
| 
 | |
| ;;;
 | |
| ;;; Generic API integration
 | |
| ;;;
 | |
| 
 | |
| (defvar slack/base-url "https://slack.com/api")
 | |
| 
 | |
| (defun slack/get (path params &optional callback)
 | |
|   "params is an alist of query parameters"
 | |
|   (let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback)))
 | |
|          (params (car params-callback)) (callback (cdr params-callback))
 | |
|          (params (append `(("token" . ,slack/token)) params))
 | |
|          (url (concat (file-name-as-directory slack/base-url) path)))
 | |
|     (request url
 | |
|              :type "GET"
 | |
|              :params params
 | |
|              :parser 'json-read
 | |
|              :success (cl-function
 | |
|                        (lambda (&key data &allow-other-keys)
 | |
|                          (funcall callback data))))))
 | |
| 
 | |
| (defun slack/post (path params &optional callback)
 | |
|   (let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback)))
 | |
|          (params (car params-callback)) (callback (cdr params-callback))
 | |
|          (url (concat (file-name-as-directory slack/base-url) path)))
 | |
|     (request url
 | |
|              :type "POST"
 | |
|              :data (json-encode params)
 | |
|              :headers `(("Content-Type"  . "application/json")
 | |
|                         ("Authorization" . ,(format "Bearer %s" slack/token)))
 | |
|              :success (cl-function
 | |
|                        (lambda (&key data &allow-other-keys)
 | |
|                          (funcall callback data))))))
 | |
| 
 | |
| 
 | |
| ;;;
 | |
| ;;; Specific API endpoints
 | |
| ;;;
 | |
| 
 | |
| ;; Users
 | |
| 
 | |
| (defun slack/users (cb)
 | |
|   "Returns users as (id . name) pairs"
 | |
|   (slack/get
 | |
|    "users.list"
 | |
|    (lambda (data)
 | |
|      (->> data
 | |
|           (assoc-default 'members)
 | |
|           ->list
 | |
|           (-map (lambda (user)
 | |
|                   (cons (assoc-default 'id user)
 | |
|                         (assoc-default 'real_name user))))
 | |
|           (-filter #'cdr)
 | |
|           (funcall cb)))))
 | |
| 
 | |
| (comment
 | |
|  (slack/get
 | |
|   "users.list"
 | |
|   (lambda (data) (setq response-data data)))
 | |
| 
 | |
|  (slack/users (lambda (data) (setq --users data)))
 | |
| 
 | |
|  )
 | |
| 
 | |
| ;; Conversations
 | |
| 
 | |
| (defun slack/conversation-types ()
 | |
|   (->>
 | |
|    (list (when slack/include-public-channels  "public_channel")
 | |
|          (when slack/include-private-channels "private_channel")
 | |
|          (when slack/include-im               "im")
 | |
|          (when slack/include-mpim             "mpim"))
 | |
|    (-filter #'identity)
 | |
|    (s-join ",")))
 | |
| 
 | |
| (defun channel-label (chan users-alist)
 | |
|   (cond
 | |
|    ((json-truthy? (assoc-default 'is_channel chan))
 | |
|     (format "#%s" (assoc-default 'name chan)))
 | |
|    ((json-truthy? (assoc-default 'is_im chan))
 | |
|     (let ((user-id (assoc-default 'user chan)))
 | |
|       (format "Private message with %s" (assoc-default user-id users-alist))))
 | |
|    ((json-truthy? (assoc-default 'is_mpim chan))
 | |
|     (->> chan
 | |
|          (assoc-default 'purpose)
 | |
|          (assoc-default 'value)))))
 | |
| 
 | |
| (defun slack/conversations (cb)
 | |
|   "Calls `cb' with (id . '((label . \"label\") '(topic . \"topic\") '(purpose . \"purpose\"))) pairs"
 | |
|   (slack/get
 | |
|    "conversations.list"
 | |
|    `(("types"            . ,(slack/conversation-types))
 | |
|      ("exclude-archived" . "true"))
 | |
|    (lambda (data)
 | |
|      (setq --data data)
 | |
|      (slack/users
 | |
|       (lambda (users)
 | |
|         (->> data
 | |
|              (assoc-default 'channels)
 | |
|              ->list
 | |
|              (-map
 | |
|               (lambda (chan)
 | |
|                 (cons (assoc-default 'id chan)
 | |
|                       `((label   . ,(channel-label chan users))
 | |
|                         (topic   . ,(->> chan
 | |
|                                          (assoc-default 'topic)
 | |
|                                          (assoc-default 'value)))
 | |
|                         (purpose . ,(->> chan
 | |
|                                          (assoc-default 'purpose)
 | |
|                                          (assoc-default 'value)))))))
 | |
|              (funcall cb)))))))
 | |
| 
 | |
| (comment
 | |
|  (slack/get
 | |
|   "conversations.list"
 | |
|   '(("types" . "public_channel,private_channel,im,mpim"))
 | |
|   (lambda (data) (setq response-data data)))
 | |
| 
 | |
|  (slack/get
 | |
|   "conversations.list"
 | |
|   '(("types" . "im"))
 | |
|   (lambda (data) (setq response-data data)))
 | |
| 
 | |
|  (slack/conversations
 | |
|   (lambda (convos) (setq --conversations convos)))
 | |
| 
 | |
|  )
 | |
| 
 | |
| ;; Messages
 | |
| 
 | |
| (cl-defun slack/post-message
 | |
|     (&key text channel-id (on-success #'identity))
 | |
|   (slack/post "chat.postMessage"
 | |
|               `((text    . ,text)
 | |
|                 (channel . ,channel-id)
 | |
|                 (as_user . t))
 | |
|               on-success))
 | |
| 
 | |
| (comment
 | |
| 
 | |
|  (slack/post-message
 | |
|   :text "hi slackbot"
 | |
|   :channel-id slackbot-channel-id
 | |
|   :on-success (lambda (data) (setq resp data)))
 | |
| 
 | |
|  )
 | |
| 
 | |
| ;;;
 | |
| ;;; Posting code snippets to slack
 | |
| ;;;
 | |
| 
 | |
| (defun prompt-for-channel (cb)
 | |
|   (slack/conversations
 | |
|    (lambda (conversations)
 | |
|      (ivy-read
 | |
|       "Select channel: "
 | |
|       ;; TODO want to potentially use purpose / topic stuff here
 | |
|       (->> conversations
 | |
|            (-filter (lambda (c) (assoc-default 'label (cdr c))))
 | |
|            (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
 | |
|                                  (id (car chan)))
 | |
|                              (propertize label 'channel-id id)))))
 | |
|       :history 'slack/channel-history
 | |
|       :action (lambda (selected)
 | |
|                 (let ((channel-id (get-text-property 0 'channel-id selected)))
 | |
|                   (funcall cb channel-id)
 | |
|                   (message "Sent message to %s" selected))))))
 | |
|   nil)
 | |
| 
 | |
| (comment
 | |
|  (prompt-for-channel #'message)
 | |
|  (->> --convos
 | |
|       (-filter (lambda (c) (assoc-default 'label (cdr c))))
 | |
|       (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
 | |
|                        (id (car chan)))
 | |
|                    (propertize label 'channel-id id)))))
 | |
| 
 | |
|  (->> --convos (car) (cdr) (assoc-default 'label))
 | |
|  )
 | |
| 
 | |
| (defun slack-send-code-snippet (&optional snippet-text)
 | |
|   (interactive
 | |
|    (list (buffer-substring-no-properties (mark) (point))))
 | |
|   (prompt-for-channel
 | |
|    (lambda (channel-id)
 | |
|      (slack/post-message
 | |
|       :text       (format "```\n%s```" snippet-text)
 | |
|       :channel-id channel-id))))
 | |
| 
 | |
| (provide 'slack-snippets)
 |