Skip to content
John Hamelink edited this page Sep 8, 2023 · 71 revisions

Configuration Examples

Circe and Lui both provide extensive configuration options using Emacs' Customize interface. Take a look at M-x customize-group RET circe RET and M-x customize-group RET lui RET.

The following more advanced examples should go into your ~/.emacs file.

Circe

Network Configuration

If you join the same networks regularly, you can configure the default values easily.

(setq circe-network-options
      `(("Libera Chat"
         :nick "yournick"
         :channels ("#emacs" "#emacs-circe")
         :nickserv-password ,libera-password)))

Once you have that, you can just use M-x circe RET Libera Chat RET to connect to IRC.

Please note that the example uses password variables. It's usually a good idea to keep your passwords out of your .emacs. I use a ~/.private.el file that just sets my passwords for me:

(setq libera-password "top-secret-string")

Then I can just use (load-file "~/.private.el") before I use passwords in my .emacs.

Safer password management

While the previously demonstrated method is simple and convenient, it comes with a serious drawback: Your password may end up in Emacs backtraces! To prevent this problem, it is possible to use a function name instead of a plaintext password, it will get called later by Circe with the associated server to determine the password to be used.

(setq my-credentials-file "~/.private.el")

(defun my-nickserv-password (server)
  (with-temp-buffer
    (insert-file-contents-literally my-credentials-file)
    (plist-get (read (buffer-string)) :nickserv-password)))

(setq circe-network-options
      '(("Libera Chat"
         :nick "yournick"
         :channels ("#emacs" "#emacs-circe")
         :nickserv-password my-nickserv-password)))

The credentials file will look a bit differently:

(:nickserv-password "top-secret-string")

An even safer example makes use of the auth-source API to read GPG-encrypted credentials:

(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 "yournick" :machine "irc.libera.chat"))

(setq circe-network-options
      '(("Libera Chat"
         :nick "yournick"
         :channels ("#emacs" "#emacs-circe")
         :nickserv-password my-nickserv-password)))

The contents of the authinfo file might look as follows:

machine irc.libera.chat login yournick password "top-secret-string" port 6667

Join Channels requiring Identification

Some channels might require you to be identified before you can join the channel. As well as configuring your credentials, you should also make sure you add :after-auth before the channel list, e.g.

(setq circe-network-options
      `(("Libera Chat"
         :nick "yournick"
         :channels (:after-auth "#wildfly" "#hibernate-dev")
         :nickserv-password ,libera-password)))

Adding a new network

To add support for a completely new type of network, you can either put the full network definition in circe-network-options or add the defaults to circe-networks. See the latter for multiple examples.

The following is the Libera definition:

("Libera Chat" :host "irc.libera.chat" :port (6667 . 6697)
 :tls t
 :nickserv-mask "^NickServ!NickServ@services\\.libera\\.chat$"
 :nickserv-identify-challenge "This nickname is registered."
 :nickserv-identify-command "PRIVMSG NickServ :IDENTIFY {nick} {password}"
 :nickserv-identify-confirmation "^You are now identified for \x02.*\x02\\.$"
 :nickserv-ghost-command "PRIVMSG NickServ :GHOST {nick} {password}"
 :nickserv-ghost-confirmation "has been ghosted\\.$\\|is not online\\.$"
 )

Each of these sets a variable, for example :nickserv-mask affects circe-nickserv-mask (which see). You can add your own network definitions based on this. The account needs to be registered for the nickserv options to work as expected.

Tab Completion

Circe is using Emacs' built-in completion support by default which opens a window with candidates you can select with the mouse. It is possible to enable a more traditional inline variant in Emacs 24.1 or newer:

(setq circe-use-cycle-completion t)

If you get a helm buffer when completing nicknames, this can be disabled by either not using helm-mode at all or customizing the following part of it:

(setq helm-mode-no-completion-in-region-in-modes
      '(circe-channel-mode
        circe-query-mode
        circe-server-mode))

See emacs-helm/helm#673 and emacs-circe/circe#193 for further discussion.

Quick IRC Command

If you have a number of networks you use, it can be useful to have a simple command to connect to them all.

