From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: "J.P." Newsgroups: gmane.emacs.bugs,gmane.emacs.erc.general Subject: bug#67220: 30.0.50; ERC 5.6: Prefer parameter-driven MODE processing in ERC Date: Wed, 15 Nov 2023 18:13:53 -0800 Message-ID: <87pm0aphr2.fsf@neverwas.me> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="21069"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: emacs-erc@gnu.org To: 67220@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Nov 16 03:15:46 2023 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1r3Ruz-0005KU-Gl for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 16 Nov 2023 03:15:46 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1r3Rua-00065v-7Z; Wed, 15 Nov 2023 21:15:22 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1r3RuK-00064y-Eq; Wed, 15 Nov 2023 21:15:06 -0500 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1r3RuJ-00014o-0M; Wed, 15 Nov 2023 21:15:04 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1r3RuI-0001Va-3k; Wed, 15 Nov 2023 21:15:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: "J.P." Original-Sender: "Debbugs-submit" Resent-CC: emacs-erc@gnu.org, bug-gnu-emacs@gnu.org Resent-Date: Thu, 16 Nov 2023 02:15:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 67220 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org X-Debbugs-Original-Xcc: emacs-erc@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.17001008785742 (code B ref -1); Thu, 16 Nov 2023 02:15:01 +0000 Original-Received: (at submit) by debbugs.gnu.org; 16 Nov 2023 02:14:38 +0000 Original-Received: from localhost ([127.0.0.1]:54256 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1r3Rtr-0001UW-R0 for submit@debbugs.gnu.org; Wed, 15 Nov 2023 21:14:38 -0500 Original-Received: from lists.gnu.org ([2001:470:142::17]:43098) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1r3Rto-0001UH-Ig for submit@debbugs.gnu.org; Wed, 15 Nov 2023 21:14:34 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1r3Rtj-000637-Hg for bug-gnu-emacs@gnu.org; Wed, 15 Nov 2023 21:14:27 -0500 Original-Received: from mail-108-mta247.mxroute.com ([136.175.108.247]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1r3Rte-0008Fv-Rp for bug-gnu-emacs@gnu.org; Wed, 15 Nov 2023 21:14:27 -0500 Original-Received: from filter006.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta247.mxroute.com (ZoneMTA) with ESMTPSA id 18bd5e86819000190b.001 for (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384); Thu, 16 Nov 2023 02:14:19 +0000 X-Zone-Loop: 857f973e0b730b0d506868cada3e1e70fac7595cca55 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=neverwas.me ; s=x; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From:Sender: Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=e6faBIMtCnhNPMMGLkblRNSVgMdUzHUsWECyuzN28KE=; b=Spb5ptWGpjlJdMvkrn5tkSP3S0 BhaRFf+WY4P4x+8ZwEGGCQuOVmUhDBbid39oecKtrhT8nsI+/pH8QkUKSV7TIeuCzFpMT168wTE3L JUz6aM5Y3J9l23396J/B4JhQLBZE52CZOOuixr2Qt1YTaBZkYfd9RGg2xF/sEKhEEVvSRXBxyRAWv 0il/91bOc4ZEIjMzos/JVTNWf7NudcpZab0L4ARW2Shzzv26K+MKRoH8wpxX5jur5mmDLz/HjfGnL 0XO6dYL2MOWXk8J92+9rD/gczcEau4m4pSleh3jQwgewDP1osR4Q3vk8GmjouoozLyWnMCSiouINz YFvXAghA==; X-Authenticated-Id: masked@neverwas.me Received-SPF: pass client-ip=136.175.108.247; envelope-from=jp@neverwas.me; helo=mail-108-mta247.mxroute.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:274432 gmane.emacs.erc.general:2307 Archived-At: --=-=-= Content-Type: text/plain Tags: patch In the early days of IRC, parsing a "MODE" command from the server was comparatively straightforward. There were a few well known letters, some taking a single argument, and a standard set of status prefixes. But somewhere along the line, things got more complicated, and it seems ERC never got the memo. While it may appear obvious that sticking to a hard-coded, heuristics based approach doesn't really accommodate ERC's core tenet of extensibility, the risk of shifting toward something more parameter driven was probably never justifiable without a vocal demand. Or an obvious bug. >From emacs -Q: 1. Connect to Libera.Chat 2. Create ##mychan 3. /mode ##mychan +Qu debugger entered--Lisp error: (wrong-type-argument char-or-string-p nil) erc-downcase(nil) erc-update-current-channel-member(nil nil nil nil nil nil nil on ...) erc-update-channel-member("#libera" nil nil nil nil nil nil nil on) erc-update-modes("##mychan" "+Qu" "mynick" "user/foo" "Hi!") The issue here is that ERC doesn't account for ISUPPORT parameters when parsing MODE commands and dispatching handlers. Instead, it simply assumes that +q (or +Q) means someone has just been promoted to a channel owner. I'll admit that although I've been aware of this basic issue for quite some time, I've been hesitant to cross this bridge until 5.7+ because of the potential pitfalls involved. In any case, with a concrete bug having surfaced (courtesy of Corwin), the issue has been forced, and it's one that can't really be papered over responsibly just to avoid holding up the current release. My proposed means of addressing this is mainly contained in the last of the attached patches. The approach comes down to rewriting the most important bits and providing adapters to reroute the rest accordingly. Comments welcome, as always. Thanks. In GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.38, cairo version 1.17.6) of 2023-11-15 built on localhost Repository revision: ff1f82cbe3fa9aee354581f2798faaae7163ea44 Repository branch: master Windowing system distributor 'The X.Org Foundation', version 11.0.12014000 System Description: Fedora Linux 37 (Workstation Edition) Configured using: 'configure --enable-check-lisp-object-type --enable-checking=yes,glyphs 'CFLAGS=-O0 -g3' PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig' Configured features: ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 M17N_FLT MODULES NOTIFY INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM GTK3 ZLIB Important settings: value of $LANG: en_US.UTF-8 value of $XMODIFIERS: @im=ibus locale-coding-system: utf-8-unix Major mode: Lisp Interaction Minor modes in effect: tooltip-mode: t global-eldoc-mode: t eldoc-mode: t show-paren-mode: t electric-indent-mode: t mouse-wheel-mode: t tool-bar-mode: t menu-bar-mode: t file-name-shadow-mode: t global-font-lock-mode: t font-lock-mode: t blink-cursor-mode: t minibuffer-regexp-mode: t line-number-mode: t indent-tabs-mode: t transient-mark-mode: t auto-composition-mode: t auto-encryption-mode: t auto-compression-mode: t Load-path shadows: None found. Features: (shadow sort mail-extr emacsbug message mailcap yank-media puny dired dired-loaddefs rfc822 mml mml-sec epa epg rfc6068 epg-config gnus-util text-property-search time-date mm-decode mm-bodies mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader sendmail rfc2047 rfc2045 ietf-drums mm-util mail-prsvr mail-utils erc derived auth-source eieio eieio-core password-cache json map format-spec erc-backend erc-networks easy-mmode byte-opt bytecomp byte-compile erc-common inline erc-compat cl-seq cl-macs gv pcase rx subr-x cl-loaddefs cl-lib erc-loaddefs rmc iso-transl tooltip cconv eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type elisp-mode mwheel term/x-win x-win term/common-win x-dnd touch-screen tool-bar dnd fontset image regexp-opt fringe tabulated-list replace newcomment text-mode lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow isearch easymenu timer select scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors frame minibuffer nadvice seq simple cl-generic indonesian philippine cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech european ethiopic indian cyrillic chinese composite emoji-zwj charscript charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button loaddefs theme-loaddefs faces cus-face macroexp files window text-properties overlay sha1 md5 base64 format env code-pages mule custom widget keymap hashtable-print-readable backquote threads dbusbind inotify lcms2 dynamic-setting system-font-setting font-render-setting cairo gtk x-toolkit xinput2 x multi-tty move-toolbar make-network-process emacs) Memory information: ((conses 16 123590 9232) (symbols 48 10137 0) (strings 32 24791 2241) (string-bytes 1 837965) (vectors 16 14517) (vector-slots 8 204449 15354) (floats 8 24 31) (intervals 56 245 0) (buffers 984 10)) --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-5.6-Use-caching-variant-of-erc-parse-prefix-internal.patch >From 77ac1ba798d1896408fab2e25e57efd32596aa18 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 13 Nov 2023 18:24:59 -0800 Subject: [PATCH 1/3] [5.6] Use caching variant of erc-parse-prefix internally * lisp/erc/erc.el (erc-parse-prefix): Rework slightly for readability. (erc--parsed-prefix): New variable and struct for caching the result of `erc-parse-prefix' locally. (erc--parse-prefix): New function to cache reversed result of `erc-parse-prefix'. * test/lisp/erc/erc-tests.el (erc--parse-prefix): New test. --- lisp/erc/erc.el | 63 ++++++++++++++++++++++++++++---------- test/lisp/erc/erc-tests.el | 39 +++++++++++++++++++++++ 2 files changed, 86 insertions(+), 16 deletions(-) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index edcfcf085e6..bbbbc405526 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -6192,22 +6192,53 @@ erc-channel-end-receiving-names (defun erc-parse-prefix () "Return an alist of valid prefix character types and their representations. -Example: (operator) o => @, (voiced) v => +." - (let ((str (or (erc-with-server-buffer (erc--get-isupport-entry 'PREFIX t)) - ;; provide a sane default - "(qaohv)~&@%+")) - types chars) - (when (string-match "^(\\([^)]+\\))\\(.+\\)$" str) - (setq types (match-string 1 str) - chars (match-string 2 str)) - (let ((len (min (length types) (length chars))) - (i 0) - (alist nil)) - (while (< i len) - (setq alist (cons (cons (elt types i) (elt chars i)) - alist)) - (setq i (1+ i))) - alist)))) +For example, if the current ISUPPORT \"PREFIX\" is \"(ov)@+\", +return an alist `equal' to ((?v . ?+) (?o . ?@)). For historical +reasons, ensure the ordering of the returned alist is opposite +that of the advertised parameter." + (let* ((str (or (erc--get-isupport-entry 'PREFIX t) "(qaohv)~&@%+")) + (i 0) + (j (string-search ")" str)) + collected) + (when j + (while-let ((u (aref str (cl-incf i))) + ((not (= ?\) u)))) + (push (cons u (aref str (cl-incf j))) collected))) + collected)) + +(defvar-local erc--parsed-prefix nil + "Cons of latest advertised PREFIX and its parsed alist. +Only usable for the current server session.") + +;; As of ERC 5.6, `erc-channel-receive-names' is the only caller, and +;; it runs infrequently. In the future, extensions, like +;; `multi-prefix', may benefit more from a two-way translation table. +(cl-defstruct erc--parsed-prefix + "Server-local channel-membership-prefix data." + (key nil :type (or null string)) + (letters "qaohv" :type string) + (statuses "~&@%+" :type string) + (alist nil :type (list-of cons))) + +(defun erc--parse-prefix () + "Return (possibly cached) status prefix translation alist for the server. +Ensure the returned value describes the most recent \"PREFIX\" +ISUPPORT parameter received from the current server and that the +original ordering is preserved." + (erc-with-server-buffer + (let ((key (erc--get-isupport-entry 'PREFIX))) + (or (and key + erc--parsed-prefix + (eq (cdr key) (erc--parsed-prefix-key erc--parsed-prefix)) + (erc--parsed-prefix-alist erc--parsed-prefix)) + (let ((alist (nreverse (erc-parse-prefix)))) + (setq erc--parsed-prefix + (make-erc--parsed-prefix + :key (cdr key) + :letters (apply #'string (map-keys alist)) + :statuses (apply #'string (map-values alist)) + :alist alist)) + alist))))) (defcustom erc-channel-members-changed-hook nil "This hook is called every time the variable `channel-members' changes. diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el index e7422d330c0..28bf1fbcccc 100644 --- a/test/lisp/erc/erc-tests.el +++ b/test/lisp/erc/erc-tests.el @@ -643,6 +643,45 @@ erc-parse-user (should (equal '("de" "" "fg@xy") (erc-parse-user "abc\nde!fg@xy")))))) +(ert-deftest erc--parse-prefix () + (erc-mode) + (erc-tests--set-fake-server-process "sleep" "1") + (setq erc--isupport-params (make-hash-table) + erc-server-parameters '(("PREFIX" . "(Yqaohv)!~&@%+"))) + + (let ((proc erc-server-process) + (expected '((?Y . ?!) (?q . ?~) (?a . ?&) + (?o . ?@) (?h . ?%) (?v . ?+))) + cached) + + (with-temp-buffer + (erc-mode) + (setq erc-server-process proc) + (should (equal expected (erc--parse-prefix)))) + + (should (equal expected (erc--parsed-prefix-alist erc--parsed-prefix))) + (setq cached erc--parsed-prefix) + (should (equal cached + #s(erc--parsed-prefix ("(Yqaohv)!~&@%+") + "Yqaohv" "!~&@%+" + ((?Y . ?!) (?q . ?~) (?a . ?&) + (?o . ?@) (?h . ?%) (?v . ?+))))) + ;; Second target buffer reuses cached value. + (with-temp-buffer + (erc-mode) + (setq erc-server-process proc) + (should (eq (erc--parsed-prefix-alist cached) (erc--parse-prefix)))) + + ;; New value computed when cache broken. + (puthash 'PREFIX (list "(Yqaohv)!~&@%+") erc--isupport-params) + (with-temp-buffer + (erc-mode) + (setq erc-server-process proc) + (should-not (eq (erc--parsed-prefix-alist cached) (erc--parse-prefix))) + (should (equal (erc--parsed-prefix-alist + (erc-with-server-buffer erc--parsed-prefix)) + expected))))) + (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"))) -- 2.41.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0002-5.6-Fix-ISUPPORT-cache-misses-in-ERC-target-buffers.patch >From cb01fdb193755cf470bc1193ca89168f47d40641 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 13 Nov 2023 18:24:59 -0800 Subject: [PATCH 2/3] [5.6] Fix ISUPPORT cache misses in ERC target buffers * lisp/erc/erc-backend.el (erc--get-isupport-entry): Check server for `erc-server-parameters' if it's empty in the current buffer. * test/lisp/erc/erc-scenarios-display-message.el: Remove stray `require'. --- lisp/erc/erc-backend.el | 4 +++- test/lisp/erc/erc-scenarios-display-message.el | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el index 9281c107d06..2242b40e9a4 100644 --- a/lisp/erc/erc-backend.el +++ b/lisp/erc/erc-backend.el @@ -2096,7 +2096,9 @@ erc--get-isupport-entry (erc-with-server-buffer erc--isupport-params))) (value (with-memoization (gethash key table) (when-let ((v (assoc (symbol-name key) - erc-server-parameters))) + (or erc-server-parameters + (erc-with-server-buffer + erc-server-parameters))))) (if (cdr v) (erc--parse-isupport-value (cdr v)) '--empty--))))) diff --git a/test/lisp/erc/erc-scenarios-display-message.el b/test/lisp/erc/erc-scenarios-display-message.el index 51bdf305ad5..5751a32212d 100644 --- a/test/lisp/erc/erc-scenarios-display-message.el +++ b/test/lisp/erc/erc-scenarios-display-message.el @@ -59,6 +59,4 @@ erc-scenarios-display-message--multibuf (erc-cmd-QUIT ""))) -(eval-when-compile (require 'erc-join)) - ;;; erc-scenarios-display-message.el ends here -- 2.41.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0003-5.6-Rework-MODE-handling-in-ERC.patch >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 --=-=-=--