-
Notifications
You must be signed in to change notification settings - Fork 51
Configuration
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.
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
.
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
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)))
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.
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.
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)))
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.
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))
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)
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}")
(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)
" ")))
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.
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))
(eval-after-load 'circe
'(defun lui-irc-propertize (&rest args)))
;;; 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)))
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.
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)
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)
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.
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")))
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
.
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\\"!")))
Display your own nickname instead of >>> in the channel buffers when you're talking.
(setq circe-format-self-say "<{nick}> {body}")
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))
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)
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.
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.
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 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$".
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 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)
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 “<” and other chars in TEXT."
(save-restriction
(with-temp-buffer
(insert text)
(goto-char (point-min))
(while (search-forward "&" nil t) (replace-match "&" nil t))
(goto-char (point-min))
(while (search-forward "<" nil t) (replace-match "<" nil t))
(goto-char (point-min))
(while (search-forward ">" nil t) (replace-match ">" 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))))))
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.