(defun irc ()
  "Connect to IRC"
  (interactive)
  (circe "Libera Chat")
  (circe "Bitlbee")
  (circe "IRCnet"))

To make this a bit smarter (e.g. against accidentally running M-x irc and opening a second connection to all the networks, when one meant to run M-x circe), the following pair of functions can be used, with circe-maybe-connect meant to replace circe in the above definition of the command irc.

(defun circe-network-connected-p (network)
  "Return non-nil if there's any Circe server-buffer whose
`circe-server-netwok' is NETWORK."
  (catch 'return
    (dolist (buffer (circe-server-buffers))
      (with-current-buffer buffer
        (if (string= network circe-server-network)
            (throw 'return t))))))

(defun circe-maybe-connect (network)
  "Connect to NETWORK, but ask user for confirmation if it's
already been connected to."
  (interactive "sNetwork: ")
  (if (or (not (circe-network-connected-p network))
          (y-or-n-p (format "Already connected to %s, reconnect?" network)))
      (circe network)))

Hiding the Join/Part Spam

Say you don't want Circe to show JOIN, PART and QUIT messages. You have two options.

First, Circe comes with a special feature to reduce such spam. If enabled, Circe will hide JOIN, PART and QUIT messages. If a user speaks, Circe will say that this is the first activity of this user, and how long ago they joined. Once they have spoken up like this, Circe will show PART and QUIT normally.

To enable this feature globally, simply add this to your .emacs:

(setq circe-reduce-lurker-spam t)

Alternatively, you can pass :reduce-lurker-spam t to your network specification to enable this feature locally.

Hiding Other Messages

Let's say you don't want to see JOIN messages at all (this code works for other IRC commands, too). You can do this by simply installing a display handler for a command that does nothing.

(circe-set-display-handler "JOIN" (lambda (&rest ignored) nil))

Automatic Pasting

Circe (actually, lui) has the ability to intercept long pastes if it is done in a single input. Lui will then ask if the user would prefer to use a paste service.

(require 'lui-autopaste)
(add-hook 'circe-channel-mode-hook 'enable-lui-autopaste)

Topic Diffs

Some channels have very long topics. When they change, it's difficult to tell what of it exactly changed. Circe has a feature to actually display a diff for a topic change. Try it!

(setq circe-format-server-topic "*** Topic change by {userhost}: {topic-diff}")

Channel Name in the Prompt

(add-hook 'circe-chat-mode-hook 'my-circe-prompt)
(defun my-circe-prompt ()
  (lui-set-prompt
   (concat (propertize (concat (buffer-name) ">")
                       'face 'circe-prompt-face)
           " ")))

Aligning nick names and messages

It is possible to align channel messages by customizing circe-format-say:

(setq circe-format-say "{nick:-16s} {body}")

The above example right-pads every nickname to take up 16 spaces at most. See the docstrings of lui-format and format for further details on permitted format control strings.

Chanop Commands

The optional module circe-chanop provides the chanop commands /MODE, /BANS, /KICK, /GETOP, and /DROPOP. To enable these commands:

(eval-after-load 'circe '(require 'circe-chanop))

Strip mIRC Color Codes

(eval-after-load 'circe
  '(defun lui-irc-propertize (&rest args)))

...only in certain channels

;;; Strip mIRC codes in certain channels
;;;
(defvar my-circe-strip-mirc-codes-channels '())
(eval-after-load 'circe
  '(progn
     (defadvice lui-irc-propertize (around strip-mirc-codes)
       (unless (member (buffer-name) my-circe-strip-mirc-codes-channels)
         ad-do-it))
     (ad-activate 'lui-irc-propertize)))

Define a Command

Defining a new slash-command in Circe is trivially easy. Just define a function with a name like circe-command-FOO where FOO, in uppercase, is the name of the slash command. When the command is invoked from the Circe prompt, this function is called with a single argument: a string of all text given after the command name (and one space) on the command line. It is the responsibility of the command to parse its arguments further.

(defun circe-command-RECONNECT (&optional ignored)
  (circe-reconnect))

Note, some of the built-in commands have longer lists of arguments and 'interactive' forms. These are simply to allow those commands to be called in other contexts such from key bindings or the M-x prompt.

Gray Out Bot Text

This snippet was broken in d8cd635165 and needs to be rewritten

Here is how to gray out, and not track, channel messages from known bots.

(defvar my-circe-bot-list '("fsbot" "rudybot"))
(defun my-circe-message-option-bot (nick &rest ignored)
  (when (member nick my-circe-bot-list)
    '((text-properties . (face circe-fool-face
                          lui-do-not-track t)))))
(add-hook 'circe-message-option-functions 'my-circe-message-option-bot)

ChanServ welcome notices in appropriate channel buffer

This snippet was broken in d8cd635165 and needs to be rewritten

The ChanServ service on Libera and elsewhere can send a welcome notice to joining users of a channel. Since these notices are directed to the user, not the channel, Circe will ordinarily display them in whatever happens to be your last active buffer for the server. I'm in one channel, but I'm seeing information about another — what's up with that? The following hack is a way to have these welcome notices displayed in the particular buffer of the channel that they are about. It does so by means of a message-options handler and a message handler. The message-options handler tells Circe to suppress ordinary display of the message. The message handler sets up an appropriate "active buffer" to receive the message, and in this temporary environment, calls the normal display handler for notices. This method avoids redefining circe-display-NOTICE, which is undesirable.

(defun my-circe-message-option-chanserv (nick user host command args)
  (when (and (string= "ChanServ" nick)
             (string-match "^\\[#.+?\\]" (cadr args)))
    '((dont-display . t))))
(add-hook 'circe-message-option-functions 'my-circe-message-option-chanserv)

(defun my-circe-chanserv-message-handler (nick user host command args)
  (when (and (string= "ChanServ" nick)
             (string-match "^\\[\\(#.+?\\)\\]" (cadr args)))
    (let* ((channel (match-string 1 (cadr args)))
           (buffer (circe-server-get-chat-buffer channel t)))
      (let ((circe-server-last-active-buffer buffer))
        (circe-display-NOTICE nick user host command args)))))
(circe-set-display-handler "NOTICE" 'my-circe-chanserv-message-handler)

Adding message-options to the rerouted display

This snippet was broken in d8cd635165 and needs to be rewritten

When you reroute the display of a message as shown in this section, the initial application of dont-display suppresses any other display message options, namely text-properties that you might want to supply. We can hack around that by a small modification to the inner let form of my-circe-chanserv-message-handler. We'll be dealing with internal details of the message-options system, so please note, this is a hack.

Define a message option function for the options you want to apply to the rerouted display:

(defun my-circe-message-option-chanserv-dont-track (nick user host command args)
  '((text-properties . (lui-do-not-track t
                        face highlight))))

Then modify the inner let form of my-circe-chanserv-message-handler so that it looks like this, instead:

(let ((circe-server-last-active-buffer buffer)
      ;; hack in some message options for the rerouted display..
      (circe-message-option-cache (make-hash-table :test 'equal))
      (circe-message-option-functions
       '(my-circe-message-option-chanserv-dont-track)))
  (circe-display-NOTICE nick user host command args))

You might obviously want to take out the face highlight from the message-options, but applying some color is just an easy way to verify that it's working.

Lui

Spell Checker

Circe's user interface, Lui, comes with spellchecking support. You can configure per-channel dictionaries. The following example will enable flyspell in Lui globally, and use the american dictionary by default, except in the channel #hamburg, where it will use german8.

  (setq lui-flyspell-p t
        lui-flyspell-alist '(("#hamburg" "german8")
                             (".*" "american")))

Logging

The logging module provides a history of chat conversations. Whenever a logging enabled chat buffer is closed its contents will be saved in a log file.

There are a few ways to enable logging:

  • globally:
    (load "lui-logging" nil t)
    (enable-lui-logging-globally)
  • per network with the :logging option:
    (setq circe-network-options
       '(("Libera Chat"
          :logging t)))
  • or per buffer with M-x enable-lui-logging

Change the directory where logs will be written to by customizing lui-logging-directory.

Spelling Training

Sometimes, there are typos you just do over and over again. You can tell Circe (or rather, Lui) to simply not send lines containing those misspellings.

(add-hook 'lui-pre-input-hook 'fc-there-is-no-wether)

(defun fc-there-is-no-wether ()
  "Throw an error when the buffer contains \\"wether\\".
Or other words I used repeatedly."
  (goto-char (point-min))
  (when (re-search-forward (regexp-opt '("wether"
                                         "occurence" "occurrance"
                                         "occurance"
                                         )
                                       'words)
                           nil t)
    (error "It's \\"whether\\" and \\"occurrence\\"!")))

Inline Nick Reply

Display your own nickname instead of >>> in the channel buffers when you're talking.

(setq circe-format-self-say "<{nick}> {body}")

Timestamps in Margins

Emacs 23 added a feature called 'margins' which lets you have annotations in a margin area to the right or left of a buffer. Circe is able to put the timestamps in this area. Below is an example of how to do this. Note that in addition to telling Circe to use margins, you also need to tell emacs to turn on margins for your circe buffers.

(setq
 lui-time-stamp-position 'right-margin
 lui-time-stamp-format "%H:%M")

(add-hook 'lui-mode-hook 'my-circe-set-margin)
(defun my-circe-set-margin ()
  (setq right-margin-width 5))

Fluid-width windows

Thanks to several interesting emacs features, Lui buffers can be made to dynamically fit arbitrary window sizes without turning ugly, even with right-aligned time-stamps. For this, put right-aligned time-stamps into the margin, preferably enable fringes-outside-margins, disable filling, enable word wrapping, and set wrap-prefix to a preferred value, e.g. the string you had in lui-fill-type. (The non-string values accepted in lui-fill-type are sadly not accepted here.)

(setq
 lui-time-stamp-position 'right-margin
 lui-fill-type nil)

(add-hook 'lui-mode-hook 'my-lui-setup)
(defun my-lui-setup ()
  (setq
   fringes-outside-margins t
   right-margin-width 5
   word-wrap t
   wrap-prefix "    "))

On a graphical display, emacs will display continuation indicators in the fringe by default when word-wrap is enabled. These can be disabled by adding the following form to the above my-lui-setup function.

(setf (cdr (assoc 'continuation fringe-indicator-alist)) nil)

Highlight Keywords

The variable lui-highlight-keywords gives a list of words to highlight in a lui buffer. This can be used, for example, to highlight pal / friend nicknames in circe buffers. See its docstring for more information.

Highlight Markup

It's possible to highlight *strong text* or _emphasized text_ or any other kind of inline markup you can think of by customizing lui-formatting-list. See its docstring for more information.

Track Bar

To track your last reading position in a lui buffer, pick the desired behavior and enable the track bar module in your init file:

(setq lui-track-bar-behavior 'before-switch-to-buffer)
(enable-lui-track-bar)

Other choices for less intrusive behavior would be before-tracking-next-buffer and after-sending. For maximum control, bind the lui-track-bar-move command and use it interactively:

(eval-after-load 'lui
  '(define-key lui-mode-map (kbd "C-c C-b") 'lui-track-bar-move))

Tracking

Selectively Disable Tracking

Tracking can be fine-tuned on a per-buffer basis by configuring the variable tracking-ignored-buffers. The value of this variable is a list of regular expressions or conses. Regular expressions cover the simple case: tracking will be disabled for any buffer whose name is matched by one of the regular expressions. Giving a cons allows you more control. That form is a regular expression consed to a list of faces for which tracking is selectively enabled. The cons form is what you usually want, because you still want tracking to work when somebody says your name in a channel that you are otherwise ignoring. The simple form might look like this in your config:

(setq tracking-ignored-buffers '("#emacs"))

The cons form that allows you to still track when your nickname is said would look like this:

(setq tracking-ignored-buffers '(("#emacs" circe-highlight-nick-face)))

Do remember, though, that the channel names are given as regular expressions, not simple strings, so "#c" would match not only "#c", but also "#chat". To match only "#c", give the regular expression "#c$".

Auto-track Ignored Channels on Talk

Let's say you've set up your tracking-ignored-buffers as described above to not track certain channels that you usually don't care to follow. But what if occasionally you do want to track those channels, like when you're actively conversing in them? The following code lets you do just that. It advises circe-command-SAY so that if you speak in one of your untracked channels, tracking will automatically be enabled in that channel for the rest of the session.

(defun circe-automatically-enable-tracking ()
  "When talking in an ignored channel, enable tracking for the rest of the session."
  (let ((ignored (tracking-ignored-p (current-buffer) nil)))
    (when ignored
       (setq tracking-ignored-buffers
             (remove ignored tracking-ignored-buffers))
       (message "This buffer will now be tracked."))))

(advice-add #'circe-command-SAY :after #'circe-automatically-enable-tracking)

Tracking: Aggressive shortening

Tracking uses the library shorten.el to shorten buffer names in its status. Shorten can be configured for a number of different styles of string shortening; to configure the shortening style used by tracking, define a piece of around advice on tracking-shorten and dynamically bind shorten's configuration variables around the ad-do-it token. For information on possible ways to configure shorten, refer to the docstrings of shorten-strings and friends, and the commentary in shorten.el. Here is an example config that makes tracking shorten strings more aggressively than the default:

(defun circe-tracking-shorten (orig-fun &rest args)
  (let ((shorten-join-function #'shorten-join-sans-tail))
    (apply orig-fun args)))

(advice-add #'tracking-shorten :around #'circe-tracking-shorten)

Tracking: Send desktop notifications on buffer activity

Use notifications.el to send desktop notifications on buffer activity. This depends on magnars s.el string manipulation library.

In my emacs I had to patch notifications.el using the following patch - if you try this out and get

D-Bus error: "Type of message, `(i)', does not match expected type `(u)'

in your Messages buffer, use the following patch

--- notifications.el	2013-12-02 08:56:29.393620567 +0100
+++ notifications.el.old	2013-12-02 10:53:18.803264520 +0100
@@ -344,7 +344,7 @@
                 notifications-path
                 notifications-interface
                 notifications-close-notification-method
-                    :uint32 id))
+                    :int32 id))
 
 (defvar dbus-debug) ; used in the macroexpansion of dbus-ignore-errors

