#+PROPERTY: tangle site-lisp/circe-init.el #+STARTUP: content I used to use ERC mostly because I didn't really use IRC at all and it was basically the first IRC client in Emacs that was presented to me, it being built-in and all. When I started to use IRC more and wanted to customize the way it looks more I was surprised to find that it wasn't all that easy. A friend of mine was using Circe and he helped me figure out how to get started with customizing Circe the way I wanted it. So now I use Circe. * Enable lexical binding #+BEGIN_SRC emacs-lisp :padline no ;; -*- lexical-binding: t -*- #+END_SRC * Require the needed libraries In order to keep compiler warnings to a minimum, require the libraries that are used in the configuration of Circe. #+BEGIN_SRC emacs-lisp (require 'auth-source) (require 'circe) (require 's) #+END_SRC * Clean-up the display of messages I once saw a very clean and simple weechat configuration on [[https://www.reddit.com/r/unixporn][/r/unixporn]] and really wanted to have something similar. This was the start of my disappointment in ERC, I couldn't figure out how to change the way messages were printed. With a little help I did find out how to do it in Circe. First we create a variable to store the length of the longest known nick, so we can properly align all messages. This variable should be buffer-local because each IRC chat will have different users with different length names. We start with a length of ~0~ because we don't know what the shortest nick there is going to be. #+BEGIN_SRC emacs-lisp (defvar oni:circe-longest-nick 0) (make-variable-buffer-local 'oni:circe-longest-nick) #+END_SRC Then we write the function that will print the most important messages, the ones people send, including me. Whenever we get a message or send a message, we check the length of the nick with the last recorded maximum length. If the new nick is longer that any previous ones we set this new length as the longest known length and adjust =lui-fill-type= accordingly. This ensures that continuation lines are indented to the correct column. #+BEGIN_SRC emacs-lisp (defun oni:circe-say-formatter (&rest keywords) (let* ((nick (plist-get keywords :nick)) (len (length nick))) (when (> len oni:circe-longest-nick) (setq oni:circe-longest-nick len) (setq-local lui-fill-type (make-string (+ len 3) ?\ ))) (plist-put keywords :nick (s-pad-left oni:circe-longest-nick " " nick)) (lui-format "{nick} {body}" keywords))) #+END_SRC I use this formatter both for messages I send myself and incoming messages, because they should basically look the same. #+BEGIN_SRC emacs-lisp (setq circe-format-self-say #'oni:circe-say-formatter circe-format-say #'oni:circe-say-formatter) #+END_SRC The rest of the formatting functions are basically the same, except they don't need to change the known size of nicks because they don't print the nick in the same column, instead they usually print something like =***= to indicate that it is a system message and not a user message. We do pad whatever they print with the same number of spaces to keep them right-justified with the nicks. #+BEGIN_SRC emacs-lisp (defun oni:circe-action-formatter (&rest keywords) (format "%s %s %s" (s-pad-left oni:circe-longest-nick " " "*") (plist-get keywords :nick) (plist-get keywords :body))) (defun oni:circe-server-message-formatter (&rest keywords) (format "%s %s" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :body))) (defun oni:circe-server-join-in-channel-formatter (&rest keywords) (format "%s Join: %s (%s) joined %s" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :nick) (plist-get keywords :userinfo) (plist-get keywords :channel))) (defun oni:circe-server-join-formatter (&rest keywords) (format "%s %s joined the channel" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :nick))) (defun oni:circe-server-quit-formatter (&rest keywords) (format "%s %s quit IRC: %s" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :nick) (plist-get keywords :reason))) (defun oni:circe-server-quit-channel-formatter (&rest keywords) (format "%s %s left %s: %s" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :nick) (plist-get keywords :channel) (plist-get keywords :reason))) (defun oni:circe-server-part-formatter (&rest keywords) (format "%s %s parted %s: %s" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :nick) (plist-get keywords :channel) (plist-get keywords :reason))) (defun oni:circe-server-nick-change-formatter (&rest keywords) (format "%s %s is now known as %s" (s-pad-left oni:circe-longest-nick " " "***") (plist-get keywords :old-nick) (plist-get keywords :new-nick))) (setq circe-format-self-action #'oni:circe-action-formatter) (setq circe-format-action #'oni:circe-action-formatter) (setq circe-format-server-message #'oni:circe-server-message-formatter) (setq circe-format-server-join-in-channel #'oni:circe-server-join-in-channel-formatter) (setq circe-format-server-join #'oni:circe-server-join-formatter) (setq circe-format-server-quit #'oni:circe-server-quit-formatter) (setq circe-format-server-quit-channel #'oni:circe-server-quit-channel-formatter) (setq circe-format-server-part #'oni:circe-server-part-formatter) (setq circe-format-server-nick-change #'oni:circe-server-nick-change-formatter) #+END_SRC * Automatically join some channels I started using IRC because #mowedline was started and I felt obligated to join it as I was one of two known Mowedline users at the time. So now that's the one I'm usually active in. I do like to keep an eye on #emacs from time to time and #ninthfloor in case something happens there, though usually not. #+BEGIN_SRC emacs-lisp (defun oni:circe-nickserv-password-getter (host) (lambda (_) (let ((found (nth 0 (auth-source-search :max 1 :host host :require '(:secret))))) (when found (let ((secret (plist-get found :secret))) (if (functionp secret) (funcall secret) secret)))))) (setq circe-network-options `(("Freenode" :nick "ryuslash" :channels ("#emacs" "#mowedline" "#ninthfloor" "#dispass" "#linuxvoice" "#conkeror") :nickserv-password ,(oni:circe-nickserv-password-getter "irc.freenode.net")) ("Twitch" :use-tls nil :nick "ryuslash" :host "irc.twitch.tv" :pass ,(oni:circe-nickserv-password-getter "irc.twitch.tv") :port 6667))) #+END_SRC * Show #mowedline in a frame without minibuffer When I'm chatting on #mowedline I do so in a separate small window. This window needs no minibuffer as I do very little actualy Emacsy things in it. Just typing a little and reading. So far I only do this with #mowedline. In order to specifically show it in a frame without a minibuffer I use =display-buffer-alist= to specify how to show it. The function called dynamically binds =default-frame-alist= to add a =minibuffer= element with the value =nil= (meaning, no minibuffer). I can't do this in the regular =default-frame-alist= because I want all other frames to show up /with/ a minibuffer. It then creates a new frame and switches to the given buffer in it. #+BEGIN_SRC emacs-lisp (defun oni:display-in-minibufferless-frame (buffer _) (let ((default-frame-alist default-frame-alist)) (push '(minibuffer . nil) default-frame-alist) (let ((frame (make-frame))) (select-frame frame) (switch-to-buffer buffer)))) (add-to-list 'display-buffer-alist '("^\#mowedline$" oni:display-in-minibufferless-frame)) #+END_SRC * Show colored nicks Show colored nicks in IRC buffers. #+BEGIN_SRC emacs-lisp (require 'circe-color-nicks) (enable-circe-color-nicks) #+END_SRC * Provide the right feature In order to be able to use =(require 'circe-init)= we must first =provide= it. #+BEGIN_SRC emacs-lisp (provide 'circe-init) #+END_SRC