I’ve set up an Emacs function my-open-irc
that connects to all IRC channels I am interested in and sets up a 2x2 layout with the channels:
#ensime/ensime-emacs (gitter) | #emacs (freenode) |
---|---|
#ensime/ensime-server (gitter) | #archlinux (freenode) |
my-open-irc
called at any time either (re)connects to irc and sets up the layout or restores the old buffers.
It also saves anything I have been doing before under register ?8 so I can come back to it with C-x r j 8
.
If you are not aware of this functionality, see the documentation of window-configuration-to-register.
You can think of as my-open-irc
as “turn IRC on” and C-x r j 8
as “turn IRC off”, all from within emacs.
I have bind the my-open-irc
to some key with the global-set-key
.
It lets me to take a quick glance at “what’s going on”, and if anything appears interesting investigate it further.
Code is not of “production” quality, I may clean it up one day if it becomes problematic. I know I shouldn’t hardcode passwords in my .el config, but I do not expose my config and irc is not sensitive.
Just add below to your emacs config, add your nick/password in my-open-irc, and call my-open-irc.
Joining my take a few seconds, but you don’t need to do anything manually besides calling my-open-irc
.
(setq my-freenode-channels '("#archlinux" "#emacs"))
(setq my-gitter-channels '("#ensime/ensime-emacs" "#ensime/ensime-server"))
(setq my-erc-channel-buffer-regexps '("^#ensime\/ensime-emacs" "#ensime\/ensime-server" "^#archlinux" "^#emacs"))
(defun my-buffer-match-regex (buf)
(string-match buffer-regex (buffer-name buf)))
(defun my-switch-to-buffer-by-regex (buffer-regex)
(switch-to-buffer (find-if 'my-buffer-match-regex (buffer-list))))
(require 'erc-sasl)
(setq erc-hide-list '("JOIN" "PART" "QUIT"))
(setq erc-prompt-for-password nil)
;;;;; JOIN Channels after connection ;;;;;
(defun my-join-freenode-channel (channel)
(message "Attempting to connect to freenode channel %s" channel)
(my-switch-to-buffer-by-regex "^irc.freenode.net")
(erc-cmd-JOIN channel))
(defun my-join-gitter-channel (channel)
(my-switch-to-buffer-by-regex "^irc.gitter.im")
(erc-cmd-JOIN channel))
(defun myerc-autojoin-channels (server nick)
(message "Connected to IRC server %s" server)
(when (s-contains-p "freenode.net" server)
(mapc 'my-join-freenode-channel my-freenode-channels))
(when (s-contains-p "gitter.im" server)
(mapc 'my-join-freenode-channel my-freenode-channels)))
(add-hook 'erc-after-connect 'myerc-autojoin-channels)
;;;;; Set up 2x2 layout when getting all 4 channels ;;;;;
(defun my-setup-windows-in-two-by-two ()
(call-interactively 'delete-other-windows)
(my-switch-to-buffer-by-regex (nth 0 my-erc-channel-buffer-regexps))
(split-window-vertically)
(split-window-horizontally)
(windmove-down)
(split-window-horizontally)
(my-switch-to-buffer-by-regex (nth 1 my-erc-channel-buffer-regexps))
(windmove-right)
(my-switch-to-buffer-by-regex (nth 2 my-erc-channel-buffer-regexps))
(windmove-up)
(my-switch-to-buffer-by-regex (nth 3 my-erc-channel-buffer-regexps))
(windmove-left)
(window-configuration-to-register ?9)
(erc-fill-mode -1) ;; Do not wrap lines
)
(defun my-is-wanted-erc-channel (channel-name)
"Erc sometimes joins me to random channels lol"
(-any-p (lambda (regex) (string-match regex channel-name)) my-erc-channel-buffer-regexps))
(setq my-irc-joined-count 0) ;; so hack.
(defun my-post-join-on-hook ()
(message "Got channel %s in my-post-join-on-hook" (buffer-name))
(when (my-is-wanted-erc-channel (buffer-name))
(progn
(setq my-irc-joined-count (+ 1 my-irc-joined-count))
(message "Connected to IRC channel %s" (buffer-name))
(message "my-open-irc, my-irc-joined-count: %s" my-irc-joined-count)))
(when (equal my-irc-joined-count 4)
(my-setup-windows-in-two-by-two)))
(add-hook 'erc-join-hook 'my-post-join-on-hook)
;;;;; Auto re-connect function ;;;;;
(setq erc-join-buffer 'bury)
(defun my-maybe-reconnect-buffer (buffer)
(message "Attempting to reconnect %s. Connected: %s" buffer erc-server-connected)
(my-switch-to-buffer-by-regex buffer)
(when (not (erc-server-process-alive))
(message "%s not connected ! (erc-server-connected: %s)\n" buffer erc-server-connected)
(erc-server-reconnect)))
(defun my-maybe-reconnect-irc ()
(mapc 'my-maybe-reconnect-buffer my-erc-channel-buffer-regexps))
;;;;; Tie it all together ;;;;;
(defun my-open-irc ()
(interactive)
(window-configuration-to-register ?8) ;; Save old layout under 8
(if (> 4 my-irc-joined-count)
(progn
(erc-tls :server "irc.gitter.im" :port 6697 :nick "kozikow" :password "")
(erc :server "irc.freenode.net" :port 6667 :nick "kozikow" :password "")
(my-freenode-force-join))
(progn
(my-maybe-reconnect-irc)
(jump-to-register ?9)
)
)
(setq erc-modified-channels-alist nil) ;; Clean up old notifications
(erc-modified-channels-update) ;; Update changes
(erc-fill-mode -1))
(setq erc-insert-timestamp-function 'erc-insert-timestamp-left)
(global-set-key (kbd "C-S-c i") 'my-open-irc)
(define-key erc-mode-map (kbd "M-b") 'erc-button-press-button)
(require 'notify)
(erc-track-disable)
(defun my-erc-message-on-hook (erc-message)
(when (or (s-contains-p "ensime" (buffer-name))
(s-contains-p "kozikow" erc-message))
(notify (format "Irc %s" (buffer-name)) (format "%s" erc-message))))
(add-hook 'erc-insert-pre-hook 'my-erc-message-on-hook)
After all of this I think ERC is more of a library that you implement your own IRC client on, rather than actual IRC client.
Erc support for sasl is a bit hacky. Alternative emacs IRC client, circe, supports sasl “out of the box”, but gitter connection was buggy using circe. At that point, I didn’t try out ERC yet, so after a bit of debugging I decided to try out the ERC.
To set up ERC with sasl I did:
- Install erc with package-install. erc-sasl is not in any emacs repo, so I prefered to install plain erc to avoid manual updates.
- From erc-sasl repository, copy just erc-sasl file: https://github.com/joseph-gay/erc-sasl/blob/sasl/erc-sasl.el to your emacs config.
- In erc-sasl add
(require 'erc)
and anywhere in your emacs config(require 'erc-sasl)
.
Motivation for such configuration is that erc-sasl is a bit outdated. erc may one day get merged sasl support. Depending only on single erc-sasl file will make it easier to migrate to hypothetical updated erc version.
Follow instructions from https://irc.gitter.im/ to get the irc password.
Beware that gitter joins you to more channels than I want. Initially, gitter did not auto connect me to any channel after just connecting to the gitter server. When I connected to at least one of the channels it connected me to all channels I ever joined through the website.
It caused some bugs in my code running erc-join-hook
.
To solve it, I simply check if joined channels are in my-erc-channels
with (-contains-p my-erc-channels (buffer-name))
.
I spent at least half an hour trying to make erc-track do what I want. It’s much easier to disable it and do what you want in erc-insert-pre-hook. This hook allows you to get the message and channel name. I use notify package, that lets me send native system notifications regardless of my host operating system and implement my desired filtering using elisp.
(require 'notify)
(erc-track-disable)
(defun my-erc-message-on-hook (erc-message)
(when (or (s-contains-p "ensime" (buffer-name))
(s-contains-p "kozikow" erc-message))
(notify (format "Irc %s" (buffer-name)) (format "%s" erc-message))))
(add-hook 'erc-insert-pre-hook 'my-erc-message-on-hook)
By default you need to guess when ERC lost connection and reconnect it.
my-maybe-reconnect-irc
works ok for me, but it sadly does not preserve IRC history.
Default line wrapping and timestamps does not work well with windowed IRC layout. I disabled the timestamp “on the right” and disabled the line wrapping.
(erc-fill-mode -1)
(setq erc-insert-timestamp-function 'erc-insert-timestamp-left)
ERC sometimes named buffers simply by “#emacs” and sometimes “#emacs@freenode”. IIUC this behavior changed between emacs versions. Good enough solution is using regexps. To switch to channel by regex:
(defun my-buffer-match-regex (buf)
(string-match buffer-regex (buffer-name buf)))
(defun my-switch-to-buffer-by-regex (buffer-regex)
(switch-to-buffer (find-if 'my-buffer-match-regex (buffer-list))))
(my-switch-to-buffer-by-regex "^#emacs")
For some reason, ERC did not auto connect to all channels, even when told so. It is quite simple to do it “manually” from elisp, so I went ahead and did it. E.g. for freenode it is:
(my-switch-to-buffer-by-regex "^irc.freenode.net")
(erc-cmd-JOIN channel))
- At any time, when I feel like checking out IRC I call the
my-open-irc
via the keybinding. It either connects, reconnects or opens the existing ERC buffers in 2x2 layout. - When I feel like doing some work after chatting on IRC I call
C-x r j 8
. - If there are any notifications,
my-open-irc
cleans them all (I still have some bug when it leaves some notifications hanging in mode buffer).