Here is the code -- ampersand, greater and lesser characters will be replaced by there corresponding entity, as desktop notifications may contain xml/html like markup.

(require 'notifications)
(require 's)

(defvar tom/chatnotification nil
  "ID of the last send desktop notification.")
(defvar tom/lastchatnotification 0
  "Time of the last send notification, seconds since epoch as float")
(defvar tom/lastbufferlist nil
  "The value of tracking-buffers when we last notified")
(defvar tom/chatnotifyintervall 90
  "Minimum delay between chat activity notifications in seconds")

(defun tom/replace-html-chars (text)
  "Replace “<” to “&lt;” and other chars in TEXT."
  (save-restriction      
    (with-temp-buffer
      (insert text)
      (goto-char (point-min))
      (while (search-forward "&" nil t) (replace-match "&amp;" nil t))
      (goto-char (point-min))
      (while (search-forward "<" nil t) (replace-match "&lt;" nil t))
      (goto-char (point-min))
      (while (search-forward ">" nil t) (replace-match "&gt;" nil t))
      (buffer-string))))

(defadvice tracking-add-buffer (after tracking-desktop-notify activate)
  (let ((current-t (float-time))
        (current-bl (s-join "\n" tracking-buffers)))
    ;; min tom/chatnotifyintervall seconds since last delay?
    (if (and (not (eql current-bl "")) (not (eql current-bl tom/lastbufferlist))
             (> (- current-t tom/lastchatnotification) tom/chatnotifyintervall))
        (progn
          ;; delete alst notification id any
          (and tom/chatnotification (notifications-close-notification tom/chatnotification))
          ;; remember time and notify
          (setq  tom/lastchatnotification current-t
                 tom/lastbufferlist current-bl
                 tom/chatnotification (notifications-notify 
                                       :title "Emacs Active Buffers"
                                       :body (tom/replace-html-chars current-bl)
                                       :timeout 750
                                       :desktop-entry "emacs24"
                                       :sound-name "message-new-entry"
                                       :transient))))))

Tracking: Send desktop notifications when nick is mentioned

Take a look at circe-notifications and the configuration example there. It works like erc-desktop-notifications and the code above but tries to avoid redundant messages. For instance, it won't send a notification if the channel it came from is visible, and prevents spam by making sure that a defined amount of time has elapsed before sending another message from any one user.