;; -*- emacs-lisp -*- ;;; mst-erc.el --- emacs IRC Client configuration file ;; Author: Mark Triggs ;; $Id: mst-erc.el,v 1.251 2006/10/02 04:45:01 mst Exp $ ;; This file is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;; These are various configuration hacks for ERC, the Emacs IRC client. Much ;; of the functionality here now comes as standard anyway. ;;; Code: ;; General settings ;; (require 'erc-stamp) (require 'erc-ring) (require 'erc-button) (require 'erc-dcc) (require 'erc-match) (require 'erc-nickserv) ;; (require 'erc-log) (require 'erc-bans) (require 'erc-print-names) (require 'op-friends) (when (fboundp 'erc-services-mode) (erc-services-mode)) (when (try-require 'escreen) (escreen-create-screen) (defvar erc-screen nil) (setq erc-screen escreen-current-screen-number) (let ((jump-to-erc '(lambda () (interactive) (escreen-goto-screen erc-screen) (unless (or (eq major-mode 'erc-mode) (eq major-mode 'erc-dcc-chat-mode)) (switch-to-buffer (find-if (lambda (b) (eq (buffer-mode b) 'erc-mode)) (buffer-list))))))) (define-key global-map [?\e f3] jump-to-erc) (define-key global-map [M-f3] jump-to-erc))) ;; (defadvice erc-cmd-WHOIS (before erc-mst-cmd-WHOIS activate) ;; (setq user (concat user " " user))) (defadvice erc-get-buffer-create (before erc-switch-to-erc-screen activate) (escreen-goto-screen erc-screen)) (defvar doterc-loaded-hook nil "A hook run after mst-erc has been loaded") (defvar erc-mst-suppress-line-regexp nil "A list of regular expressions matching lines that should not be displayed") ;; Timestamps (erc-timestamp-mode 1) ;; Nickserv (erc-nickserv-mode 1) (defstruct erc-network name addresses nick description channels) (defun erc-find-server-network (server) (find-if (lambda (network) (member server (erc-network-addresses network))) erc-networks)) (defvar erc-networks nil "IRC networks") (require 'erc-user-settings) ;; Logging (setq erc-enable-logging t erc-netsplit-regexp "^$" ; foil erc-netsplit.. erc-verbose-dcc nil erc-log-channels-directory "~/.irc/erc/" erc-email-userid "woobar" erc-timestamp-format "[%H:%M %d/%m/%y]" erc-save-buffer-on-part t erc-save-queries-on-quit t erc-generate-log-file-name-function 'erc-mst-log erc-track-exclude '("auth" "nickop" "root") erc-track-exclude-types '("NICK" "QUIT") erc-track-visibility nil erc-log-insert-log-on-open nil) (defun erc-track-find-face (faces) 'erc-mst-track-face) (defface erc-mst-track-face '((t (:foreground "#def"))) "erc tracking") (set-face-attribute 'erc-mst-track-face nil :weight 'bold) ;; User settings (setq erc-fill-column 100 erc-timestamp-right-column 105 erc-current-nick-highlight-type 'nick fill-column 100 erc-auto-query 'bury) ;; enable match mode (erc-match-mode 1) ;; Channel tracking (when (try-require 'erc-track) (erc-track-mode 1)) ;; Key bindings ;; (define-key erc-mode-map (kbd "C-c C-q") 'erc-mst-nuke-server) (define-key erc-mode-map (kbd "M-*") (lambda (&optional arg) (interactive "P") (insert-pair arg ?* ?*))) (defun erc-mst-nuke-server () (interactive) "Quit the current server and close all its windows" (save-some-buffers t (lambda () (when (and (eq major-mode 'erc-mode) buffer-file-name) t))) (let* ((quit-buffer (erc-server-buffer)) (reason (read-string "Reason?: "))) (erc-cmd-QUIT reason) (mapc (lambda (current) (set-buffer current) (when (equal (erc-server-buffer) quit-buffer) (kill-buffer nil))) (remove-if (lambda (x) (equal quit-buffer x)) (erc-buffer-list))) ;; Finally, kill the server buffer (kill-buffer quit-buffer))) (define-key erc-mode-map (kbd "M-.") (lambda () (interactive) (ignore-errors (erc-display-line-1 " " (current-buffer))) (recenter 1))) (define-key erc-mode-map (kbd "C-a") 'erc-maybe-bol) (define-key erc-mode-map (kbd "C-c b") 'erc-iswitchb) (defvar last-screen 0 "The last screen we were using") (defun erc-mst-track-next () (interactive) (unless (member (current-buffer) (erc-buffer-list)) (setq last-screen escreen-current-screen-number)) (let ((modified-channels erc-modified-channels-alist)) (if (null modified-channels) (and (escreen-goto-screen last-screen)) (escreen-goto-screen erc-screen) ;; by switching to the erc screen, we may have already selected one of ;; the modified buffers. (unless (assoc (current-buffer) modified-channels) (let ((erc-track-last-non-erc-buffer nil)) (erc-track-switch-buffer 1)))))) (define-key global-map (kbd "M-`") 'erc-mst-track-next) ;; who needs the scroll wheel anyway? (define-key global-map [mouse-4] 'erc-mst-track-next) ;; Hooks ;; ;; Munge input (add-hook 'erc-send-pre-hook 'erc-mst-munge-input) ;; Disable tracking for messages from the announced server (add-hook 'erc-server-004-hook 'erc-mst-server-004) ;; Auto-join channels (add-hook 'erc-after-connect 'erc-mst-autojoin) (defadvice erc-auto-query (around keep-buffer activate) (save-window-excursion ad-do-it)) ;; notify of MSGs when not looking at erc (add-hook 'erc-server-PRIVMSG-functions (lambda (proc parsed) (when (or (and (string= (erc-current-nick) (car (erc-response.command-args parsed))) (not (member (car (erc-parse-user (erc-response.sender parsed))) erc-track-exclude))) (string= "#bitlbee" (car (erc-response.command-args parsed)))) ;; Don't disturb me if I'm using emacs (when (not (string= "t\n" (shell-command-to-string (concat "sawfish-client -e " "'(string-match \"NULL\\|Erc\\|emacs\\|Gnus\"" "(if (input-focus) (window-name (input-focus)) \"NULL\"))'")))) (start-process "osd-print" nil "/home/mst/.bin/osd-print" (format "irc: %s" (erc-parse-user (erc-response.sender parsed)))))) nil) nil nil) ;; "end of names" (defun erc-mst-get-password (file) "Grab the first line of a file (used for passwords)" (with-temp-buffer (insert-file file) (remove (string-to-char "\n") (buffer-string)))) (defun erc-mst-join (proc parsed) "Joining a channel" (let* ((chnl (erc-response.contents parsed)) (sndr (erc-parse-user (erc-response.sender parsed))) (nick (car sndr))) (when (string= (erc-current-nick) nick) ;; I have joined the channel ;; If this is bitbee, login to stuff (when (and (string= chnl "#bitlbee") (string= (buffer-name (erc-server-buffer)) "localhost:6667")) (erc-send-command (concat "PRIVMSG #bitlbee :identify " (erc-mst-get-password "~/.pass/bitlbeepass")) nil)))) nil) (add-hook 'erc-server-JOIN-functions 'erc-mst-join t nil) (when (try-require 'erc-fill) (add-hook 'erc-mode-hook (lambda () (erc-fill-mode 1)))) (add-hook 'erc-mode-hook (lambda () ;; find-file from erc should default to my home-dir (setq default-directory "~/") (when (fboundp 'erc-ring-mode) (erc-ring-mode 1)) (erc-button-mode 1) (set (make-variable-buffer-local 'coding-system-for-write) 'emacs-mule) ;; turn off fill mode in erc - erc has its own fill features (auto-fill-mode 0) (when (fboundp 'filladapt-mode) (filladapt-mode 0)))) ;; Functions ;; (defun erc-mst-log (buffer target nick server port) (downcase (format "%s/%s-%s.log" erc-log-channels-directory server target))) (defun erc-mst-autojoin (&optional server nick) "Autojoin any channels for the current server" (interactive) (let* ((buffer (car (split-string (buffer-name (erc-server-buffer)) ":"))) (channels (erc-network-channels (erc-find-server-network buffer)))) (mapc (lambda (x) (cond ((string-match "^#" x) (erc-cmd-JOIN (car (split-string x)) ;; include the key if it was given (cadr (split-string x)))) (t (erc-cmd-QUERY x)))) channels))) (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))) (defalias 'erc-cmd-TALKTO 'erc-cmd-QUERY) (defun erc-cmd-CHOPS () "Request chanop to op me" (erc-send-command (format "PRIVMSG %s :OP %s %s" (erc-mst-channel-service) (erc-default-target) (erc-current-nick))) t) (defun erc-cmd-ID () "Identify to nickop" (erc-send-command (concat (format "PRIVMSG %s :IDENTIFY " (erc-mst-nick-service)) (erc-mst-get-password "~/.pass/ircpass"))) t) (defun erc-cmd-GHOST (nick) "kill a ghost" (unless (null nick) (erc-send-command (concat "PRIVMSG Nickop@austnet.org :KILL " nick " " (erc-mst-get-password "~/.pass/ircpass"))) t)) (defun erc-mst-channel-service () (if (eq (erc-network) 'Astrolink) "ChanServ" "Chanop")) (defun erc-mst-nick-service () (if (eq (erc-network) 'Astrolink) "NickServ" "Nickop@austnet.org")) (defun erc-mst-select () (interactive) (let* ((table (mapcan (lambda (network) (mapcar (lambda (s) (cons (if (erc-network-description network) (format "%s (%s)" s (erc-network-description network)) s) (list s network))) (erc-network-addresses network))) erc-networks))) (destructuring-bind (selection network) (cdr (assoc (completing-read "Server? " table nil t) table)) (destructuring-bind (server &optional port) (split-string selection ":") (with-current-buffer (erc server (or port 6667) (erc-network-nick network) nil t) (set (make-local-variable 'network-name) (erc-network-name network))))))) (defun erc-mst-current-network-name () (with-current-buffer (erc-server-buffer) (if (boundp 'network-name) network-name nil))) ;; (defun erc-auto-query (proc parsed) ;; "Put this on `erc-server-PRIVMSG-hook'." ;; (when erc-auto-query ;; (let* ((nick (car (erc-parse-user (aref parsed 1)))) ;; (old-buffer erc-active-buffer) ;; (target (aref parsed 2)) ;; (msg (aref parsed 3)) ;; (query (if (not erc-query-on-unjoined-chan-privmsg) ;; nick ;; (if (string= (erc-downcase target) ;; (erc-downcase (erc-current-nick))) ;; nick ;; target)))) ;; (and (not (erc-ignored-user-p (aref parsed 1))) ;; (or erc-query-on-unjoined-chan-privmsg ;; (string= target (erc-current-nick))) ;; (not (erc-get-buffer query proc)) ;; (not (erc-is-message-ctcp-and-not-action-p msg)) ;; (erc-cmd-QUERY query) ;; (when (not erc-auto-query-jump) ;; (let ((faces (erc-faces-in (buffer-string)))) ;; (setq erc-modified-channels-alist ;; (cons (cons (current-buffer) ;; (cons 1 (erc-track-find-face faces))) ;; erc-modified-channels-alist))) ;; (switch-to-buffer old-buffer) ;; (erc-modified-channels-display)) ;; nil)))) (defvar erc-input-replacements nil "A list of pairs of the form (old . new) which will be used when substituting input lines. For example: '((\"apple\" . \"orange\"))") (defun erc-mst-replace-word-in-string (word replacement s) (let ((word-delim "[\\.-,:\"!' ]")) (replace-regexp-in-string (format "%s %s" word-delim word) replacement (replace-regexp-in-string (format "^%s%s" word word-delim) replacement s)))) (defun nuke-trailing-whitespace (s) (subseq s 0 (loop for i from (1- (length s)) downto 0 while (member (aref s i) (list (string-to-char " ") (string-to-char "\n"))) finally (return (1+ i))))) (defun erc-mst-munge-input (line) "Modify the line about to be sent" (setq str (copy-seq line)) (setq str (nuke-trailing-whitespace str)) (with-temp-buffer (insert (concat " "str " ")) (dolist (pair erc-input-replacements) (goto-char (point-min)) (while (search-forward (concat " " (car pair) " ") nil t) (replace-match (concat " " (cdr pair) " ") nil t))) (goto-char (point-min)) (delete-horizontal-space) (goto-char (point-max)) (delete-horizontal-space) (setq str (buffer-string))) ;; If we're using bitlbee, munge smileys. (let ((server (with-current-buffer (erc-server-buffer) erc-session-server))) (when (string= server "localhost") (mapc #'(lambda (pair) (let ((old (car pair)) (new (cdr pair))) (setq str (replace-regexp-in-string old new str)))) '((":o)" . ":)") (":o(" . ":(") (":oP" . ":P"))))) ;; Confirm when sending something that looks like it might be the erc prompt, ;; an intended command, stupidness or if something has been changed (above) (let ((case-fold-search nil) (prompt-regexp (reduce (lambda (x y) (concat x ".*" y)) (mapcar (lambda (x) (format "%c" x)) (string-to-list (erc-string-no-properties erc-prompt)))))) (when (or (string-match (concat prompt-regexp "\\|" "^ +/" "\\|" "^ *[^ ] *$") str) (and (not (string-match "^\\.\\.\\." str)) (string-match "^\\([^w ]\\)\\1\\1+" str))) (setq str (read-string "Please confirm: " str))))) ;; Pretty erc! ;; (when (not (featurep 'color-theme)) (try-require 'color-theme)) ;; Show idle time in whois ;; (defadvice erc-cmd-WHOIS (before erc-mst-cmd-WHOIS activate) ;; (setq user (concat user " " user))) (defun erc-mst-server-004 (proc parsed) "Disable tracking for messages from the announced server" (let ((server-name (aref parsed 3))) (setq erc-track-exclude (cons server-name erc-track-exclude))) nil) ;; helper defun to unfill lines that have been cut from elsewhere - Damo (defun erc-unfill () "Unfill the region after the prompt. Intended to be called just before you send a line" (interactive) (save-excursion (end-of-buffer) (goto-char (previous-single-property-change (point) 'erc-prompt)) (while (search-forward "\n" nil t) (delete-backward-char 1) (just-one-space)))) (defun erc-cmd-SPOOK () "Spook 'em" (erc-cmd-TOPIC (remove (string-to-char "\n") (with-temp-buffer (spook) (buffer-string))))) (defun erc-mst-save (beg end) "Save a region to the kill ring removing timestamps. If a prefix argument is used, remove nicknames too." (interactive "r") (flet ((message (&rest args) nil)) (kill-ring-save beg end) (with-temp-buffer (yank) (goto-char (point-min)) (end-of-line) (while (not (eobp)) (backward-char 1) (when (eq (get-face-at) 'erc-timestamp-face) (backward-up-list) (kill-sexp) (delete-horizontal-space)) (next-line) (end-of-line)) (kill-ring-save (point-min) (point-max))))) (define-key erc-mode-map (kbd "C-M-w") 'erc-mst-save) (defun erc-dcc-open-network-stream (procname buffer addr port entry) (open-network-stream procname buffer addr port)) (defadvice erc-display-line (around erc-mst-drop-lines activate) "A *really* blunt instrument for not showing certain lines" (unless (some #'(lambda (regexp) (string-match regexp string)) erc-mst-suppress-line-regexp) ad-do-it)) ;; This should always be at the end (when (boundp 'doterc-loaded-hook) (run-hooks 'doterc-loaded-hook)) (add-hook 'erc-dcc-chat-mode-hook (lambda () (auto-fill-mode -1) (filladapt-mode -1))) ;; Pretty-print nickname lists (used in conjunction with erc-print-names.el) (setq erc-p-n-filters '(erc-mst-sort-names erc-mst-show-nick-modes)) (setq erc-p-n-format-nicks-column-width 12) (defun erc-mst-sort-names (name-list) (let ((ops '()) (plebs '()) (voices '())) (mapc #'(lambda (entry) (cond ((erc-channel-user-op (caddr entry)) (push entry ops)) ((erc-channel-user-voice (caddr entry)) (push entry voices)) (t (push entry plebs)))) name-list) (mapcan #'(lambda (group) (sort group (lambda (entry1 entry2) (string< (car entry1) (car entry2))))) (list ops voices plebs)))) (defun erc-mst-show-nick-modes (name-list) (mapcar (lambda (entry) (cons (concat (cond ((erc-channel-user-op (caddr entry)) "@") ((erc-channel-user-voice (caddr entry)) "+") (t "")) (car entry)) (cdr entry))) name-list)) (defun erc-cmd-REFRESHUSERS () (with-current-buffer (erc-get-buffer (erc-default-target)) (setq erc-channel-users (make-hash-table :test 'equal)))) (set (make-local-variable 'pcomplete-default-completion-function) (lambda () (pcomplete-here (pcomplete-erc-nicks-from-bbdb)))) (defun get-nick-bbdb-record (nick) (find-if (lambda (record) (member nick (split-string (bbdb-get-field record 'screen-name) "[, ]" t))) (bbdb-records))) (defun nick-to-name (nick) (let ((record (get-nick-bbdb-record nick))) (if record (list (bbdb-get-field record 'nick) (car (split-string (bbdb-record-name record)))) nil))) (defun pcomplete-erc-nicks (&optional postfix) "Add people's first names to ERC's completion possibilities" (let ((users (erc-get-channel-user-list))) (if erc-pcomplete-order-nickname-completions (setq users (erc-sort-channel-users-by-activity users))) (let ((nicks (mapcan (lambda (user) (let ((nick (erc-server-user-nickname (car user)))) (cons nick (nick-to-name nick)))) users))) (mapcar (lambda (nick) (concat nick postfix)) nicks)))) ;; Send mail to people straight from ERC. (push (cons "Mail" '(let ((record (get-nick-bbdb-record nick))) (if record (bbdb-send-mail (get-nick-bbdb-record nick)) (error "No suitable record for %s" nick)))) erc-nick-popup-alist) (cond ((and (featurep 'color-theme) window-system) ;; Tweak colours if we are running in a window system (set-face-foreground 'erc-timestamp-face "gray80") (set-face-foreground 'erc-direct-msg-face "#abc") (set-face-foreground 'erc-prompt-face "#def") (set-face-background 'erc-prompt-face (face-background 'default)) (set-face-foreground 'erc-default-face "#abc") (set-face-foreground 'erc-keyword-face "blue") ;; (set-face-foreground 'erc-input-face "#def") (set-face-foreground 'erc-input-face "gray45") (set-face-foreground 'erc-nick-msg-face "#def") (setq erc-nick-msg-face 'bold) (set-face-foreground 'erc-notice-face "#5080AA") (set-face-attribute 'erc-notice-face nil :weight 'normal)) (t (set-face-foreground 'erc-direct-msg-face "white") (set-face-foreground 'erc-default-face "white") (set-face-foreground 'erc-input-face "white") (set-face-foreground 'erc-timestamp-face "white") (setq erc-nick-msg-face 'bold) (set-face-foreground 'erc-nick-msg-face "white") (set-face-foreground 'erc-notice-face "white"))) (provide 'mst-erc)