;;; erc.el --- An Emacs Internet Relay Chat client -*- lexical-binding:t -*- ;; Copyright (C) 1997-2022 Free Software Foundation, Inc. ;; Author: Alexander L. Belikoff ;; Maintainer: Amin Bandali , F. Jason Park ;; Contributors: Sergey Berezin (sergey.berezin@cs.cmu.edu), ;; Mario Lang (mlang@delysid.org), ;; Alex Schroeder (alex@gnu.org) ;; Andreas Fuchs (afs@void.at) ;; Gergely Nagy (algernon@midgard.debian.net) ;; David Edmondson (dme@dme.org) ;; Michael Olson (mwolson@gnu.org) ;; Kelvin White (kwhite@gnu.org) ;; Version: 5.4.1 ;; Package-Requires: ((emacs "27.1") (compat "28.1.2.0")) ;; Keywords: IRC, chat, client, Internet ;; URL: https://www.gnu.org/software/emacs/erc.html ;; This file is part of GNU Emacs. ;; GNU Emacs 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 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs 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. If not, see . ;;; Commentary: ;; ERC is a powerful, modular, and extensible IRC client for Emacs. ;; For more information, visit the ERC page at ;; . ;; Configuration: ;; Use M-x customize-group RET erc RET to get an overview ;; of all the variables you can tweak. ;; Usage: ;; To connect to an IRC server, do ;; ;; M-x erc RET ;; ;; or ;; ;; M-x erc-tls RET ;; ;; to connect over TLS (encrypted). Once you are connected to a ;; server, you can use C-h m or have a look at the ERC menu. ;;; Code: (load "erc-loaddefs" 'noerror 'nomessage) (require 'erc-networks) (require 'erc-goodies) (require 'erc-backend) (require 'cl-lib) (require 'format-spec) (require 'pp) (require 'thingatpt) (require 'auth-source) (require 'time-date) (require 'iso8601) (eval-when-compile (require 'subr-x) (require 'url-parse)) (defconst erc-version "5.4.1" "This version of ERC.") (defvar erc-official-location "https://www.gnu.org/software/emacs/erc.html (mailing list: emacs-erc@gnu.org)" "Location of the ERC client on the Internet.") ;; Map each :package-version to the associated Emacs version. ;; (This eliminates the need for explicit :version keywords on the ;; custom definitions.) (add-to-list 'customize-package-emacs-version-alist '(ERC ("5.2" . "22.1") ("5.3" . "23.1") ("5.4" . "28.1") ("5.4.1" . "29.1"))) (defgroup erc nil "Emacs Internet Relay Chat client." :link '(url-link "https://www.gnu.org/software/emacs/erc.html") :link '(custom-manual "(erc) Top") :prefix "erc-" :group 'applications) (defgroup erc-buffers nil "Creating new ERC buffers." :group 'erc) (defgroup erc-display nil "Settings for how various things are displayed." :group 'erc) (defgroup erc-mode-line-and-header nil "Displaying information in the mode-line and header." :group 'erc-display) (defgroup erc-ignore nil "Ignoring certain messages." :group 'erc) (defgroup erc-lurker nil "Hide specified message types sent by lurkers." :version "24.3" :group 'erc-ignore) (defgroup erc-query nil "Using separate buffers for private discussions." :group 'erc) (defgroup erc-quit-and-part nil "Quitting and parting channels." :group 'erc) (defgroup erc-paranoia nil "Know what is sent and received; control the display of sensitive data." :group 'erc) (defgroup erc-scripts nil "Running scripts at startup and with /LOAD." :group 'erc) ;; Forward declarations (defvar erc-message-parsed) (defvar tabbar--local-hlf) (defvar motif-version-string) (defvar gtk-version-string) ;; tunable connection and authentication parameters (defcustom erc-server nil "IRC server to use if one is not provided. See function `erc-compute-server' for more details on connection parameters and authentication." :group 'erc :type '(choice (const :tag "None" nil) (string :tag "Server"))) (defcustom erc-port nil "IRC port to use if not specified. This can be either a string or a number." :group 'erc :type '(choice (const :tag "None" nil) (integer :tag "Port number") (string :tag "Port string"))) (defcustom erc-nick nil "Nickname to use if one is not provided. This can be either a string, or a list of strings. In the latter case, if the first nick in the list is already in use, other nicks are tried in the list order. See function `erc-compute-nick' for more details on connection parameters and authentication." :group 'erc :type '(choice (const :tag "None" nil) (string :tag "Nickname") (repeat (string :tag "Nickname")))) (defcustom erc-nick-uniquifier "`" "The string to append to the nick if it is already in use." :group 'erc :type 'string) (defcustom erc-try-new-nick-p t "Non-nil means attempt to connect with another nickname if nickname unavailable. You can manually set another nickname with the /NICK command." :group 'erc :type 'boolean) (defcustom erc-user-full-name nil "User full name. This can be either a string or a function to call. See function `erc-compute-full-name' for more details on connection parameters and authentication." :group 'erc :type '(choice (const :tag "No name" nil) (string :tag "Name") (function :tag "Get from function")) :set (lambda (sym val) (set sym (if (functionp val) (funcall val) val)))) (defcustom erc-rename-buffers t "Non-nil means rename buffers with network name, if available." :version "24.5" :group 'erc :type 'boolean) ;; For the sake of compatibility, an ID will be created on the user's ;; behalf when `erc-rename-buffers' is nil and one wasn't provided. ;; The name will simply be that of the buffer, usually SERVER:PORT. ;; This violates the policy of treating provided IDs as gospel, but ;; it'll have to do for now. (make-obsolete-variable 'erc-rename-buffers "old behavior when t now permanent" "29.1") (defvar erc-password nil "Password to use when authenticating to an IRC server interactively. This variable only exists for legacy reasons. It's not customizable and is limited to a single server password. Users looking for similar functionality should consider auth-source instead. See info node `(auth) Top' and info node `(erc) Connecting'.") (make-obsolete-variable 'erc-password "use auth-source instead" "29.1") (defcustom erc-user-mode "+i" ;; +i "Invisible". Hides user from global /who and /names. "Initial user modes to be set after a connection is established." :group 'erc :type '(choice (const nil) string function) :version "28.1") (defcustom erc-prompt-for-password t "Ask for a server password when invoking `erc-tls' interactively." :group 'erc :type 'boolean) (defcustom erc-warn-about-blank-lines t "Warn the user if they attempt to send a blank line." :group 'erc :type 'boolean) (defcustom erc-send-whitespace-lines nil "If set to non-nil, send lines consisting of only whitespace." :group 'erc :type 'boolean) (defcustom erc-inhibit-multiline-input nil "When non-nil, conditionally disallow input consisting of multiple lines. Issue an error when the number of input lines submitted for sending exceeds this value. The value t means disallow more than 1 line of input." :package-version '(ERC . "5.4.1") ; FIXME match to next release :group 'erc :type '(choice integer boolean)) (defcustom erc-ask-about-multiline-input nil "Whether to ask to ignore `erc-inhibit-multiline-input' when tripped." :package-version '(ERC . "5.4.1") ; FIXME match to next release :group 'erc :type 'boolean) (defcustom erc-prompt-hidden ">" "Text to show in lieu of the prompt when hidden." :package-version '(ERC . "5.4.1") ; FIXME increment on next ELPA release :group 'erc-display :type 'string) (defcustom erc-hide-prompt t "If non-nil, hide input prompt upon disconnecting. To unhide, type something in the input area. Once revealed, a prompt remains unhidden until the next disconnection. Channel prompts are unhidden upon rejoining. See `erc-unhide-query-prompt' for behavior concerning query prompts." :package-version '(ERC . "5.4.1") ; FIXME increment on next ELPA release :group 'erc-display :type '(choice (const :tag "Always hide prompt" t) (set (const server) (const query) (const channel)))) (defcustom erc-unhide-query-prompt nil "When non-nil, always reveal query prompts upon reconnecting. Otherwise, prompts in a connection's query buffers remain hidden until the user types in the input area or a new message arrives from the target." :package-version '(ERC . "5.4.1") ; FIXME increment on next ELPA release :group 'erc-display ;; Extensions may one day offer a way to discover whether a target ;; is online. When that happens, this can be expanded accordingly. :type 'boolean) ;; tunable GUI stuff (defcustom erc-show-my-nick t "If non-nil, display one's own nickname when sending a message. If non-nil, \"\" will be shown. If nil, only \"> \" will be shown." :group 'erc-display :type 'boolean) (define-widget 'erc-message-type 'set "A set of standard IRC Message types." :args '((const "JOIN") (const "KICK") (const "NICK") (const "PART") (const "QUIT") (const "MODE") (repeat :inline t :tag "Others" (string :tag "IRC Message Type")))) (defcustom erc-hide-list nil "A global list of IRC message types to hide. A typical value would be \(\"JOIN\" \"PART\" \"QUIT\")." :group 'erc-ignore :type 'erc-message-type) (defcustom erc-network-hide-list nil "A list of IRC networks to hide message types from. A typical value would be \((\"Libera.Chat\" \"MODE\") \(\"OFTC\" \"JOIN\" \"QUIT\"))." :version "25.1" :group 'erc-ignore :type 'erc-message-type) (defcustom erc-channel-hide-list nil "A list of IRC channels to hide message types from. A typical value would be \((\"#emacs\" \"QUIT\" \"JOIN\") \(\"#erc\" \"NICK\")." :version "25.1" :group 'erc-ignore :type 'erc-message-type) (defcustom erc-disconnected-hook nil "Run this hook with arguments (NICK IP REASON) when disconnected. This happens before automatic reconnection. Note, that `erc-server-QUIT-functions' might not be run when we disconnect, simply because we do not necessarily receive the QUIT event." :group 'erc-hooks :type 'hook) (defcustom erc-complete-functions nil "These functions get called when the user hits TAB in ERC. Each function in turn is called until one returns non-nil to indicate it has handled the input." :group 'erc-hooks :type 'hook) (defcustom erc-join-hook nil "Hook run when we join a channel. Hook functions are called without arguments, with the current buffer set to the buffer of the new channel. See also `erc-server-JOIN-functions', `erc-part-hook'." :group 'erc-hooks :type 'hook) (defcustom erc-quit-hook nil "Hook run when processing a quit command directed at our nick. The hook receives one argument, the current PROCESS. See also `erc-server-QUIT-functions' and `erc-disconnected-hook'." :group 'erc-hooks :type 'hook) (defcustom erc-part-hook nil "Hook run when processing a PART message directed at our nick. The hook receives one argument, the current BUFFER. See also `erc-server-QUIT-functions', `erc-quit-hook' and `erc-disconnected-hook'." :group 'erc-hooks :type 'hook) (defcustom erc-kick-hook nil "Hook run when processing a KICK message directed at our nick. The hook receives one argument, the current BUFFER. See also `erc-server-PART-functions' and `erc-part-hook'." :group 'erc-hooks :type 'hook) (defcustom erc-nick-changed-functions nil "List of functions run when your nick was successfully changed. Each function should accept two arguments, NEW-NICK and OLD-NICK." :group 'erc-hooks :type 'hook) (defcustom erc-connect-pre-hook '(erc-initialize-log-marker) "Hook called just before `erc' calls `erc-connect'. Functions are passed a buffer as the first argument." :group 'erc-hooks :type 'hook) (defvar-local erc-channel-users nil "Hash table of members in the current channel. It associates nicknames with cons cells of the form: \(USER . MEMBER-DATA) where USER is a pointer to an erc-server-user struct, and MEMBER-DATA is a pointer to an erc-channel-user struct.") (defvar-local erc-server-users nil "Hash table of users on the current server. It associates nicknames with `erc-server-user' struct instances.") (defconst erc--casemapping-rfc1459 (make-translation-table '((?\[ . ?\{) (?\] . ?\}) (?\\ . ?\|) (?~ . ?^)) (mapcar (lambda (c) (cons c (+ c 32))) "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) (defconst erc--casemapping-rfc1459-strict (make-translation-table '((?\[ . ?\{) (?\] . ?\}) (?\\ . ?\|)) (mapcar (lambda (c) (cons c (+ c 32))) "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) (defun erc-add-server-user (nick user) "This function is for internal use only. Adds USER with nickname NICK to the `erc-server-users' hash table." (erc-with-server-buffer (puthash (erc-downcase nick) user erc-server-users))) (defun erc-remove-server-user (nick) "This function is for internal use only. Removes the user with nickname NICK from the `erc-server-users' hash table. This user is not removed from the `erc-channel-users' lists of other buffers. See also: `erc-remove-user'." (erc-with-server-buffer (remhash (erc-downcase nick) erc-server-users))) (defun erc-change-user-nickname (user new-nick) "This function is for internal use only. Changes the nickname of USER to NEW-NICK in the `erc-server-users' hash table. The `erc-channel-users' lists of other buffers are also changed." (let ((nick (erc-server-user-nickname user))) (setf (erc-server-user-nickname user) new-nick) (erc-with-server-buffer (remhash (erc-downcase nick) erc-server-users) (puthash (erc-downcase new-nick) user erc-server-users)) (dolist (buf (erc-server-user-buffers user)) (if (buffer-live-p buf) (with-current-buffer buf (let ((cdata (erc-get-channel-user nick))) (remhash (erc-downcase nick) erc-channel-users) (puthash (erc-downcase new-nick) cdata erc-channel-users))))))) (defun erc-remove-channel-user (nick) "This function is for internal use only. Removes the user with nickname NICK from the `erc-channel-users' list for this channel. If this user is not in the `erc-channel-users' list of any other buffers, the user is also removed from the server's `erc-server-users' list. See also: `erc-remove-server-user' and `erc-remove-user'." (let ((channel-data (erc-get-channel-user nick))) (when channel-data (let ((user (car channel-data))) (setf (erc-server-user-buffers user) (delq (current-buffer) (erc-server-user-buffers user))) (remhash (erc-downcase nick) erc-channel-users) (if (null (erc-server-user-buffers user)) (erc-remove-server-user nick)))))) (defun erc-remove-user (nick) "This function is for internal use only. Removes the user with nickname NICK from the `erc-server-users' list as well as from all `erc-channel-users' lists. See also: `erc-remove-server-user' and `erc-remove-channel-user'." (let ((user (erc-get-server-user nick))) (when user (let ((buffers (erc-server-user-buffers user))) (dolist (buf buffers) (if (buffer-live-p buf) (with-current-buffer buf (remhash (erc-downcase nick) erc-channel-users) (run-hooks 'erc-channel-members-changed-hook))))) (erc-remove-server-user nick)))) (defun erc-remove-channel-users () "This function is for internal use only. Removes all users in the current channel. This is called by `erc-server-PART' and `erc-server-QUIT'." (when (and erc-server-connected (erc-server-process-alive) (hash-table-p erc-channel-users)) (maphash (lambda (nick _cdata) (erc-remove-channel-user nick)) erc-channel-users) (clrhash erc-channel-users))) (defun erc-channel-user-owner-p (nick) "Return non-nil if NICK is an owner of the current channel." (and nick (hash-table-p erc-channel-users) (let ((cdata (erc-get-channel-user nick))) (and cdata (cdr cdata) (erc-channel-user-owner (cdr cdata)))))) (defun erc-channel-user-admin-p (nick) "Return non-nil if NICK is an admin in the current channel." (and nick (hash-table-p erc-channel-users) (let ((cdata (erc-get-channel-user nick))) (and cdata (cdr cdata) (erc-channel-user-admin (cdr cdata)))))) (defun erc-channel-user-op-p (nick) "Return non-nil if NICK is an operator in the current channel." (and nick (hash-table-p erc-channel-users) (let ((cdata (erc-get-channel-user nick))) (and cdata (cdr cdata) (erc-channel-user-op (cdr cdata)))))) (defun erc-channel-user-halfop-p (nick) "Return non-nil if NICK is a half-operator in the current channel." (and nick (hash-table-p erc-channel-users) (let ((cdata (erc-get-channel-user nick))) (and cdata (cdr cdata) (erc-channel-user-halfop (cdr cdata)))))) (defun erc-channel-user-voice-p (nick) "Return non-nil if NICK has voice in the current channel." (and nick (hash-table-p erc-channel-users) (let ((cdata (erc-get-channel-user nick))) (and cdata (cdr cdata) (erc-channel-user-voice (cdr cdata)))))) (defun erc-get-channel-user-list () "Return a list of users in the current channel. Each element of the list is of the form (USER . CHANNEL-DATA), where USER is an erc-server-user struct, and CHANNEL-DATA is either nil or an erc-channel-user struct. See also: `erc-sort-channel-users-by-activity'." (let (users) (if (hash-table-p erc-channel-users) (maphash (lambda (_nick cdata) (setq users (cons cdata users))) erc-channel-users)) users)) (defun erc-get-server-nickname-list () "Return a list of known nicknames on the current server." (erc-with-server-buffer (let (nicks) (when (hash-table-p erc-server-users) (maphash (lambda (_n user) (setq nicks (cons (erc-server-user-nickname user) nicks))) erc-server-users) nicks)))) (defun erc-get-channel-nickname-list () "Return a list of known nicknames on the current channel." (let (nicks) (when (hash-table-p erc-channel-users) (maphash (lambda (_n cdata) (setq nicks (cons (erc-server-user-nickname (car cdata)) nicks))) erc-channel-users) nicks))) (defun erc-get-server-nickname-alist () "Return an alist of known nicknames on the current server." (erc-with-server-buffer (let (nicks) (when (hash-table-p erc-server-users) (maphash (lambda (_n user) (setq nicks (cons (cons (erc-server-user-nickname user) nil) nicks))) erc-server-users) nicks)))) (defun erc-get-channel-nickname-alist () "Return an alist of known nicknames on the current channel." (let (nicks) (when (hash-table-p erc-channel-users) (maphash (lambda (_n cdata) (setq nicks (cons (cons (erc-server-user-nickname (car cdata)) nil) nicks))) erc-channel-users) nicks))) (defun erc-sort-channel-users-by-activity (list) "Sort LIST such that users which have spoken most recently are listed first. LIST must be of the form (USER . CHANNEL-DATA). See also: `erc-get-channel-user-list'." (sort list (lambda (x y) (when (and (cdr x) (cdr y)) (let ((tx (erc-channel-user-last-message-time (cdr x))) (ty (erc-channel-user-last-message-time (cdr y)))) (and tx (or (not ty) (time-less-p ty tx)))))))) (defun erc-sort-channel-users-alphabetically (list) "Sort LIST so that users' nicknames are in alphabetical order. LIST must be of the form (USER . CHANNEL-DATA). See also: `erc-get-channel-user-list'." (sort list (lambda (x y) (when (and (cdr x) (cdr y)) (let ((nickx (downcase (erc-server-user-nickname (car x)))) (nicky (downcase (erc-server-user-nickname (car y))))) (and nickx (or (not nicky) (string-lessp nickx nicky)))))))) (defvar-local erc-channel-topic nil "A topic string for the channel. Should only be used in channel-buffers.") (defvar-local erc-channel-modes nil "List of strings representing channel modes. E.g. (\"i\" \"m\" \"s\" \"b Quake!*@*\") \(not sure the ban list will be here, but why not)") (defvar-local erc-insert-marker nil "The place where insertion of new text in erc buffers should happen.") (defvar-local erc-input-marker nil "The marker where input should be inserted.") (defun erc-string-no-properties (string) "Return a copy of STRING will all text-properties removed." (let ((newstring (copy-sequence string))) (set-text-properties 0 (length newstring) nil newstring) newstring)) (defcustom erc-prompt "ERC>" "Prompt used by ERC. Trailing whitespace is not required." :group 'erc-display :type '(choice string function)) (defun erc-prompt () "Return the input prompt as a string. See also the variable `erc-prompt'." (let ((prompt (if (functionp erc-prompt) (funcall erc-prompt) erc-prompt))) (if (> (length prompt) 0) (concat prompt " ") prompt))) (defcustom erc-command-indicator nil "Indicator used by ERC for showing commands. If non-nil, this will be used in the ERC buffer to indicate commands (i.e., input starting with a `/'). If nil, the prompt will be constructed from the variable `erc-prompt'." :group 'erc-display :type '(choice (const nil) string function)) (defun erc-command-indicator () "Return the command indicator prompt as a string. This only has any meaning if the variable `erc-command-indicator' is non-nil." (and erc-command-indicator (let ((prompt (if (functionp erc-command-indicator) (funcall erc-command-indicator) erc-command-indicator))) (if (> (length prompt) 0) (concat prompt " ") prompt)))) (defcustom erc-notice-prefix "*** " "Prefix for all notices." :group 'erc-display :type 'string) (defcustom erc-notice-highlight-type 'all "Determines how to highlight notices. See `erc-notice-prefix'. The following values are allowed: `prefix' - highlight notice prefix only `all' - highlight the entire notice Any other value disables notice's highlighting altogether." :group 'erc-display :type '(choice (const :tag "highlight notice prefix only" prefix) (const :tag "highlight the entire notice" all) (const :tag "don't highlight notices at all" nil))) (defcustom erc-echo-notice-hook nil "List of functions to call to echo a private notice. Each function is called with four arguments, the string to display, the parsed server message, the target buffer (or nil), and the sender. The functions are called in order, until a function evaluates to non-nil. These hooks are called after those specified in `erc-echo-notice-always-hook'. See also: `erc-echo-notice-always-hook', `erc-echo-notice-in-default-buffer', `erc-echo-notice-in-target-buffer', `erc-echo-notice-in-minibuffer', `erc-echo-notice-in-server-buffer', `erc-echo-notice-in-active-non-server-buffer', `erc-echo-notice-in-active-buffer', `erc-echo-notice-in-user-buffers', `erc-echo-notice-in-user-and-target-buffers', `erc-echo-notice-in-first-user-buffer'." :group 'erc-hooks :type 'hook :options '(erc-echo-notice-in-default-buffer erc-echo-notice-in-target-buffer erc-echo-notice-in-minibuffer erc-echo-notice-in-server-buffer erc-echo-notice-in-active-non-server-buffer erc-echo-notice-in-active-buffer erc-echo-notice-in-user-buffers erc-echo-notice-in-user-and-target-buffers erc-echo-notice-in-first-user-buffer)) (defcustom erc-echo-notice-always-hook '(erc-echo-notice-in-default-buffer) "List of functions to call to echo a private notice. Each function is called with four arguments, the string to display, the parsed server message, the target buffer (or nil), and the sender. The functions are called in order, and all functions are called. These hooks are called before those specified in `erc-echo-notice-hook'. See also: `erc-echo-notice-hook', `erc-echo-notice-in-default-buffer', `erc-echo-notice-in-target-buffer', `erc-echo-notice-in-minibuffer', `erc-echo-notice-in-server-buffer', `erc-echo-notice-in-active-non-server-buffer', `erc-echo-notice-in-active-buffer', `erc-echo-notice-in-user-buffers', `erc-echo-notice-in-user-and-target-buffers', `erc-echo-notice-in-first-user-buffer'." :group 'erc-hooks :type 'hook :options '(erc-echo-notice-in-default-buffer erc-echo-notice-in-target-buffer erc-echo-notice-in-minibuffer erc-echo-notice-in-server-buffer erc-echo-notice-in-active-non-server-buffer erc-echo-notice-in-active-buffer erc-echo-notice-in-user-buffers erc-echo-notice-in-user-and-target-buffers erc-echo-notice-in-first-user-buffer)) ;; other tunable parameters (defcustom erc-whowas-on-nosuchnick nil "If non-nil, do a whowas on a nick if no such nick." :group 'erc :type 'boolean) (defcustom erc-verbose-server-ping nil "If non-nil, show every time you get a PING or PONG from the server." :group 'erc-paranoia :type 'boolean) (defcustom erc-public-away-p nil "Let others know you are back when you are no longer marked away. This happens in this form: * is back (gone for