diff options
| author | Craig Jennings <c@cjennings.net> | 2026-03-06 04:09:44 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-03-06 04:09:44 -0600 |
| commit | fc271461ac3b16ec161750335b5cb0bc81660639 (patch) | |
| tree | a241368affef0db9a5a8acba06dfbb22e31fd3d1 /modules | |
| parent | 60872075b083a615046782ea413d34ad9f301311 (diff) | |
feat: add Slack client module with dashboard integration
Add emacs-slack config with auth-source credentials, DM/mention-only
notifications via notify script, compose buffer workflow, and dashboard
icon. Keybindings under C-; S prefix.
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/dashboard-config.el | 6 | ||||
| -rw-r--r-- | modules/slack-config.el | 143 |
2 files changed, 149 insertions, 0 deletions
diff --git a/modules/dashboard-config.el b/modules/dashboard-config.el index 3dcc6b6b..833fb7b5 100644 --- a/modules/dashboard-config.el +++ b/modules/dashboard-config.el @@ -160,6 +160,11 @@ Adjust this if the title doesn't appear centered under the banner image.") (lambda (&rest _) (cj/erc-switch-to-buffer-with-completion)) nil " " "") + (,(nerd-icons-faicon "nf-fa-slack") + "Slack" "Slack Client" + (lambda (&rest _) (cj/slack-start)) + nil " " "") + (,(nerd-icons-mdicon "nf-md-school") "Flashcards" "Org-Drill" (lambda (&rest _) (cj/drill-start)) @@ -193,6 +198,7 @@ Adjust this if the title doesn't appear centered under the banner image.") (define-key dashboard-mode-map (kbd "f") (lambda () (interactive) (cj/drill-start))) (define-key dashboard-mode-map (kbd "r") (lambda () (interactive) (cj/elfeed-open))) (define-key dashboard-mode-map (kbd "i") (lambda () (interactive) (cj/erc-switch-to-buffer-with-completion))) + (define-key dashboard-mode-map (kbd "s") (lambda () (interactive) (cj/slack-start))) (define-key dashboard-mode-map (kbd "t") (lambda () (interactive) (vterm))) (define-key dashboard-mode-map (kbd "d") (lambda () (interactive) (dirvish user-home-dir)))) diff --git a/modules/slack-config.el b/modules/slack-config.el new file mode 100644 index 00000000..5ff33917 --- /dev/null +++ b/modules/slack-config.el @@ -0,0 +1,143 @@ +;;; 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-room-im-p room) + (slack-message-mentioned-p message))) + (let ((title (format "Slack: %s" (slack-room-display-name room team))) + (body (slack-message-to-string message team))) + (start-process "slack-notify" nil + "notify" "info" title body)))) + +;; ------------------------------ 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") #'slack-buffer-mark-as-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. |
