about summary refs log tree commit diff
path: root/configs/shared/emacs/.emacs.d/vendor/slack-snippets.el
diff options
context:
space:
mode:
Diffstat (limited to 'configs/shared/emacs/.emacs.d/vendor/slack-snippets.el')
-rw-r--r--configs/shared/emacs/.emacs.d/vendor/slack-snippets.el228
1 files changed, 228 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/vendor/slack-snippets.el b/configs/shared/emacs/.emacs.d/vendor/slack-snippets.el
new file mode 100644
index 000000000000..6bf933cfb86d
--- /dev/null
+++ b/configs/shared/emacs/.emacs.d/vendor/slack-snippets.el
@@ -0,0 +1,228 @@
+;;; 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)))))