about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2019-12-24T13·38+0000
committerWilliam Carroll <wpcarro@gmail.com>2020-01-06T15·25+0000
commitf182410fd23b63ffc6af24e7e73cc99ee528405e (patch)
tree91b8e9fa763f04e8ba5ebc16a8875bbbf0a0fc33
parent50f99976e0ba8de4ee03e6fa92498c50b75e57c0 (diff)
Support Freenode IRC
Previously my ERC setup just supported Google's internal IRC. Now I have
Freenode for things like #nixos, #emacs.

This complicated my KBDs for cycling through IRC channels since certain channels
only exist on certain servers. To remedy this, I introduced a temporary solution
that looks up the server given a particular channel. This isn't ideal, but it
works for now.
-rw-r--r--configs/shared/.emacs.d/init.el1
-rw-r--r--configs/shared/.emacs.d/wpc/irc.el129
2 files changed, 113 insertions, 17 deletions
diff --git a/configs/shared/.emacs.d/init.el b/configs/shared/.emacs.d/init.el
index 3d0030314b6f..e07157c70f3f 100644
--- a/configs/shared/.emacs.d/init.el
+++ b/configs/shared/.emacs.d/init.el
@@ -23,6 +23,7 @@
 (require 'dotfiles)
 (require 'bookmark)
 (require 'keyboard)
+(require 'irc)
 (require 'email)
 
 (require 'wpc-keybindings)
diff --git a/configs/shared/.emacs.d/wpc/irc.el b/configs/shared/.emacs.d/wpc/irc.el
index 3dfaabdb397b..790ecc283587 100644
--- a/configs/shared/.emacs.d/wpc/irc.el
+++ b/configs/shared/.emacs.d/wpc/irc.el
@@ -13,24 +13,70 @@
 (require 'erc)
 (require 'cycle)
 (require 'string)
+(require 'prelude)
+(require 'alist)
+(require 'set)
+(require 'maybe)
+(require 'macros)
+(require 'password-store)
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Configuration
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
+(defconst irc/enable-tests? t
+  "When t, run the tests defined herein.")
+
 (setq erc-rename-buffers t)
 
-(defvar irc/channels-cycle
-  (cycle/from-list
-   '("#omg" "#london" "#panic" "#prod-team"))
-  "List of channels through which I can cycle.")
+;; TODO: Find a way to avoid putting "freenode" and "#freenode" as channels
+;; here.  I'm doing it because when erc first connects, it's `(buffer-name)' is
+;; "freenode", so when `irc/next-channel' is called, it 404s on the
+;; `cycle/contains?' call in `irc/channel->cycle" unless "freenode" is there. To
+;; make matters even uglier, when `erc-join-channel' is called with "freenode"
+;; as the value, it connects to the "#freenode" channel, so unless "#freenode"
+;; exists in this cycle also, `irc/next-channel' breaks again.  This doesn't
+;; pass my smell test.
+(defconst irc/server->channels
+  `(("irc.freenode.net"    . ,(cycle/new "freenode" "#freenode" "#nixos" "#emacs" "#pass"))
+    ("irc.corp.google.com" . ,(cycle/new "#omg" "#london" "#panic" "#prod-team")))
+  "Mapping of IRC servers to a cycle of my preferred channels.")
+
+;; TODO: Assert that no two servers have a channel with the same name. We need
+;; this because that's the assumption that underpins the `irc/channel->server'
+;; function. This will probably be an O(n^2) operation.
+(prelude/assert
+ (set/distinct? (set/from-list
+                 (cycle/to-list
+                  (alist/get "irc.freenode.net"
+                             irc/server->channels)))
+                (set/from-list
+                 (cycle/to-list
+                  (alist/get "irc.corp.google.com"
+                             irc/server->channels)))))
+
+(defun irc/channel->server (server->channels channel)
+  "Resolve an IRC server from a given CHANNEL."
+  (let ((result (alist/find (lambda (k v) (cycle/contains? channel v))
+                            server->channels)))
+    (prelude/assert (maybe/some? result))
+    result))
+
+(defun irc/channel->cycle (server->channels channel)
+  "Resolve an IRC's channels cycle from a given CHANNEL."
+  (alist/get (irc/channel->server server->channels channel)
+             server->channels))
 
 ;; Setting `erc-join-buffer' to 'bury prevents erc from stealing focus of the
 ;; current buffer when it connects to IRC servers.
 (setq erc-join-buffer 'bury)
 
+;; TODO: Here is another horrible hack that should be revisted.
 (setq erc-autojoin-channels-alist
-      `(("corp.google.com" . ,(cycle/to-list irc/channels-cycle))))
+      (->> irc/server->channels
+           (alist/map-values #'cycle/to-list)
+           (alist/map-keys (>> (s-chop-prefix "irc.")
+                               (s-chop-suffix ".net")))))
 
 (defcustom irc/install-kbds? t
   "When t, install the keybindings defined herein.")
@@ -43,6 +89,34 @@
   "Print message X in a structured way."
   (message (string/format "[irc.el] %s" x)))
 
+;; TODO: Integrate Google setup with Freenode setup.
+
+;; TODO: Support function or KBD for switching to an ERC buffer.
+
+(defun irc/kill-all-erc-processes ()
+  "Kills all ERC buffers and processes."
+  (interactive)
+  (->> (erc-buffer-list)
+       (-map #'kill-buffer)))
+
+(defun irc/switch-to-erc-buffer ()
+  "Switch to an ERC buffer."
+  (interactive)
+  (let ((buffers (erc-buffer-list)))
+    (if (list/empty? buffers)
+        (error "[irc.el] No ERC buffers available")
+      (switch-to-buffer (list/head (erc-buffer-list))))))
+
+(defun irc/connect-to-freenode ()
+  "Connect to Freenode IRC."
+  (interactive)
+  (erc-ssl :server "irc.freenode.net"
+           :port 6697
+           :nick "wpcarro"
+           :password (password-store-get "programming/irc/freenode")
+           :full-name "William Carroll"))
+
+;; TODO: Handle failed connections.
 (defun irc/connect-to-google ()
   "Connect to Google's Corp IRC using ERC."
   (interactive)
@@ -51,23 +125,32 @@
            :nick "wpcarro"
            :full-name "William Carroll"))
 
+;; TODO: Prefer defining these with a less homespun solution. There is a
+;; function call `erc-buffer-filter' that would be more appropriate for the
+;; implementation of `irc/next-channel' and `irc/prev-channel'.
 (defun irc/next-channel ()
-  "Join the next channel in `irc/channels-cycle'."
+  "Join the next channel for the active server."
   (interactive)
-  (erc-join-channel
-   (cycle/next irc/channels-cycle))
-  (irc/message
-   (string/format "Current IRC channel: %s"
-                  (cycle/current irc/channels-cycle))))
+  (with-current-buffer (current-buffer)
+    (let ((cycle (irc/channel->cycle irc/server->channels (buffer-name))))
+      (erc-join-channel
+       (cycle/next cycle))
+      (irc/message
+       (string/format "Current IRC channel: %s" (cycle/current cycle))))))
 
 (defun irc/prev-channel ()
-  "Join the previous channel in `irc/channels-cycle'."
+  "Join the previous channel for the active server."
   (interactive)
-  (erc-join-channel
-   (cycle/prev irc/channels-cycle))
-  (irc/message
-   (string/format "Current IRC channel: %s"
-                  (cycle/current irc/channels-cycle))))
+  (with-current-buffer (current-buffer)
+    (let ((cycle (irc/channel->cycle irc/server->channels (buffer-name))))
+      (erc-join-channel
+       (cycle/prev cycle))
+      (irc/message
+       (string/format "Current IRC channel: %s" (cycle/current cycle))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Keybindings
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (when irc/install-kbds?
   (general-define-key
@@ -75,5 +158,17 @@
    "<C-tab>" #'irc/next-channel
    "<C-S-iso-lefttab>" #'irc/prev-channel))
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Tests
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(when irc/enable-tests?
+  (prelude/assert
+   (equal
+    (irc/channel->server `(("irc.dairy.com" . ,(cycle/new "#cheese" "#milk"))
+                           ("irc.color.com" . ,(cycle/new "#red" "#blue")))
+                         "#cheese")
+    "irc.dairy.com")))
+
 (provide 'irc)
 ;;; irc.el ends here