summaryrefslogtreecommitdiff
path: root/modules/slack-config.el
blob: 067c94ff2caf776129072cacd5ec73ca28042e40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
;;; slack-config.el --- Slack Configuration -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>

;;; Commentary:

;; Slack client using emacs-slack (https://github.com/emacs-slack/emacs-slack).
;;
;; Authentication:
;;   Credentials are stored in authinfo.gpg (never plaintext).
;;   Add an entry like:
;;     machine deepsatworkspace.slack.com login TOKEN password xoxc-YOUR-TOKEN
;;     machine deepsatworkspace.slack.com login COOKIE password xoxd-YOUR-COOKIE
;;
;;   To get these values, run M-x slack-refresh-token and follow the
;;   instructions to extract them from your browser's developer tools.
;;
;; Keybindings (C-; S prefix):
;;   C-; S s  — connect to Slack
;;   C-; S c  — select unread rooms
;;   C-; S C  — select any channel/room
;;   C-; S d  — direct message a person
;;   C-; S w  — compose message in separate buffer
;;   C-; S r  — reply / show thread
;;   C-; S e  — insert emoji
;;   C-; S !  — add reaction to message
;;   C-; S @  — embed @mention
;;   C-; S #  — embed #channel
;;   C-; S q  — mark channel read and bury buffer
;;   C-; S S  — disconnect
;;
;; Compose buffer:
;;   C-<return> — send message

;;; Code:

(require 'auth-source)

(defvar cj/slack-workspace "deepsatworkspace.slack.com"
  "Slack workspace domain for auth-source lookup.")

(defun cj/slack--get-credential (login-key)
  "Look up LOGIN-KEY credential for the Slack workspace from auth-source."
  (let ((entry (car (auth-source-search :host cj/slack-workspace
                                        :user login-key
                                        :max 1))))
    (when entry
      (let ((secret (plist-get entry :secret)))
        (if (functionp secret)
            (funcall secret)
          secret)))))

(defun cj/slack-start ()
  "Connect to Slack, registering the team if needed."
  (interactive)
  (require 'slack)
  (let ((token (cj/slack--get-credential "TOKEN"))
        (cookie (cj/slack--get-credential "COOKIE")))
    (unless token
      (user-error "No Slack token found in authinfo for %s (login: TOKEN)" cj/slack-workspace))
    (unless cookie
      (user-error "No Slack cookie found in authinfo for %s (login: COOKIE)" cj/slack-workspace))
    (unless (and (boundp 'slack-teams) slack-teams)
      (slack-register-team
       :name "DeepSat"
       :token token
       :cookie cookie
       :full-and-display-names t
       :default t))
    (slack-start)))

(defun cj/slack-stop ()
  "Disconnect from Slack."
  (interactive)
  (require 'slack)
  (slack-ws-close)
  (message "Slack disconnected."))

(use-package slack
  :defer t
  :commands (slack-start slack-select-rooms slack-select-unread-rooms
             slack-im-select slack-thread-show-or-create
             slack-insert-emoji slack-register-team)
  :custom
  (slack-buffer-emojify t)
  (slack-prefer-current-team t)
  :config
  (setq slack-message-custom-notifier #'cj/slack-notify))

;; ----------------------------- Notifications ---------------------------------

(defun cj/slack-notify (message room team)
  "Send desktop notification for DMs and @mentions only.
MESSAGE is the incoming slack message, ROOM is the channel/DM,
TEAM is the slack team object."
  (when (and (not (slack-message-minep message team))
             (or (slack-im-p room)
                 (slack-message-mentioned-p message)))
    (let ((title (format "Slack: %s" (slack-room-display-name room team)))
          (body (slack-message-body message team)))
      (start-process "slack-notify" nil
                     "notify" "info" title body))))

(defun cj/slack-mark-read-and-bury ()
  "Mark the current Slack channel as read and bury the buffer."
  (interactive)
  (when (and (boundp 'slack-current-buffer) slack-current-buffer)
    (let ((ts (slack-buffer-latest-ts slack-current-buffer)))
      (when ts
        (slack-buffer-update-mark-request slack-current-buffer ts))))
  (bury-buffer))

;; ------------------------------ Keybindings ----------------------------------

(defvar cj/slack-keymap (make-sparse-keymap)
  "Keymap for Slack commands under C-; S.")

(global-set-key (kbd "C-; S") cj/slack-keymap)

(define-key cj/slack-keymap (kbd "s") #'cj/slack-start)
(define-key cj/slack-keymap (kbd "c") #'slack-select-unread-rooms)
(define-key cj/slack-keymap (kbd "C") #'slack-select-rooms)
(define-key cj/slack-keymap (kbd "d") #'slack-im-select)
(define-key cj/slack-keymap (kbd "w") #'slack-message-write-another-buffer)
(define-key cj/slack-keymap (kbd "r") #'slack-thread-show-or-create)
(define-key cj/slack-keymap (kbd "e") #'slack-insert-emoji)
(define-key cj/slack-keymap (kbd "!") #'slack-message-add-reaction)
(define-key cj/slack-keymap (kbd "@") #'slack-message-embed-mention)
(define-key cj/slack-keymap (kbd "#") #'slack-message-embed-channel)
(define-key cj/slack-keymap (kbd "q") #'cj/slack-mark-read-and-bury)
(define-key cj/slack-keymap (kbd "S") #'cj/slack-stop)

(which-key-add-keymap-based-replacements cj/slack-keymap
  "" "slack menu"
  "s" "start slack"
  "c" "unread rooms"
  "C" "select channel"
  "d" "direct message"
  "w" "compose message"
  "r" "reply / thread"
  "e" "insert emoji"
  "!" "add reaction"
  "@" "embed @mention"
  "#" "embed #channel"
  "q" "mark read & bury"
  "S" "disconnect")

;; Send from compose buffer with C-<return>
(with-eval-after-load 'slack-message-compose-buffer
  (define-key slack-message-compose-buffer-mode-map (kbd "C-<return>") #'slack-message-send-from-buffer))

(provide 'slack-config)
;;; slack-config.el ends here.