From b57325022457ad86ae990f8cd6275a284c4912f0 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Tue, 14 Nov 2023 21:10:39 -0800 Subject: [PATCH 3/3] [5.6] Rework MODE handling in ERC * etc/ERC-NEWS: Mention shift toward CHANMODES ISUPPORT parameter for dictating parsing behavior. * lisp/erc/erc-backend.el (erc--init-channel-modes, erc-update-modes, erc-set-modes, erc-update-modes): Forward declarations, the last two being removals. (erc-server-MODE, erc-server-221): Call `erc--update-modes' instead of `erc-update-modes'. (erc-server-324): Call `erc--init-channel-modes' instead of `erc-set-modes'. * lisp/erc/erc.el (erc-channel-modes): Fix doc string. (erc-set-initial-user-mode): Display a local notice when requesting redundant user MODE operations. (erc-set-modes, erc-parse-modes, erc-update-modes): Deprecate. (erc--update-membership-prefix): New function, a helper for specifying unruly `erc-update-current-channel-member' parameters. (erc--update-channel-modes-omit-status-p): New internal variable. (erc--update-channel-modes): New function to replace much of `erc-update-modes'. (erc--user-modes): New local variable for remembering user modes per server. New function of the same name, a getter for that variable. (erc--parse-user-modes): New function to parse user modes only. (erc--merge-user-modes): New function, a helper for deduping `erc--user-modes' after adding or removing. (erc--update-modes): New function to dispatch correct parsing and updating function for the current buffer context. (erc--init-channel-modes): New function to update channel mode letters while skipping status prefixes. (erc--handle-channel-mode): New internal generic function, a placeholder for eventual API to handle specific unary modes. (erc-update-channel-limit): Update doc string. (erc-message-english-user-mode-redundant-add, erc-message-english-user-mode-redundant-drop): New English catalog messages. * test/lisp/erc/erc-scenarios-base-chan-modes.el: New file. * test/lisp/erc/erc-tests.el (erc-parse-modes, erc--update-channel-modes): New tests. * test/lisp/erc/resources/base/modes/chan-changed.eld: New file. --- etc/ERC-NEWS | 11 ++ lisp/erc/erc-backend.el | 11 +- lisp/erc/erc.el | 171 +++++++++++++++++- .../lisp/erc/erc-scenarios-base-chan-modes.el | 84 +++++++++ test/lisp/erc/erc-tests.el | 86 +++++++++ .../erc/resources/base/modes/chan-changed.eld | 55 ++++++ 6 files changed, 402 insertions(+), 16 deletions(-) create mode 100644 test/lisp/erc/erc-scenarios-base-chan-modes.el create mode 100644 test/lisp/erc/resources/base/modes/chan-changed.eld diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS index 04b11fc19f0..3bb9a30cfb2 100644 --- a/etc/ERC-NEWS +++ b/etc/ERC-NEWS @@ -480,6 +480,17 @@ release lacks a similar solution for detecting "joinedness" directly, but users can turn to 'xor'-ing 'erc-default-target' and 'erc-target' as a makeshift kludge. +*** Channel-mode handling has become stricter and more predictable. +ERC has always processed channel modes using "standardized" letters +and popular status prefixes. Starting with this release, ERC will +begin preferring advertised "CHANMODES" when interpreting letters and +their arguments. To facilitate this transition, the functions +'erc-set-modes', 'erc-parse-modes', and 'erc-update-modes', have all +been provisionally deprecated. Expect a new, replacement API for +handling specific "MODE" types and letters in coming releases. If +you'd like a say in shaping how this transpires, please share your +ideas and use cases on the tracker. + *** Miscellaneous changes Two helper macros from GNU ELPA's Compat library are now available to third-party modules as 'erc-compat-call' and 'erc-compat-function'. diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 2242b40e9a4..ace46cf84f5 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -132,8 +132,10 @@ erc-reuse-buffers (defvar erc-verbose-server-ping) (defvar erc-whowas-on-nosuchnick) +(declare-function erc--init-channel-modes "erc" (channel raw-args)) (declare-function erc--open-target "erc" (target)) (declare-function erc--target-from-string "erc" (string)) +(declare-function erc--update-modes "erc" (raw-args)) (declare-function erc-active-buffer "erc" nil) (declare-function erc-add-default-channel "erc" (channel)) (declare-function erc-banlist-update "erc" (proc parsed)) @@ -179,7 +181,6 @@ erc-whowas-on-nosuchnick (declare-function erc-server-buffer "erc" nil) (declare-function erc-set-active-buffer "erc" (buffer)) (declare-function erc-set-current-nick "erc" (nick)) -(declare-function erc-set-modes "erc" (tgt mode-string)) (declare-function erc-time-diff "erc" (t1 t2)) (declare-function erc-trim-string "erc" (s)) (declare-function erc-update-mode-line "erc" (&optional buffer)) @@ -194,8 +195,6 @@ erc-whowas-on-nosuchnick (proc parsed nick login host msg)) (declare-function erc-update-channel-topic "erc" (channel topic &optional modify)) -(declare-function erc-update-modes "erc" - (tgt mode-string &optional _nick _host _login)) (declare-function erc-update-user-nick "erc" (nick &optional new-nick host login full-name info)) (declare-function erc-open "erc" @@ -1802,7 +1801,7 @@ erc--server-determine-join-display-context (t (erc-get-buffer tgt))))) (with-current-buffer (or buf (current-buffer)) - (erc-update-modes tgt mode nick host login)) + (erc--update-modes (cdr (erc-response.command-args parsed)))) (if (or (string= login "") (string= host "")) (erc-display-message parsed 'notice buf 'MODE-nick ?n nick @@ -2144,7 +2143,7 @@ erc--get-isupport-entry (let* ((nick (car (erc-response.command-args parsed))) (modes (mapconcat #'identity (cdr (erc-response.command-args parsed)) " "))) - (erc-set-modes nick modes) + (erc--update-modes (cdr (erc-response.command-args parsed))) (erc-display-message parsed 'notice 'active 's221 ?n nick ?m modes))) (define-erc-response-handler (252) @@ -2310,7 +2309,7 @@ erc-server-322-message (let ((channel (cadr (erc-response.command-args parsed))) (modes (mapconcat #'identity (cddr (erc-response.command-args parsed)) " "))) - (erc-set-modes channel modes) + (erc--init-channel-modes channel (cddr (erc-response.command-args parsed))) (erc-display-message parsed 'notice (erc-get-buffer channel proc) 's324 ?c channel ?m modes))) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index bbbbc405526..8a74414cb0c 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -732,9 +732,9 @@ erc-channel-topic "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)") + "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.") @@ -4552,6 +4552,10 @@ erc--send-message-nested (erc--send-input-lines (erc--run-send-hooks lines-obj))) t) +;; FIXME if the user types /MODE, LINE becomes "\n", which +;; matches the pattern, so "\n" is sent to the server. Perhaps +;; instead of `do-not-parse-args', this should just join &rest +;; arguments. (defun erc-cmd-MODE (line) "Change or display the mode value of a channel or user. The first word specifies the target. The rest is the mode string @@ -5914,9 +5918,19 @@ erc-set-initial-user-mode The server buffer is given by BUFFER." (with-current-buffer buffer (when erc-user-mode - (let ((mode (if (functionp erc-user-mode) - (funcall erc-user-mode) - erc-user-mode))) + (let* ((mode (if (functionp erc-user-mode) + (funcall erc-user-mode) + erc-user-mode)) + (as-pair (erc--parse-user-modes mode)) + (have (erc--user-modes)) + (redundant-want (seq-intersection (car as-pair) have)) + (redundant-drop (seq-difference (cadr as-pair) have))) + (when redundant-want + (erc-display-message nil 'notice buffer 'user-mode-redundant-add + ?m (apply #'string redundant-want))) + (when redundant-drop + (erc-display-message nil 'notice buffer 'user-mode-redundant-drop + ?m (apply #'string redundant-drop))) (when (stringp mode) (erc-log (format "changing mode for %s to %s" nick mode)) (erc-server-send (format "MODE %s %s" nick mode))))))) @@ -6488,7 +6502,9 @@ erc-update-channel-topic (defun erc-set-modes (tgt mode-string) "Set the modes for the TGT provided as MODE-STRING." - (let* ((modes (erc-parse-modes mode-string)) + (declare (obsolete "see comment atop `erc--update-modes'" "30.1")) + (let* ((modes (with-suppressed-warnings ((obsolete erc-parse-modes)) + (erc-parse-modes mode-string))) (add-modes (nth 0 modes)) ;; list of triples: (mode-char 'on/'off argument) (arg-modes (nth 2 modes))) @@ -6534,6 +6550,7 @@ erc-parse-modes arg-modes is a list of triples of the form: (MODE-CHAR ON/OFF ARGUMENT)." + (declare (obsolete "see comment atop `erc--update-modes'" "30.1")) (if (string-match "^\\s-*\\(\\S-+\\)\\(\\s-.*$\\|$\\)" mode-string) (let ((chars (mapcar #'char-to-string (match-string 1 mode-string))) ;; arguments in channel modes @@ -6578,8 +6595,10 @@ erc-update-modes "Update the mode information for TGT, provided as MODE-STRING. Optional arguments: NICK, HOST and LOGIN - the attributes of the person who changed the modes." + (declare (obsolete "see comment atop `erc--update-modes'" "30.1")) ;; FIXME: neither of nick, host, and login are used! - (let* ((modes (erc-parse-modes mode-string)) + (let* ((modes (with-suppressed-warnings ((obsolete erc-parse-modes)) + (erc-parse-modes mode-string))) (add-modes (nth 0 modes)) (remove-modes (nth 1 modes)) ;; list of triples: (mode-char 'on/'off argument) @@ -6628,9 +6647,137 @@ erc-update-modes ;; nick modes - ignored at this point (t nil)))) +(defun erc--update-membership-prefix (nick letter state) + "Update status prefixes for NICK in current channel buffer. +Expect LETTER to be a status char and STATE to be a boolean." + (erc-update-current-channel-member nick nil nil + (and (= letter ?v) state) + (and (= letter ?h) state) + (and (= letter ?o) state) + (and (= letter ?a) state) + (and (= letter ?q) state))) + +(defvar erc--update-channel-modes-omit-status-p nil) + +(defun erc--update-channel-modes (string &rest args) + "Update `erc-channel-modes' and dispatch individual mode handlers. +Also update status prefixes, as needed. Expect STRING to be a +\"modestring\" and ARGS to match mode-specific parameters. When +`erc--update-channel-modes-omit-status-p' is non-nil, forgo +setting status prefixes for channel members." + (cl-assert erc-server-process) + (cl-assert erc--target) + (cl-assert (erc--target-channel-p erc--target)) + (pcase-let* ((status-letters + (and (not erc--update-channel-modes-omit-status-p) + (or (erc-with-server-buffer + (erc--parse-prefix) + (erc--parsed-prefix-letters erc--parsed-prefix)) + "qaovhbQAOVHB"))) + (`(,type-a ,type-b ,type-c ,type-d) + (or (cdr (erc--get-isupport-entry 'CHANMODES)) + '(nil "Kk" "Ll" nil))) + (+p t)) + (dolist (c (append string nil)) + (let ((letter (char-to-string c))) + (cond ((= ?+ c) (setq +p t)) + ((= ?- c) (setq +p nil)) + ((and status-letters (string-search letter status-letters)) + (erc--update-membership-prefix (pop args) c (if +p 'on 'off))) + ((and type-a (string-search letter type-a)) + (erc--handle-channel-mode 'a c +p (pop args))) + ((string-search letter type-b) + (erc--handle-channel-mode 'b c +p (pop args))) + ((string-search letter type-c) + (erc--handle-channel-mode 'c c +p (and +p (pop args)))) + ((or (null type-d) (string-search letter type-d)) + (setq erc-channel-modes + (if +p + (cl-pushnew letter erc-channel-modes :test #'equal) + (delete letter erc-channel-modes)))) + (type-d ; OK to print error because server buffer exists + (erc-display-message nil '(notice error) (erc-server-buffer) + (format "Unknown channel mode: %S" c)))))) + (setq erc-channel-modes (erc-sort-strings erc-channel-modes)) + (erc-update-mode-line (current-buffer)))) + +(defvar-local erc--user-modes nil + "List of current user modes, analogous to `erc-channel-modes'.") + +(defun erc--user-modes (&optional as-string-p) + "Return user mode letters as chars or, with AS-STRING-P, a single string." + (let ((modes (erc-with-server-buffer erc--user-modes))) + (if as-string-p + (apply #'string (if (memq as-string-p '(+ ?+)) (cons '?+ modes) modes)) + modes))) + +(defun erc--parse-user-modes (string) + "Return a list of mode chars to add and remove, based on STRING." + (let ((addp t) + add-modes remove-modes) + (seq-doseq (c string) + (pcase c + (?+ (setq addp t)) + (?- (setq addp nil)) + (_ (push c (if addp add-modes remove-modes))))) + (list (nreverse add-modes) + (nreverse remove-modes)))) + +(defun erc--merge-user-modes (adding dropping) + "Update `erc--user-modes' with chars ADDING and DROPPING." + (sort (seq-difference (seq-union erc--user-modes adding) dropping) #'-)) + +;; XXX this comment is referenced elsewhere (grep before deleting). +;; +;; The function `erc-update-modes' was deprecated in ERC 5.6 with no +;; immediate public replacement. Third parties needing such a thing +;; are encouraged to write to emacs-erc@gnu.org with ideas for a +;; mode-handler API, possibly one incorporating mode-letter specific +;; handlers, like `erc--handle-channel-mode' below. +(defun erc--update-modes (raw-args) + "Handle user or channel mode update from server. +Expect RAW-ARGS to be a \"modestring\" followed by mode-specific +arguments." + (if (and erc--target (erc--target-channel-p erc--target)) + (apply #'erc--update-channel-modes raw-args) + (setq erc--user-modes + (apply #'erc--merge-user-modes + (erc--parse-user-modes (car raw-args)))))) + +(defun erc--init-channel-modes (channel raw-args) + "Set CHANNEL modes from RAW-ARGS." + (let ((erc--update-channel-modes-omit-status-p t)) + (erc-with-buffer (channel) + (apply #'erc--update-channel-modes raw-args)))) + +(cl-defgeneric erc--handle-channel-mode (type letter state arg) + "Handle a STATE change for mode LETTER of TYPE with ARG. +Expect to be called in the affected target buffer. Expect TYPE +to be a symbol, namely, one of `a', `b', `c', or `d'. Expect +LETTER to be a character, STATE to be a boolean, and ARGUMENT to +be either a string or nil." + (erc-log (format "Channel-mode %c (type %s, arg %S) %s" + letter type arg (if state 'enabled 'disabled)))) + +;; We could specialize on (eql 'c), but that may be too brittle. +(cl-defmethod erc--handle-channel-mode (_ (_ (eql ?l)) state arg) + (erc-update-channel-limit (erc--target-string erc--target) + (if state 'on 'off) + arg)) + +;; We could specialize on (eql 'b), but that may be too brittle. +(cl-defmethod erc--handle-channel-mode (_ (_ (eql ?k)) state arg) + ;; Mimic old parsing behavior in which an ARG of "*" was discarded + ;; even though `erc-update-channel-limit' checks STATE first. + (erc-update-channel-key (erc--target-string erc--target) + (if state 'on 'off) + (if (equal arg "*") nil arg))) + (defun erc-update-channel-limit (channel onoff n) - ;; FIXME: what does ONOFF actually do? -- Lawrence 2004-01-08 - "Update CHANNEL's user limit to N." + "Update CHANNEL's user limit to N. +Expect ONOFF to be `on' when the mode is being enabled and `off' +otherwise. And because this mode is of \"type C\", expect N to +be non-nil only when enabling." (if (or (not (eq onoff 'on)) (and (stringp n) (string-match "^[0-9]+$" n))) (erc-with-buffer @@ -8306,6 +8453,10 @@ erc-define-catalog (ops . "%i operator%s: %o") (ops-none . "No operators in this channel.") (undefined-ctcp . "Undefined CTCP query received. Silently ignored") + (user-mode-redundant-add + . "Already have user mode(s): %m. Requesting again anyway.") + (user-mode-redundant-drop + . "Already without user mode(s): %m. Requesting removal anyway.") (variable-not-bound . "Variable not bound!") (ACTION . "* %n %a") (CTCP-CLIENTINFO . "Client info for %n: %m") diff --git a/test/lisp/erc/erc-scenarios-base-chan-modes.el b/test/lisp/erc/erc-scenarios-base-chan-modes.el new file mode 100644 index 00000000000..9c63d8aff8e --- /dev/null +++ b/test/lisp/erc/erc-scenarios-base-chan-modes.el @@ -0,0 +1,84 @@ +;;; erc-scenarios-base-chan-modes.el --- Channel mode scenarios -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; 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 . + +;;; Code: + +(require 'ert-x) +(eval-and-compile + (let ((load-path (cons (ert-resource-directory) load-path))) + (require 'erc-scenarios-common))) + +;; This asserts that a bug present in ERC 5.4+ is now absent. +;; Previously, ERC would attempt to parse a nullary channel mode as if +;; it were a status prefix update, which led to a wrong-type error. +;; This test does not address similar collisions with unary modes, +;; such as "MODE +q foo!*@*", but it should. +(ert-deftest erc-scenarios-base-chan-modes--plus-q () + :tags '(:expensive-test) + (erc-scenarios-common-with-cleanup + ((erc-scenarios-common-dialog "base/modes") + (erc-server-flood-penalty 0.1) + (dumb-server (erc-d-run "localhost" t 'chan-changed)) + (erc-modules (cons 'fill-wrap erc-modules)) + (erc-autojoin-channels-alist '((Libera.Chat "#chan"))) + (expect (erc-d-t-make-expecter))) + + (ert-info ("Connect to Libera.Chat") + (with-current-buffer (erc :server "127.0.0.1" + :port (process-contact dumb-server :service) + :nick "tester" + :full-name "tester") + (funcall expect 5 "changed mode"))) + + (with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan")) + (should-not erc-channel-key) + (should-not erc-channel-user-limit) + + (ert-info ("Receive notice that mode has changed") + (erc-d-t-wait-for 10 (equal erc-channel-modes '("n" "t"))) + (erc-scenarios-common-say "ready before") + (funcall expect 10 " before") + (funcall expect 10 " has changed mode for #chan to +Qu") + (erc-d-t-wait-for 10 (equal erc-channel-modes '("Q" "n" "t" "u")))) + + (ert-info ("Key stored locally") + (erc-scenarios-common-say "ready key") + (funcall expect 10 " doing key") + (funcall expect 10 " has changed mode for #chan to +k hunter2") + (should (equal erc-channel-key "hunter2"))) + + (ert-info ("Limit stored locally") + (erc-scenarios-common-say "ready limit") + (funcall expect 10 " doing limit") + (funcall expect 10 " has changed mode for #chan to +l 3") + (erc-d-t-wait-for 10 (eql erc-channel-user-limit 3)) + (should (equal erc-channel-modes '("Q" "n" "t" "u")))) + + (ert-info ("Modes removed and local state deletion succeeds") + (erc-scenarios-common-say "ready drop") + (funcall expect 10 " dropping") + (funcall expect 10 " has changed mode for #chan to -lu") + (funcall expect 10 " has changed mode for #chan to -Qk *") + (erc-d-t-wait-for 10 (equal erc-channel-modes '("n" "t")))) + + (should-not erc-channel-key) + (should-not erc-channel-user-limit) + (funcall expect 10 " after")))) + +;;; erc-scenarios-base-chan-modes.el ends here diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index 28bf1fbcccc..1ff5f4890a8 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -682,6 +682,92 @@ erc--parse-prefix (erc-with-server-buffer erc--parsed-prefix)) expected))))) +;; This tests exists to prove legacy behavior in order to incorporate +;; it as a fallback in the 5.6+ replacement. +(ert-deftest erc-parse-modes () + (with-suppressed-warnings ((obsolete erc-parse-modes)) + (should (equal (erc-parse-modes "+u") '(("u") nil nil))) + (should (equal (erc-parse-modes "-u") '(nil ("u") nil))) + (should (equal (erc-parse-modes "+o bob") '(nil nil (("o" on "bob"))))) + (should (equal (erc-parse-modes "-o bob") '(nil nil (("o" off "bob"))))) + (should (equal (erc-parse-modes "+uo bob") '(("u") nil (("o" on "bob"))))) + (should (equal (erc-parse-modes "+o-u bob") '(nil ("u") (("o" on "bob"))))) + (should (equal (erc-parse-modes "+uo-tv bob alice") + '(("u") ("t") (("o" on "bob") ("v" off "alice"))))) + + (ert-info ("Modes of type B are always grouped as unary") + (should (equal (erc-parse-modes "+k h2") '(nil nil (("k" on "h2"))))) + ;; Channel key args are thrown away. + (should (equal (erc-parse-modes "-k *") '(nil nil (("k" off nil)))))) + + (ert-info ("Modes of type C are grouped as unary even when disabling") + (should (equal (erc-parse-modes "+l 3") '(nil nil (("l" on "3"))))) + (should (equal (erc-parse-modes "-l") '(nil nil (("l" off nil)))))))) + +(ert-deftest erc--update-channel-modes () + (erc-mode) + (setq erc-channel-users (make-hash-table :test #'equal) + erc-server-users (make-hash-table :test #'equal) + erc--isupport-params (make-hash-table) + erc--target (erc--target-from-string "#test")) + (erc-tests--set-fake-server-process "sleep" "1") + + (let (calls) + (cl-letf (((symbol-function 'erc--handle-channel-mode) + (lambda (&rest r) (push r calls))) + ((symbol-function 'erc-update-mode-line) #'ignore)) + + (ert-info ("Unknown user not created") + (erc--update-channel-modes "+o" "bob") + (should-not (erc-get-channel-user "bob"))) + + (ert-info ("Status updated when user known") + (puthash "bob" (cons (erc-add-server-user + "bob" (make-erc-server-user :nickname "bob")) + (make-erc-channel-user)) + erc-channel-users) + ;; Also asserts fallback behavior for traditional prefixes. + (should-not (erc-channel-user-op-p "bob")) + (erc--update-channel-modes "+o" "bob") + (should (erc-channel-user-op-p "bob")) + (erc--update-channel-modes "-o" "bob") ; status revoked + (should-not (erc-channel-user-op-p "bob"))) + + (ert-info ("Unknown nullary added and removed") + (should-not erc-channel-modes) + (erc--update-channel-modes "+u") + (should (equal erc-channel-modes '("u"))) + (erc--update-channel-modes "-u") + (should-not erc-channel-modes) + (should-not calls)) + + (ert-info ("Fallback for Type B includes mode letter k") + (erc--update-channel-modes "+k" "h2") + (should (equal (pop calls) '(b ?k t "h2"))) + (should-not erc-channel-modes) + (erc--update-channel-modes "-k" "*") + (should (equal (pop calls) '(b ?k nil "*"))) + (should-not erc-channel-modes)) + + (ert-info ("Fallback for Type C includes mode letter l") + (erc--update-channel-modes "+l" "3") + (should (equal (pop calls) '(c ?l t "3"))) + (should-not erc-channel-modes) + (erc--update-channel-modes "-l" nil) + (should (equal (pop calls) '(c ?l nil nil))) + (should-not erc-channel-modes)) + + (ert-info ("Advertised supersedes heuristics") + (setq erc-server-parameters + '(("PREFIX" . "(ov)@+") + ("CHANMODES" . "eIbq,k,flj,CFLMPQRSTcgimnprstuz"))) + (erc--update-channel-modes "+qu" "fool!*@*") + (should (equal (pop calls) '(a ?q t "fool!*@*"))) + (should (equal erc-channel-modes '("u"))) + (should-not (erc-channel-user-owner-p "bob"))) + + (should-not calls)))) + (ert-deftest erc--parse-isupport-value () (should (equal (erc--parse-isupport-value "a,b") '("a" "b"))) (should (equal (erc--parse-isupport-value "a,b,c") '("a" "b" "c"))) diff --git a/test/lisp/erc/resources/base/modes/chan-changed.eld b/test/lisp/erc/resources/base/modes/chan-changed.eld new file mode 100644 index 00000000000..6cf6596b0b2 --- /dev/null +++ b/test/lisp/erc/resources/base/modes/chan-changed.eld @@ -0,0 +1,55 @@ +;; -*- mode: lisp-data; -*- +((nick 10 "NICK tester")) +((user 10 "USER user 0 * :tester") + (0.03 ":cadmium.libera.chat 001 tester :Welcome to the Libera.Chat Internet Relay Chat Network tester") + (0.02 ":cadmium.libera.chat 002 tester :Your host is cadmium.libera.chat[103.196.37.95/6697], running version solanum-1.0-dev") + (0.01 ":cadmium.libera.chat 003 tester :This server was created Wed Jan 25 2023 at 10:22:45 UTC") + (0.01 ":cadmium.libera.chat 004 tester cadmium.libera.chat solanum-1.0-dev DGMQRSZaghilopsuwz CFILMPQRSTbcefgijklmnopqrstuvz bkloveqjfI") + (0.00 ":cadmium.libera.chat 005 tester CALLERID=g WHOX ETRACE FNC SAFELIST ELIST=CMNTU KNOCK MONITOR=100 CHANTYPES=# EXCEPTS INVEX CHANMODES=eIbq,k,flj,CFLMPQRSTcgimnprstuz :are supported by this server") + (0.01 ":cadmium.libera.chat 005 tester CHANLIMIT=#:250 PREFIX=(ov)@+ MAXLIST=bqeI:100 MODES=4 NETWORK=Libera.Chat STATUSMSG=@+ CASEMAPPING=rfc1459 NICKLEN=16 MAXNICKLEN=16 CHANNELLEN=50 TOPICLEN=390 DEAF=D :are supported by this server") + (0.01 ":cadmium.libera.chat 005 tester TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,PRIVMSG:4,NOTICE:4,ACCEPT:,MONITOR: EXTBAN=$,ajrxz :are supported by this server") + (0.01 ":cadmium.libera.chat 251 tester :There are 70 users and 42996 invisible on 28 servers") + (0.02 ":cadmium.libera.chat 252 tester 38 :IRC Operators online") + (0.01 ":cadmium.libera.chat 253 tester 57 :unknown connection(s)") + (0.01 ":cadmium.libera.chat 254 tester 22912 :channels formed") + (0.01 ":cadmium.libera.chat 255 tester :I have 2499 clients and 1 servers") + (0.01 ":cadmium.libera.chat 265 tester 2499 4187 :Current local users 2499, max 4187") + (0.01 ":cadmium.libera.chat 266 tester 43066 51827 :Current global users 43066, max 51827") + (0.01 ":cadmium.libera.chat 250 tester :Highest connection count: 4188 (4187 clients) (319420 connections received)") + (0.01 ":cadmium.libera.chat 375 tester :- cadmium.libera.chat Message of the Day - ") + (0.01 ":cadmium.libera.chat 372 tester :- This server kindly provided by Mach Dilemma (www.m-d.net)") + (0.01 ":cadmium.libera.chat 372 tester :- Welcome to Libera Chat, the IRC network for") + (0.00 ":cadmium.libera.chat 372 tester :- Email: support@libera.chat") + (0.00 ":cadmium.libera.chat 376 tester :End of /MOTD command.") + (0.00 ":tester MODE tester :+Ziw")) + +((mode-tester 10 "MODE tester +i")) + +((join-chan 10 "JOIN #chan") + (0.09 ":tester!~tester@127.0.0.1 JOIN #chan")) + +((mode-chan 10 "MODE #chan") + (0.03 ":cadmium.libera.chat 353 tester = #chan :tester @Chad dummy") + (0.02 ":cadmium.libera.chat 366 tester #chan :End of /NAMES list.") + (0.00 ":cadmium.libera.chat 324 tester #chan +nt") + (0.01 ":cadmium.libera.chat 329 tester #chan 1621432263")) + +((privmsg-before 10 "PRIVMSG #chan :ready before") + (0.02 ":Chad!~u@ggpg6r3a68wak.irc PRIVMSG #chan before") + (0.00 ":Chad!~u@ggpg6r3a68wak.irc MODE #chan +Qu")) + +((privmsg-key 10 "PRIVMSG #chan :ready key") + (0.02 ":Chad!~u@ggpg6r3a68wak.irc PRIVMSG #chan :doing key") + (0.00 ":Chad!~u@ggpg6r3a68wak.irc MODE #chan +k hunter2")) + +((privmsg-limit 10 "PRIVMSG #chan :ready limit") + (0.02 ":Chad!~u@ggpg6r3a68wak.irc PRIVMSG #chan :doing limit") + (0.00 ":Chad!~u@ggpg6r3a68wak.irc MODE #chan +l 3")) + +((privmsg-drop 10 "PRIVMSG #chan :ready drop") + (0.02 ":Chad!~u@ggpg6r3a68wak.irc PRIVMSG #chan dropping") + (0.00 ":Chad!~u@ggpg6r3a68wak.irc MODE #chan -lu") + (0.00 ":Chad!~u@ggpg6r3a68wak.irc MODE #chan -Qk *") + (0.02 ":Chad!~u@ggpg6r3a68wak.irc PRIVMSG #chan after")) + +((drop 0 DROP)) -- 2.41.0