app/irc: general rewrite (#103)
+ Refactor initialization process. + Refactor for consistency. + Add +irc-defer-notifications (for ZNC users). + Rewrote =irc (opens separate workspace + auto-connects to registered networks). + Add +irc/connect (connect to specific network). + Add +irc/quit (kill whole circe session). + Add +irc/ivy-jump-to-channel command. + Rewrite README. + Silence QUIT/PART default messages; they're cute, but no thanks. + Truncate nicks non-destructively. + Jump to prompt when entering insert mode (with evil). + Activate solaire-mde in channel buffers to visually distinguish them from server buffers.
This commit is contained in:
parent
07299c5020
commit
b3dafe96d3
3 changed files with 201 additions and 90 deletions
|
@ -1,19 +1,74 @@
|
|||
* :app irc
|
||||
|
||||
This module makes Emacs an irc client, using [[https://github.com/jorgenschaefer/circe][~circe~]].
|
||||
This module turns adds an IRC client to Emacs ([[https://github.com/jorgenschaefer/circe][~circe~)]] with native notifications ([[https://github.com/eqyiel/circe-notifications][circe-notifications]]).
|
||||
|
||||
** Dependencies
|
||||
This module has no dependencies, besides =gnutls-cli= or =openssl= for secure connections.
|
||||
|
||||
I use ~pass~ to not have the passwords written in my dotfiles. It's available under ~:tools~ modules.
|
||||
If you want to use TLS you also need =openssl= or =gnutls-cli=.
|
||||
** Configure
|
||||
Use the ~:irc~ setting to configure IRC servers. Its second argument (a plist) takes the same arguments as ~circe-network-options~.
|
||||
|
||||
Configure Emacs to use your favorite irc servers:
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(set! :irc "chat.freenode.net"
|
||||
`(:tls t
|
||||
:nick "benny"
|
||||
:sasl-username ,(password-store-get "irc/freenode")
|
||||
:sasl-password ,(password-store-get "irc/freenode")
|
||||
:nick "doom"
|
||||
:sasl-username "myusername"
|
||||
:sasl-password "mypassword"
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
*It is a obviously a bad idea to store auth-details in plaintext,* so here are some ways to avoid that:
|
||||
|
||||
*** Pass: the unix password manager
|
||||
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the [[/modules/tools/password-store/README.org][:tools password-store]] module you get an elisp API through which to access your password store.
|
||||
|
||||
~:irc~'s plist can use functions instead of strings. ~+pass-get-user~ and ~+pass-get-secret~ can help here:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(set! :irc "chat.freenode.net"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password ,(+pass-get-secret "irc/freenode.net")
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
But wait, there's more! This stores your password in a public variable which could be accessed or appear in backtraces. Not good! So we go a step further:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(set! :irc "chat.freenode.net"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
And you're good to go!
|
||||
|
||||
*** Emacs' auth-source API
|
||||
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store (and retrieve) encrypted passwords with it.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(setq auth-sources '("~/.authinfo.gpg"))
|
||||
|
||||
(defun my-fetch-password (&rest params)
|
||||
(require 'auth-source)
|
||||
(let ((match (car (apply #'auth-source-search params))))
|
||||
(if match
|
||||
(let ((secret (plist-get match :secret)))
|
||||
(if (functionp secret)
|
||||
(funcall secret)
|
||||
secret))
|
||||
(error "Password not found for %S" params))))
|
||||
|
||||
(defun my-nickserv-password (server)
|
||||
(my-fetch-password :user "forcer" :host "irc.freenode.net"))
|
||||
|
||||
(set! :irc "chat.freenode.net"
|
||||
'(:tls t
|
||||
:nick "doom"
|
||||
:sasl-password my-nickserver-password
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
|
|
|
@ -1,17 +1,65 @@
|
|||
;;; app/irc/autoload/email.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defun =irc ()
|
||||
"Connect to IRC."
|
||||
(interactive)
|
||||
(call-interactively #'circe))
|
||||
(defvar +irc--workspace-name "*IRC*")
|
||||
|
||||
(defun +irc-setup-wconf (&optional inhibit-workspace)
|
||||
(unless inhibit-workspace
|
||||
(+workspace-switch +irc--workspace-name t))
|
||||
(let ((buffers (doom-buffers-in-mode 'circe-mode nil t)))
|
||||
(if buffers
|
||||
(ignore (switch-to-buffer (car buffers)))
|
||||
(require 'circe)
|
||||
(delete-other-windows)
|
||||
(switch-to-buffer (doom-fallback-buffer))
|
||||
t)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/connect-all ()
|
||||
"Connect to all `:irc' defined servers."
|
||||
(interactive)
|
||||
;; force a library load for +irc--accounts
|
||||
(circe--version)
|
||||
(cl-loop for network in +irc--accounts
|
||||
collect (circe (car network))))
|
||||
(defun =irc (&optional inhibit-workspace)
|
||||
"Connect to IRC and auto-connect to all registered networks."
|
||||
(interactive "P")
|
||||
(and (+irc-setup-wconf inhibit-workspace)
|
||||
(cl-loop for network in +irc-connections
|
||||
collect (circe (car network)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/connect ()
|
||||
"Connect to a specific registered server."
|
||||
(interactive)
|
||||
(and (+irc-setup-wconf inhibit-workspace)
|
||||
(call-interactively #'circe)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/quit ()
|
||||
"Kill current circe session and workgroup."
|
||||
(interactive)
|
||||
(if (y-or-n-p "Really kill IRC session?")
|
||||
(let (circe-channel-killed-confirmation
|
||||
circe-server-killed-confirmation)
|
||||
(mapcar #'kill-buffer (doom-buffers-in-mode 'circe-mode (buffer-list) t))
|
||||
(when (equal (+workspace-current-name) +irc--workspace-name)
|
||||
(+workspace/delete +irc--workspace-name)))
|
||||
(message "Aborted")))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/ivy-jump-to-channel (&optional this-server)
|
||||
"Jump to an open channel or server buffer with ivy. If THIS-SERVER (universal
|
||||
argument) is non-nil only show channels in current server."
|
||||
(interactive "P")
|
||||
(if (not (circe-server-buffers))
|
||||
(message "No circe buffers available")
|
||||
(when (and this-server (not circe-server-buffer))
|
||||
(setq this-server nil))
|
||||
(ivy-read (format "Jump to%s: " (if this-server (format " (%s)" (buffer-name circe-server-buffer)) ""))
|
||||
(cl-loop with servers = (if this-server (list circe-server-buffer) (circe-server-buffers))
|
||||
with current-buffer = (current-buffer)
|
||||
for server in servers
|
||||
collect (buffer-name server)
|
||||
nconc
|
||||
(with-current-buffer server
|
||||
(cl-loop for buf in (circe-server-chat-buffers)
|
||||
unless (eq buf current-buffer)
|
||||
collect (format " %s" (buffer-name buf)))))
|
||||
:action #'ivy--switch-buffer-action
|
||||
:preselect (buffer-name (current-buffer))
|
||||
:keymap ivy-switch-buffer-map
|
||||
:caller '+irc/ivy-jump-to-channel)))
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
;;; app/irc/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;; Config
|
||||
;;
|
||||
|
||||
(defvar +irc-left-padding 13
|
||||
"TODO")
|
||||
|
||||
|
@ -19,13 +15,18 @@
|
|||
(defvar +irc-notifications-watch-strings nil
|
||||
"TODO")
|
||||
|
||||
(defvar +irc-connections nil
|
||||
"A list of connections set with :irc. W")
|
||||
|
||||
(defvar +irc-defer-notifications nil
|
||||
"How long to defer enabling notifications, in seconds (e.g. 5min = 300).
|
||||
Useful for ZNC users who want to avoid the deluge of notifications during buffer
|
||||
playback.")
|
||||
|
||||
(def-setting! :irc (server letvars)
|
||||
"Registers an irc server for circe."
|
||||
`(progn
|
||||
(push ',(cons server letvars) +irc--accounts)
|
||||
(push ',(cons server letvars) circe-network-options)))
|
||||
|
||||
(defvar +irc--accounts nil)
|
||||
`(cl-pushnew (cons ,server ,letvars) +irc-connections
|
||||
:test #'equal :key #'car))
|
||||
|
||||
|
||||
;;
|
||||
|
@ -33,93 +34,92 @@
|
|||
;;
|
||||
|
||||
(def-package! circe
|
||||
;; need a low impact function just to force an eval
|
||||
:commands (circe circe--version)
|
||||
:init
|
||||
(setq circe-network-defaults nil)
|
||||
:commands (circe circe-server-buffers)
|
||||
:init (setq circe-network-defaults nil)
|
||||
:config
|
||||
(set! :evil-state 'circe-channel-mode 'emacs)
|
||||
;; change hands
|
||||
(setq circe-network-options +irc-connections)
|
||||
(defvaralias '+irc-connections 'circe-network-options)
|
||||
|
||||
(defun +irc|circe-format-padding (left right)
|
||||
(format (format "%%%ds | %%s" +irc-left-padding) left right))
|
||||
(defsubst +irc--pad (left right)
|
||||
(format (format "%%%ds | %%s" +irc-left-padding)
|
||||
(concat "*** " left) right))
|
||||
|
||||
(setq circe-use-cycle-completion t
|
||||
(setq circe-default-quit-message nil
|
||||
circe-default-part-message nil
|
||||
circe-use-cycle-completion t
|
||||
circe-reduce-lurker-spam t
|
||||
|
||||
circe-format-say (format "{nick:+%ss} │ {body}" +irc-left-padding)
|
||||
circe-format-self-say circe-format-say
|
||||
circe-format-action (format "{nick:+%ss} * {body}" +irc-left-padding)
|
||||
circe-format-self-action circe-format-action
|
||||
|
||||
circe-format-server-topic
|
||||
(+irc|circe-format-padding "*** Topic" "{userhost}: {topic-diff}")
|
||||
|
||||
(+irc--pad "Topic" "{userhost}: {topic-diff}")
|
||||
circe-format-server-join-in-channel
|
||||
(+irc|circe-format-padding "*** Join" "{nick} ({userinfo}) joined {channel}")
|
||||
|
||||
(+irc--pad "Join" "{nick} ({userinfo}) joined {channel}")
|
||||
circe-format-server-join
|
||||
(+irc|circe-format-padding "*** Join" "{nick} ({userinfo})")
|
||||
|
||||
(+irc--pad "Join" "{nick} ({userinfo})")
|
||||
circe-format-server-part
|
||||
(+irc|circe-format-padding "*** Part" "{nick} ({userhost}) left {channel}: {reason}")
|
||||
|
||||
(+irc--pad "Part" "{nick} ({userhost}) left {channel}: {reason}")
|
||||
circe-format-server-quit
|
||||
(+irc|circe-format-padding "*** Quit" "{nick} ({userhost}) left IRC: {reason}]")
|
||||
|
||||
(+irc--pad "Quit" "{nick} ({userhost}) left IRC: {reason}]")
|
||||
circe-format-server-quit-channel
|
||||
(+irc|circe-format-padding "*** Quit" "{nick} ({userhost}) left {channel}: {reason}]")
|
||||
|
||||
(+irc--pad "Quit" "{nick} ({userhost}) left {channel}: {reason}]")
|
||||
circe-format-server-rejoin
|
||||
(+irc|circe-format-padding "*** Re-join" "{nick} ({userhost}), left {departuredelta} ago")
|
||||
|
||||
(+irc--pad "Re-join" "{nick} ({userhost}), left {departuredelta} ago")
|
||||
circe-format-server-nick-change
|
||||
(+irc|circe-format-padding "*** Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
|
||||
|
||||
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
|
||||
circe-format-server-nick-change-self
|
||||
(+irc|circe-format-padding "*** Nick" "You are now known as {new-nick} ({old-nick})")
|
||||
|
||||
(+irc--pad "Nick" "You are now known as {new-nick} ({old-nick})")
|
||||
circe-format-server-nick-change-self
|
||||
(+irc|circe-format-padding "*** Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
|
||||
|
||||
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
|
||||
circe-format-server-mode-change
|
||||
(+irc|circe-format-padding "*** Mode" "{change} on {target} by {setter} ({userhost})")
|
||||
|
||||
(+irc--pad "Mode" "{change} on {target} by {setter} ({userhost})")
|
||||
circe-format-server-lurker-activity
|
||||
(+irc|circe-format-padding "*** Lurk" "{nick} joined {joindelta} ago"))
|
||||
(+irc--pad "Lurk" "{nick} joined {joindelta} ago"))
|
||||
|
||||
(defun +irc*circe-truncate-nicks (orig-func keywords)
|
||||
"If nick is too long, truncate it. Uses `+irc-left-padding'
|
||||
to determine length."
|
||||
(when (plist-member keywords :nick)
|
||||
(let* ((long-nick (plist-get keywords :nick))
|
||||
(short-nick (s-left (- +irc-left-padding 1) long-nick)))
|
||||
;; only change the nick if it's needed
|
||||
(unless (or (= +irc-left-padding
|
||||
(length long-nick))
|
||||
(equal long-nick short-nick))
|
||||
(plist-put keywords :nick (s-concat short-nick "▶")))))
|
||||
(funcall orig-func keywords))
|
||||
(advice-add 'circe--display-add-nick-property :around #'+irc*circe-truncate-nicks)
|
||||
(enable-circe-new-day-notifier)
|
||||
|
||||
(defun +irc*circe-disconnect-hook (&rest _)
|
||||
(run-hooks '+irc-disconnect-hook))
|
||||
(advice-add 'circe--irc-conn-disconnected :after #'+irc*circe-disconnect-hook)
|
||||
|
||||
(defun +irc*circe-truncate-nicks ()
|
||||
"Truncate long nicknames in chat output (non-destructive)."
|
||||
(when-let (beg (text-property-any (point-min) (point-max) 'lui-format-argument 'nick))
|
||||
(goto-char beg)
|
||||
(let ((end (next-single-property-change beg 'lui-format-argument))
|
||||
(nick (plist-get (plist-get (text-properties-at beg) 'lui-keywords)
|
||||
:nick)))
|
||||
(when (> (length nick) +irc-left-padding)
|
||||
(compose-region (+ beg +irc-left-padding -1) end
|
||||
?…)))))
|
||||
(add-hook 'lui-pre-output-hook #'+irc*circe-truncate-nicks)
|
||||
|
||||
(add-hook! '+irc-disconnect-hook
|
||||
(run-at-time "5 minute" nil #'circe-reconnect-all))
|
||||
|
||||
(defun +irc|circe-message-option-bot (nick &rest ignored)
|
||||
(when (member nick +irc-bot-list)
|
||||
'((text-properties . (face circe-fool-face
|
||||
lui-do-not-track t)))))
|
||||
'((text-properties . (face circe-fool-face lui-do-not-track t)))))
|
||||
(add-hook 'circe-message-option-functions #'+irc|circe-message-option-bot)
|
||||
|
||||
(enable-circe-new-day-notifier)
|
||||
(add-hook! 'circe-server-connected-hook
|
||||
(run-at-time "5 minutes" nil #'enable-circe-notifications))
|
||||
|
||||
(add-hook! 'circe-channel-mode-hook
|
||||
#'(enable-circe-color-nicks enable-lui-autopaste turn-on-visual-line-mode)))
|
||||
#'(enable-circe-color-nicks enable-lui-autopaste turn-on-visual-line-mode))
|
||||
|
||||
(after! evil
|
||||
;; Let `+irc/quit' and `circe' handle buffer cleanup
|
||||
(map! :map circe-mode-map [remap doom/kill-this-buffer] #'bury-buffer)
|
||||
|
||||
;; Ensure entering insert mode will put us at the prompt.
|
||||
(add-hook! 'lui-mode-hook
|
||||
(add-hook 'evil-insert-state-entry-hook #'end-of-buffer nil t)))
|
||||
|
||||
(after! solaire-mode
|
||||
;; distinguish chat/channel buffers from server buffers.
|
||||
(add-hook 'circe-chat-mode-hook #'solaire-mode)))
|
||||
|
||||
|
||||
|
||||
(def-package! circe-color-nicks
|
||||
|
@ -131,22 +131,33 @@ to determine length."
|
|||
|
||||
(def-package! circe-new-day-notifier
|
||||
:commands enable-circe-new-day-notifier
|
||||
:config (setq circe-new-day-notifier-format-message
|
||||
(+irc|circe-format-padding
|
||||
"*** Day"
|
||||
"Date changed [{day}]")))
|
||||
:config
|
||||
(setq circe-new-day-notifier-format-message
|
||||
(+irc--pad "Day" "Date changed [{day}]")))
|
||||
|
||||
|
||||
(def-package! circe-notifications
|
||||
:commands enable-circe-notifications
|
||||
:config (setq circe-notifications-watch-strings
|
||||
+irc-notifications-watch-strings))
|
||||
:init
|
||||
(if +irc-defer-notifications
|
||||
(add-hook! 'circe-server-connected-hook
|
||||
(run-at-time +irc-defer-notifications nil #'enable-circe-notifications))
|
||||
(add-hook 'circe-server-connected-hook #'enable-circe-notifications))
|
||||
:config
|
||||
(setq circe-notifications-watch-strings +irc-notifications-watch-strings
|
||||
circe-notifications-emacs-focused nil
|
||||
circe-notifications-alert-style
|
||||
(cond (IS-MAC 'osx-notifier)
|
||||
(IS-LINUX 'libnotify))))
|
||||
|
||||
|
||||
(def-package! lui
|
||||
:commands lui-mode
|
||||
:config
|
||||
(map! :map lui-mode-map "C-u" #'lui-kill-to-beginning-of-line)
|
||||
(when (featurep! :feature spellcheck)
|
||||
(setq lui-flyspell-p t
|
||||
lui-fill-type nil))
|
||||
|
||||
(enable-lui-logging)
|
||||
(defun +irc|lui-setup-margin ()
|
||||
|
@ -157,10 +168,7 @@ to determine length."
|
|||
(setq fringes-outside-margins t
|
||||
word-wrap t
|
||||
wrap-prefix (s-repeat (+ +irc-left-padding 3) " ")))
|
||||
(add-hook! 'lui-mode-hook '(+irc|lui-setup-margin +irc|lui-setup-wrap))
|
||||
(when (featurep! :feature spellcheck)
|
||||
(setq lui-flyspell-p t
|
||||
lui-fill-type nil)))
|
||||
(add-hook! 'lui-mode-hook #'(+irc|lui-setup-margin +irc|lui-setup-wrap)))
|
||||
|
||||
|
||||
(def-package! lui-autopaste
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue