The first step is probably the customization of ERC. Note that you can only use the custom interface when you have the code loaded which you want to customize. This is especially true for ERC, where a lot of functionality is stuffed away in modules. Only after loading a module will you be able to customize it, for example by typing
M-x customize-group erc.
The next step in configuring ERC is the loading of additional modules. ERC is very extensible and as much as possible is done via hooks (see ErcHooks for the gory details). This means that other people can easily write code that “hooks” into ERC.
If you want to emulate ‘message’-style C-a, you could use something like this:
(defun erc-maybe-bol ()
"Goto the end of `erc-prompt'.
If already there, go to `beginning-of-line'."
(interactive)
(if (and (string-match (concat "^" (regexp-quote (erc-prompt))
" *$")
(buffer-substring-no-properties
(line-beginning-position)
(point)))
(not (bolp)))
(beginning-of-line)
(erc-bol)))Or more simply like this – ZwaX:
(add-hook 'erc-mode-hook '(lambda () (define-key erc-mode-map "\C-a" '(lambda () (interactive) (let ((p (point))) (erc-bol) (if (= p (point)) (beginning-of-line)))))))
If you hit RETs by mistake before being done with the current line, rebind it…
(add-hook 'erc-mode-hook
'(lambda ()
(define-key erc-mode-map "\C-m" 'newline)
(define-key erc-mode-map "\C-c\C-c" 'erc-send-current-line)
(define-key erc-mode-map (kbd "C-<return>")
'erc-send-current-line)
;; uncomment this to make C-g do more intuitive stuff.. when in ERC..
;;(define-key erc-mode-map "\C-c\C-g" 'keyboard-quit)
))Here is a keybinding for C-c C-q to start a query:
(define-key erc-mode-map (kbd "C-c C-q")
(lambda (nick)
(interactive (list (completing-read "Nick: " channel-members)))
(erc-cmd-QUERY nick)))Contributors: LawrenceMitchell, DeepakGoel, AlexSchroeder
This is what I use as a poor man’s menu to the servers I use on a regular basis:
(setq erc-server-history-list '("irc.openprojects.net"
"Geneva.CH.EU.undernet.org"
"irc.lugs.ch"))Then I can connect to other servers by using the fake history.
See ErcStartupFiles for more information on this.
This is what I use to provide channel-specific prompts (like #emacs> in the Emacs channel, and #php> in the PHP channel)
(setq erc-prompt (lambda ()
(if (and (boundp 'erc-default-recipients) (erc-default-target))
(erc-propertize (concat (erc-default-target) ">") 'read-only t 'rear-nonsticky t 'front-nonsticky t)
(erc-propertize (concat "ERC>") 'read-only t 'rear-nonsticky t 'front-nonsticky t))))Bug alert: (erc-default-target) returns nil when the prompt is first displayed. Also, this code is ugly and needs fixing. I’d love to hear about the fix - mail to sacha@free.net.ph
When carrying on a conversation in IRC, most typically you want to address the person or people you last addressed. This will automatically insert the last recipient after the prompt, providing it was less than 3 minutes ago:
(defadvice erc-display-prompt (after conversation-erc-display-prompt activate)
"Insert last recipient after prompt."
(let ((previous
(save-excursion
(if (and (search-backward-regexp (concat "^[^<]*<" erc-nick ">") nil t)
(search-forward-regexp (concat "^[^<]*<" erc-nick ">"
" *\\([^:]*: ?\\)") nil t))
(match-string 1)))))
;; when we got something, and it was in the last 3 mins, put it in
(when (and
previous
(> 180 (time-to-seconds
(time-since (get-text-property 0 'timestamp previous)))))
(set-text-properties 0 (length previous) nil previous)
(insert previous))))
Motivation: by default, ERC automatically splits too long (by the IRC definition) lines, and it also pauses between successive lines if you submit too many. There are some channels (or times) where I don’t want that though. (For example, sometimes I join channels specifically to collaborate on a little bit of code with only friends, and it’s more convenient to just paste into buffers than to use a paste service.)
So, I’ve got these two things:
;; allow some channels to not auto-delay messages. This can probably
;; get you kicked from some channels, so don't use it.
(add-hook 'erc-mode-hook
(lambda ()
(let ((floodable-buffers
'(;; every channel in this list is floodable:
"#bugfunk"
)))
(when (member (buffer-name) floodable-buffers)
(make-local-variable 'erc-server-flood-penalty)
(setq erc-server-flood-penalty 0)))))and
(defun bwm-make-buffer-floodable ()
(make-local-variable 'erc-server-flood-penalty)
(setq erc-server-flood-penalty 0))you know, for friends. BrandonWMaister
Viper and ERC don’t seem to get along by default. Here’s a fix.
;; Ensure that ERC comes up in Insert mode. (add-to-list 'viper-insert-state-mode-list 'erc-mode)
(defun ted-viper-erc-hook ()
"Make RET DTRT when you use Viper and ERC together."
(viper-add-local-keys 'insert-state
`((,(kbd "RET") . erc-send-current-line)))
(viper-add-local-keys 'vi-state
`((,(kbd "RET") . erc-send-current-line))))(add-hook 'erc-mode-hook 'ted-viper-erc-hook)
If you are behind a quirky corporate firewall, the irc server (viz., like, irc.freenode.net) may not be able to look up your hostname or ping you, and hence will soon drop you when you are idle. A solution is to keep your nick active by sending out periodic pings. Here’s a DIRTY implementation (tested only with 1.440 or later versions of ERC cvs) Wiki contributors, please feel free to improve it :)
(add-hook 'erc-server-376-hook '(lambda (&rest args) (keep-alive)))
;;; else flood-quit messages if an accidental disconnection occurs, which will annoy people :) (setq erc-auto-reconnect nil)
(defvar keep-active-active-p nil)
(defun keep-alive () (interactive) ; prevent timer duplication (run-with-timer 15 5 'keep-alive-kick-once) (setq keep-alive-active-p t) (message "Started erbtrain-keep-alive. "))
(defun keep-alive-kick-once ()
(interactive)
;; this seems necessary else ERC won't send the ping after a while.
(let ((erc-flood-protect nil))
(save-window-excursion
(when (buffer-name-p-my "#emacs")
(switch-to-buffer "#emacs")
;; don't send pings unless we are actually alive, to protect flood-quits.
(when (erc-process-alive) (erc-send-command "PING"))
)))) ;;;###autoload
(defun buffer-name-p-my (name &optional matchfn)
"Whether name is the name of a buffer.."
(unless matchfn (setq matchfn 'string=))
(member-if
(lambda (arg)
(funcall matchfn name
(buffer-name arg)))
(buffer-list)))IF you don’t setq erc-auto-reconnect to nil above, then if a channel split logs you off, the pings seem to pile up and upon auto-join, you get an excess flood quit.. which then keeps going on forever..
Sometimes people often type URL’s like:
<nick> Try foo.org!
I want my emacs to be able to buttonize that url (even though it doesn’t contain a www or http, and launch my galeon if i middle-click. I also want emacs to be very strict about the last letter and realize that ! is not part of the url. Finally, I don’t care too much about false positives, but false negatives are a pain. So, here’s the tweak I use:
(setq erc-button-url-regexp
"\\([-a-zA-Z0-9_=!?#$@~`%&*+\\/:;,]+\\.\\)+[-a-zA-Z0-9_=!?#$@~`%&*+\\/:;,]*[-a-zA-Z0-9\\/]")This is probably only meaningful if you use BitlBee with erc. Bitblee rocks.
(defun erc-cmd-PROFILEYAHOO (name) (interactive) (browse-url-galeon (concat "http://profiles.yahoo.com/" (fifth (assoc* name channel-members :test 'equal))) t))
ERC has channel specific Character Encoding support, which is based on the underlying mule system. Exp. say, I want to use utf-8 encoding at all channls but chinese-iso-8bit in a channel named #linuxfire, I can just add the following lines to my .emacs:
(setq erc-server-coding-system '(utf-8 . utf-8)
erc-encoding-coding-alist '(("#linuxfire" . chinese-iso-8bit) ; note that, this is an alist and you can append cons cell for multi-channel
("#foobar" . chinese-iso-8bit)
("#barfoo" . chinese-big5)))
;;要是多个频道讷? -- see above
;;我是这样设置的:
(push '("#linuxfire" . (chinese-iso-8bit . chinese-iso-8bit))
erc-encoding-coding-alist
)
(push '("#jiye" . (chinese-iso-8bit . chinese-iso-8bit))
erc-encoding-coding-alist
)
I put the following in my .emacs so that if I’m mentioned when I’m away, I’ll say “/me is away: <REASON>”, but only once (so as not to get annoying).
Unfortunately ERC doesn’t store the reason, so I had to change erc-cmd-AWAY (is there a hook I could use?) and I couldn’t find any back-from-away-hook so I changed erc-process-away to set my new variables erc-responded-once and erc-away-reason back to nil. Ugly, but it works.
(defvar erc-responded-once nil)
(defvar erc-away-reason nil)
(defun erc-respond-once-if-away (match-type nickuserhost msg)
(if (erc-away-time)
(if (eq match-type 'current-nick)
(unless erc-responded-once
;; erc-send-message escapes /, how to fix?
(erc-send-message (concat "/me is away: " erc-away-reason))
(setq erc-responded-once t)))))
(add-hook 'erc-text-matched-hook 'erc-respond-once-if-away)
(defun erc-process-away (proc away-p)
"Toggle the away status of the user depending on the value of AWAY-P.
If nil, set the user as away.
If non-nil, return from being away."
(let ((sessionbuf (process-buffer proc)))
(when sessionbuf
(with-current-buffer sessionbuf
(when erc-away-nickname
(erc-log (format "erc-process-away: away-nick: %s, away-p: %s"
erc-away-nickname away-p))
(erc-cmd-NICK (if away-p
erc-away-nickname
erc-nick)))
(cond
(away-p
(setq erc-away (current-time)))
(t
(let ((away-time erc-away))
;; away must be set to NIL BEFORE sending anything to prevent
;; an infinite recursion
(setq erc-away nil
erc-responded-once nil
erc-away-reason nil)
(save-excursion
(set-buffer (erc-active-buffer))
(when erc-public-away-p
(erc-send-action
(erc-default-target)
(if away-time
(format "is back (gone for %s)"
(erc-sec-to-time
(erc-time-diff
(erc-emacs-time-to-erc-time away-time)
(erc-current-time))))
"is back")))))))))
(erc-update-mode-line)))
(defun erc-cmd-AWAY (line)
"Mark the user as being away, the reason being indicated by LINE.
If no reason is given, unset away status.
Redefined to store reason"
(when (string-match "^\\s-*\\(.*\\)$" line)
(let ((reason (match-string 1 line)))
(setq erc-away-reason reason)
(erc-log (format "cmd: AWAY: %s" reason))
(erc-server-send
(if (string= reason "")
"AWAY"
(concat "AWAY :" reason))))
t))
Here is another version of the above which uses defadvice to avoid having to redefine the whole functions. Also, there is no problem with escaping of the “/” character.
(defvar erc-responded-once nil)
(defvar erc-away-reason nil)
(defun erc-respond-once-if-away (match-type nickuserhost msg)
(if (erc-away-time)
(if (eq match-type 'current-nick)
(unless erc-responded-once
(erc-send-action (erc-default-target) (concat "is away: " erc-away-reason))
(setq erc-responded-once t)))))
(add-hook 'erc-text-matched-hook 'erc-respond-once-if-away)
(defadvice erc-process-away (after erc-away-reason-clear (proc away-p) activate)
"Clear things"
(unless away-p
(setq erc-responded-once nil
erc-away-reason nil)))
(defadvice erc-cmd-AWAY (after erc-store-reason (line) activate)
"store line"
(when (string-match "^\\s-*\\(.*\\)$" line)
(let ((reason (match-string 1 line)))
(setq erc-away-reason reason))))
This version is tested to work, so (with the original author’s consent) the other version could be removed.
Sometimes I’m working in another application outside Emacs (hard to believe, I know), and so it’s nice to still know when I’ve received a direct message. I thought I’d share the code I use to achieve this:
(defun ysph-erc-privmsg-notify (proc res)
(flet ((rtrim-string (s) (replace-regexp-in-string "\\([[:space:]\n]*$\\)" "" s)))
(let ((channel-buffers (erc-channel-list proc))
(sender (or (car (split-string (erc-response.sender res) "!"))
(erc-response.sender res)))
(target-channel-name (car (erc-response.command-args res)))
(xwindow-class (rtrim-string (shell-command-to-string "stumpish current-window-class"))))
(unless (or (string= xwindow-class "Emacs") ; we are in an emacs frame
(member (get-buffer target-channel-name) channel-buffers)) ; this is a channel message
(progn (notify "Instant message!"
(format "Direct message from %s" sender)
:icon "/home/ysph/.emacs.d/emacs.png"
:timeout 120000
:app "ERC")
nil ; we never want this to interrupt processing
)))))
(add-hook 'erc-server-PRIVMSG-functions 'ysph-erc-privmsg-notify)
This requires Lisp:notify.el, but you can use whatever notification lib is best suited to your setup. I also use stumpish (a part of stumpwm) to determine whether an Emacs frame is focused. I should note, I occasionally ran into issues with stumpish never returning and thus shell-command-to-string never returning, which of course could be resolved by killing stumpish, but a more robust approach would be to introduce an async command with timeout. JosephGay
Instead of stumpish, the following shell script can be used to achieve the same (i.e. output the current window class):
#!/bin/bash WINDOW_ID=$(xprop -root | grep "^_NET_ACTIVE_WINDOW(WINDOW)" | sed -e "s/^[^#]*# *//") CLASS=$(xprop -id $WINDOW_ID | grep "^WM_CLASS(STRING)" | sed -e 's/^[^,]*, //' -e 's/"//g') echo $CLASS
I’ve used something like in in rcircDbusNotification; while the code isn’t pretty it does not use any external commands - although it relies on running on X11 and perhaps some cooperation of the window manager - but then again, most external applications have the same possible issues.
;;; Frame-related function from rcircDbusNotification
(defun fsm-x-active-window ()
"Return the window ID of the current active window in X, as
given by the _NET_ACTIVE_WINDOW of the root window set by the
window-manager, or nil if not able to"
(if (eq (window-system) 'x)
(let ((x-active-window (x-window-property "_NET_ACTIVE_WINDOW" nil "WINDOW" 0 nil t)))
(string-to-number (format "%x00%x" (car x-active-window) (cdr x-active-window))
16))
nil))
(defun fsm-frame-outer-window-id (frame)
"Return the frame outer-window-id property, or nil if FRAME not of the correct type"
(if (framep frame)
(string-to-number
(frame-parameter frame 'outer-window-id))
nil))
(defun fsm-frame-x-active-window-p (frame)
"Check if FRAME is is the X active windows
Returns t if frame has focus or nil if"
(if (framep frame)
(progn
(if (eq (fsm-frame-outer-window-id frame)
(fsm-x-active-window))
t
nil))
nil))
The usage there is not exactly the same but with this functions I’m sure similar things can be accomplished