;;; erc.el --- An Emacs Internet Relay Chat client -*- lexical-binding:t -*- ;; Copyright (C) 1997-2024 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.6.1-git ;; Package-Requires: ((emacs "27.1") (compat "29.1.4.5")) ;; Keywords: IRC, chat, client, Internet ;; URL: https://www.gnu.org/software/emacs/erc.html ;; This is a GNU ELPA :core package. Avoid functionality that is not ;; compatible with the version of Emacs recorded above. ;; 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: (eval-and-compile (load "erc-loaddefs" 'noerror 'nomessage)) (require 'erc-networks) (require 'erc-backend) (require 'cl-lib) (require 'format-spec) (require 'auth-source) (eval-when-compile (require 'subr-x)) (defconst erc-version "5.6.1-git" "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") ("5.5" . "29.1") ("5.6" . "30.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 controlling how various things are displayed. See the customization group `erc-buffers' for display options concerning buffers." :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) ;; Add `custom-loads' features for group symbols missing from a ;; supported Emacs version, possibly because they belong to a new ERC ;; library. These groups all share their library's feature name. ;;;###autoload(dolist (symbol '( erc-sasl erc-spelling ; 29 ;;;###autoload erc-imenu erc-nicks)) ; 30 ;;;###autoload (custom-add-load symbol symbol)) (defvar erc-message-parsed) ; only known to this file (defvar erc--msg-props nil "Hash table containing metadata properties for current message. Provided by the insertion functions `erc-display-message' and `erc-display-msg' while running their modification hooks. Initialized when null for each visitation round from function parameters and environmental factors, as well as the alist `erc--msg-prop-overrides'. Keys are symbols. Values are opaque objects, unless otherwise specified. Items present after running `erc-insert-post-hook' or `erc-send-post-hook' become text properties added to the first character of an inserted message. A given message therefore spans the interval extending from one set of such properties to the newline before the next (or `erc-insert-marker'). As of ERC 5.6, this forms the basis for visiting and editing inserted messages. Modules should align their markers accordingly. The following properties have meaning as of ERC 5.6: - `erc--msg': a symbol, guaranteed present; possible values include `unknown', a fallback used by `erc-display-message'; a catalog key, such as `s401' or `finished'; an `erc-display-message' TYPE parameter, like `notice' - `erc--cmd': a message's associated IRC command, as read by `erc--get-eq-comparable-cmd'; currently either a symbol, like `PRIVMSG', or a number, like 5, which represents the numeric \"005\"; absent on \"local\" messages, such as simple warnings and help text, and on outgoing messages unless echoed back by the server (assuming future support) - `erc--spkr': a string, the non-case-mapped nick of the speaker as stored in the `nickname' slot of its `erc-server-users' item - `erc--ctcp': a CTCP command, like `ACTION' - `erc--ts': a timestamp, possibly provided by the server; as of 5.6, a ticks/hertz pair on Emacs 29 and above, and a \"list\" type otherwise; managed by the `stamp' module - `erc--skip': list of symbols known to modules that indicate an intent to skip or simplify module-specific processing - `erc--ephemeral': a symbol prefixed by or matching a module name; indicates to other modules and members of modification hooks that the current message should not affect stateful operations, such as recording a channel's most recent speaker - `erc--hide': a symbol or list of symbols added as an `invisible' prop value to the entire message, starting *before* the preceding newline and ending before the trailing newline This is an internal API, and the selection of related helper utilities is fluid and provisional. As of ERC 5.6, see the functions `erc--check-msg-prop' and `erc--get-inserted-msg-prop'.") (defvar erc--msg-prop-overrides nil "Alist of \"message properties\" for populating `erc--msg-props'. These override any defaults normally shown to modification hooks by `erc-display-msg' and `erc-display-message'. Modules should accommodate existing overrides when applicable. Items toward the front shadow any that follow. Ignored when `erc--msg-props' is already non-nil.") ;; Forward declarations (declare-function decoded-time-period "time-date" (time)) (declare-function iso8601-parse-duration "iso8601" (string)) (declare-function word-at-point "thingatpt" (&optional no-properties)) (autoload 'word-at-point "thingatpt") ; for hl-nicks (declare-function gnutls-negotiate "gnutls" (&rest rest)) (declare-function socks-open-network-stream "socks" (name buffer host service)) (declare-function url-host "url-parse" (cl-x)) (declare-function url-password "url-parse" (cl-x)) (declare-function url-portspec "url-parse" (cl-x)) (declare-function url-type "url-parse" (cl-x)) (declare-function url-user "url-parse" (cl-x)) ;; 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) auth-source'.") (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. When non-nil, ERC signals a `user-error' upon encountering prompt input containing empty or whitespace-only lines. When nil, ERC still inhibits sending but does so silently. With the companion option `erc-send-whitespace-lines' enabled, ERC sends pending input and prints a message in the echo area indicating the amount of padding and/or stripping applied, if any. Setting this option to nil suppresses such reporting." :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 meets or exceeds this value. The value t is synonymous with a value of 2 and means disallow more than 1 line of input." :package-version '(ERC . "5.5") :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.5") :group 'erc :type 'boolean) (defcustom erc-prompt-hidden ">" "Text to show in lieu of the prompt when hidden." :package-version '(ERC . "5.5") :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. For behavior concerning query prompts, see `erc-unhide-query-prompt'. Longtime ERC users should note that this option was repurposed in ERC 5.5 because it had lain dormant for years after being sidelined in 5.3 when its only use in the interactive client was removed. Before then, its role was controlling whether `erc-command-indicator' would appear alongside echoed slash-command lines." :package-version '(ERC . "5.5") :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.5") :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") (const :tag "Away notices (RPL_AWAY 301)" "301") (const :tag "Self back notice (REP_UNAWAY 305)" "305") (const :tag "Self away notice (REP_NOWAWAY 306)" "306") (const :tag "Channel modes on join (RPL_CHANNELMODEIS 324)" "324") (const :tag "Channel creation time (RPL_CREATIONTIME 329)" "329") (const :tag "Channel no-topic on join (RPL_NOTOPIC 331)" "331") (const :tag "Channel topic on join (RPL_TOPIC 332)" "332") (const :tag "Topic author and time on join (RPL_TOPICWHOTIME 333)" "333") (const :tag "Invitation success notice (RPL_INVITING 341)" "341") (const :tag "Channel member names (353 RPL_NAMEREPLY)" "353") (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 '(alist :key-type string :value-type erc-message-type :options ("Libera.Chat"))) (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 '(alist :key-type string :value-type erc-message-type :options ("#emacs"))) (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. Called in the server buffer with a single argument: the channel buffer being parted. For historical reasons, the buffer argument may be nil if it's been killed or otherwise can't be found. This typically happens when the \"PART\" response being handled comes by way of a channel buffer being killed, which, by default, tells `erc-part-channel-on-kill' to emit a \"PART\". Users needing to operate on a parted channel buffer before it's killed in this manner should use `erc-kill-channel-hook' and condition their code on `erc-killing-buffer-on-part-p' being nil." :group 'erc-hooks :type 'hook) (defvar erc-killing-buffer-on-part-p nil "Non-nil when killing a target buffer while handling a \"PART\" response. Useful for preventing the redundant execution of code designed to run once when parting a channel.") (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) (defvaralias 'erc-channel-users 'erc-channel-members) (defvar-local erc-channel-members nil "Hash table of members in the current channel or query buffer. It associates nicknames with cons cells of the form \(SERVER-USER . MEMBER-DATA), where SERVER-USER is a `erc-server-user' object and MEMBER-DATA is a `erc-channel-user' object. Convenient abbreviations for these two components are `susr' and `cusr', along with `cmem' for the pair.") (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-strict (let ((tbl (copy-sequence ascii-case-table)) (cup (copy-sequence (char-table-extra-slot ascii-case-table 0)))) (set-char-table-extra-slot tbl 0 cup) (set-char-table-extra-slot tbl 1 nil) (set-char-table-extra-slot tbl 2 nil) (pcase-dolist (`(,uc . ,lc) '((?\[ . ?\{) (?\] . ?\}) (?\\ . ?\|))) (aset tbl uc lc) (aset tbl lc lc) (aset cup uc uc)) tbl)) (defconst erc--casemapping-rfc1459 (let ((tbl (copy-sequence erc--casemapping-rfc1459-strict)) (cup (copy-sequence (char-table-extra-slot erc--casemapping-rfc1459-strict 0)))) (set-char-table-extra-slot tbl 0 cup) (aset tbl ?~ ?^) (aset tbl ?^ ?^) (aset cup ?~ ?~) tbl)) (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))) (defvar erc--decouple-query-and-channel-membership-p nil "When non-nil, don't tether query participation to channel membership. Specifically, add users to query tables when they speak, don't remove them when they leave all channels, and allow removing the client's own user from `erc-server-users'. Note that enabling this compatibility flag degrades the user experience and isn't guaranteed to correctly restore the described historical behavior.") (cl-defmethod erc--queries-current-p () "Return non-nil if ERC actively updates query manifests." (and (not erc--decouple-query-and-channel-membership-p) (erc-query-buffer-p) (erc-get-channel-member (erc-target)))) (defun erc--ensure-query-member (nick) "Populate membership table in query buffer for online NICK." (erc-with-buffer (nick) (when-let (((not erc--decouple-query-and-channel-membership-p)) ((zerop (hash-table-count erc-channel-users))) (user (erc-get-server-user nick))) (erc-update-current-channel-member nick nil t) (erc--unhide-prompt) t))) (defun erc--ensure-query-members () "Update membership tables in all query buffers. Ensure targets with an entry in `erc-server-users' are present in `erc-channel-members'." (erc-with-all-buffers-of-server erc-server-process #'erc-query-buffer-p (when-let (((not erc--decouple-query-and-channel-membership-p)) ((zerop (hash-table-count erc-channel-users))) (target (erc-target)) ((erc-get-server-user target))) (erc-update-current-channel-member target nil t) (erc--unhide-prompt)) erc-server-process)) (defun erc-remove-server-user (nick) "Remove NICK from the session's `erc-server-users' table." (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))))))) (defvar erc--forget-server-user-function #'erc--forget-server-user-ignoring-queries "Function to conditionally remove a user from `erc-server-users'. Called with a nick and its `erc-server-user' object.") (defun erc--forget-server-user (nick user) "Remove NICK's USER from server table if they're not in any target buffers." (unless (erc-server-user-buffers user) (erc-remove-server-user nick))) (defun erc--forget-server-user-ignoring-queries (nick user) "Remove NICK's USER from `erc-server-users' if they've parted all channels." (let ((buffers (erc-server-user-buffers user))) (when (or (null buffers) (and (not erc--decouple-query-and-channel-membership-p) (cl-every #'erc-query-buffer-p buffers))) (when buffers (erc--remove-user-from-targets (erc-downcase nick) buffers)) (erc-remove-server-user nick)))) (defun erc-remove-channel-user (nick) "Remove NICK from the current target buffer's `erc-channel-members'. If this was their only target, also remove them from `erc-server-users'." (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) (funcall erc--forget-server-user-function nick user))))) (defun erc-remove-user (nick) "Remove NICK from the server and all relevant channels tables." (let ((user (erc-get-server-user nick))) (when user (erc--remove-user-from-targets (erc-downcase nick) (erc-server-user-buffers user)) (erc-remove-server-user nick)))) (defun erc-remove-channel-users () "Drain current buffer's `erc-channel-members' table. Also remove members from the server table if this was their only buffer." (when (erc--target-channel-p erc--target) (setf (erc--target-channel-joined-p erc--target) nil)) (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--remove-channel-users-but (nick) "Drain channel users and remove from server, sparing NICK." (when-let ((users (erc-with-server-buffer erc-server-users)) (my-user (gethash (erc-downcase nick) users)) (original-function erc--forget-server-user-function) (erc--forget-server-user-function (if erc--decouple-query-and-channel-membership-p erc--forget-server-user-function (lambda (nick user) (unless (eq user my-user) (funcall original-function nick user)))))) (erc-remove-channel-users))) (defmacro erc--define-channel-user-status-compat-getter (name c d) "Define accessor with gv getter for historical `erc-channel-user' slot NAME. Expect NAME to be a string, C to be its traditionally associated letter, and D to be its fallback power-of-2 integer for non-ERC buffers. Unlike pre-ERC-5.6 accessors, do not bother generating a compiler macro for inlining calls to these adapters." `(defun ,(intern (concat "erc-channel-user-" name)) (u) ,(format "Get equivalent of pre-5.6 `%s' slot for `erc-channel-user'." name) (declare (gv-setter (lambda (v) (macroexp-let2 nil v v (,'\`(let ((val (erc-channel-user-status ,',u)) (n (or (erc--get-prefix-flag ,c) ,d))) (setf (erc-channel-user-status ,',u) (if ,',v (logior val n) (logand val (lognot n)))) ,',v)))))) (let ((n (or (erc--get-prefix-flag ,c) ,d))) (= n (logand n (erc-channel-user-status u)))))) (erc--define-channel-user-status-compat-getter "voice" ?v 1) (erc--define-channel-user-status-compat-getter "halfop" ?h 2) (erc--define-channel-user-status-compat-getter "op" ?o 4) (erc--define-channel-user-status-compat-getter "admin" ?a 8) (erc--define-channel-user-status-compat-getter "owner" ?q 16) ;; This is a generalized version of the compat-oriented getters above. (defun erc--cusr-status-p (nick-or-cusr letter) "Return non-nil if NICK-OR-CUSR has channel membership status LETTER." (and-let* ((cusr (or (and (erc-channel-user-p nick-or-cusr) nick-or-cusr) (cdr (erc-get-channel-member nick-or-cusr)))) (n (erc--get-prefix-flag letter))) (= n (logand n (erc-channel-user-status cusr))))) (defun erc--cusr-change-status (nick-or-cusr letter enablep &optional resetp) "Add or remove membership status associated with LETTER for NICK-OR-CUSR. With RESETP, clear the user's status info completely. If ENABLEP is non-nil, add the status value associated with LETTER." (when-let ((cusr (or (and (erc-channel-user-p nick-or-cusr) nick-or-cusr) (cdr (erc-get-channel-member nick-or-cusr)))) (n (erc--get-prefix-flag letter))) (cl-callf (lambda (v) (if resetp (if enablep n 0) (if enablep (logior v n) (logand v (lognot n))))) (erc-channel-user-status cusr)))) (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 letters, as strings, representing channel modes. For example, (\"i\" \"m\" \"s\"). Modes that take accompanying parameters are not included.") (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-item :tag "Interpret format specifiers" erc-prompt-format) function)) (defvar erc--prompt-format-face-example #("%p%m%a\u00b7%b>" 0 2 (font-lock-face erc-my-nick-prefix-face) 2 4 (font-lock-face font-lock-keyword-face) 4 6 (font-lock-face erc-error-face) 6 7 (font-lock-face shadow) 7 9 (font-lock-face font-lock-constant-face) 9 10 (font-lock-face shadow)) "An example value for option `erc-prompt-format' with faces.") (defcustom erc-prompt-format erc--prompt-format-face-example "Format string when `erc-prompt' is `erc-prompt-format'. ERC recognizes these substitution specifiers: %a - away indicator %b - buffer name %t - channel or query target, server domain, or dialed address %S - target@network or buffer name %s - target@server or server %N - current network, like Libera.Chat %p - channel membership prefix, like @ or + %n - current nickname %c - channel modes with args for select modes %C - channel modes with all args %u - user modes %m - channel modes sans args in channels, user modes elsewhere %M - like %m but show nothing in query buffers To pick your own colors, do something like: (setopt erc-prompt-format (concat (propertize \"%b\" \\='font-lock-face \\='erc-input-face) (propertize \"%a\" \\='font-lock-face \\='erc-error-face))) Please remember that ERC ignores this option completely unless the \"parent\" option `erc-prompt' is set to `erc-prompt-format'." :package-version '(ERC . "5.6") :group 'erc-display :type `(choice (const :tag "{Prefix}{Mode}{Away}{MIDDLE DOT}{Buffer}>" ,erc--prompt-format-face-example) string)) (defun erc-prompt-format () "Make predefined `format-spec' substitutions. See option `erc-prompt-format' and option `erc-prompt'." (format-spec erc-prompt-format (erc-compat--defer-format-spec-in-buffer (?C erc--channel-modes 3 ",") (?M erc--format-modes 'no-query-p) (?N erc-format-network) (?S erc-format-target-and/or-network) (?a erc--format-away-indicator) (?b buffer-name) (?c erc-format-channel-modes) (?m erc--format-modes) (?n erc-current-nick) (?p erc--format-channel-status-prefix) (?s erc-format-target-and/or-server) (?t erc-format-target) (?u erc--format-user-modes)) 'ignore-missing)) ; formerly `only-present' (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-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