Moving all of my Emacs-related files into their own directory at the root of this repository.
		
			
				
	
	
		
			228 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
;;; private/grfn/slack-snippets.el -*- lexical-binding: t; -*-
 | 
						|
 | 
						|
(require 's)
 | 
						|
(require 'json)
 | 
						|
(require 'dash)
 | 
						|
(require 'dash-functional)
 | 
						|
(require 'request)
 | 
						|
(require 'subr-x)
 | 
						|
 | 
						|
;;;
 | 
						|
;;; 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
 | 
						|
             (-filter
 | 
						|
              (lambda (chan) (channel-label chan users)))
 | 
						|
             (-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)))
 | 
						|
 | 
						|
 (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
 | 
						|
                            (id (car chan)))
 | 
						|
                        (propertize label 'channel-id id)))
 | 
						|
            --conversations)
 | 
						|
 | 
						|
 )
 | 
						|
 | 
						|
;;;
 | 
						|
;;; Posting code snippets to slack
 | 
						|
;;;
 | 
						|
 | 
						|
(defun prompt-for-channel (cb)
 | 
						|
  (slack/conversations
 | 
						|
   (lambda (conversations)
 | 
						|
     (setq testing (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
 | 
						|
                            (id (car chan)))
 | 
						|
                        (propertize label 'channel-id id)))
 | 
						|
            conversations))
 | 
						|
     (ivy-read
 | 
						|
      "Select channel: "
 | 
						|
      ;; TODO want to potentially use purpose / topic stuff here
 | 
						|
      (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
 | 
						|
                            (id (car chan)))
 | 
						|
                        (propertize label 'channel-id id)))
 | 
						|
            conversations)
 | 
						|
      :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)
 | 
						|
 | 
						|
(defun slack-send-code-snippet (&optional snippet-text)
 | 
						|
  (interactive)
 | 
						|
  (when-let ((snippet-text (or snippet-text
 | 
						|
                               (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)))))
 |