mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-02-16 17:24:23 +00:00
Actually derive channel membership from PREFIX in ERC
* lisp/erc/erc-backend.el (erc--with-isupport-data): Add comment for possibly superior alternate implementation. * lisp/erc/erc-common.el (erc--get-isupport-entry): Use helper to initialize traditional prefix slots in overridden well-known constructor. (erc--parsed-prefix): Reverse order of characters in the `letters' and `statuses' slots, in their defaults and also their definitions. (erc--strpos): New function, a utility for finding a single character in a string. * lisp/erc/erc.el (erc--define-channel-user-status-compat-getter): Modify to query advertised value for associated mode letter at runtime instead of baking it in. (erc-channel-user-voice, erc-channel-user-halfop, erc-channel-user-op, erc-channel-user-admin, erc-channel-user-owner): Supply second argument for fallback mode letter. (erc--cusr-status-p, erc--cusr-change-status): New functions for querying and modifying `erc-channel-user' statuses. (erc-send-input-line): Update speaker time in own nick's `erc-channel-member' entry. (erc-get-channel-membership-prefix): Adapt code to prefer advertised prefix for mode letter. (erc--parsed-prefix): Save "reversed" `letters' and `statuses' so that they're ordered from lowest to highest semantically. (erc--get-prefix-flag, erc--init-cusr-fallback-status, erc--compute-cusr-fallback-status): New functions for retrieving internal prefix values and massaging hard-coded traditional prefixes so they're compatible with existing `erc-channel-member' update code. (erc--partition-prefixed-names): New function, separated for testing and for conversion to a generic in the future when ERC supports extensions that list member rolls in a different format. (erc-channel-receive-names): Refactor to use new status-aware update and init workhorse functions for updating and initializing a `erc-channel-members' entry. (erc--create-current-channel-member): New "status-aware" function comprising the `addp' path of `erc-update-current-channel-member'. (erc--update-current-channel-member): New "status-aware" function comprising the "update" path of `erc-update-current-channel-member', which ran when an existing `erc-channel-members' entry for the queried nick was found. (erc-update-current-channel-member): Split code body into two constituent functions, both for readability and for usability, so callers can more explicitly request the desired operation in a "status-aware" manner. (erc--update-membership-prefix): Remove unused function, originally meant to be new in ERC 5.6. (erc--process-channel-modes): Call `erc--cusr-change-status' instead of `erc--update-membership-prefix'. (erc--shuffle-nuh-nickward): New utility function to ensure code like `erc--partition-prefixed-names' can use `erc--parse-nuh' in a practical and relatively convenient way in the near future. * test/lisp/erc/erc-scenarios-base-chan-modes.el (erc-scenarios-base-chan-modes--speaker-status): New test. * test/lisp/erc/erc-tests.el (erc--parsed-prefix): Reverse expected order of various slot values in `erc--parsed-prefix' objects. (erc--get-prefix-flag, erc--init-cusr-fallback-status, erc--compute-cusr-fallback-status, erc--cusr-status-p, erc--cusr-change-status): New tests. (erc--update-channel-modes, erc-process-input-line): Use newly available utilities imported from common library. * test/lisp/erc/resources/base/modes/speaker-status.eld: New file. (Bug#67220)
This commit is contained in:
parent
d85f561da0
commit
aedc8b55bf
6 changed files with 470 additions and 167 deletions
|
|
@ -2201,7 +2201,9 @@ primitive value."
|
|||
;; While it's better to depend on interfaces than specific types,
|
||||
;; using `cl-struct-slot-value' or similar to extract a known slot at
|
||||
;; runtime would incur a small "ducktyping" tax, which should probably
|
||||
;; be avoided when running dozens of times per incoming message.
|
||||
;; be avoided when running hundreds of times per incoming message.
|
||||
;; Instead of separate keys per data type, we could increment a
|
||||
;; counter whenever a new 005 arrives.
|
||||
(defmacro erc--with-isupport-data (param var &rest body)
|
||||
"Return structured data stored in VAR for \"ISUPPORT\" PARAM.
|
||||
Expect VAR's value to be an instance of `erc--isupport-data'. If
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
(defvar erc-session-server)
|
||||
|
||||
(declare-function erc--get-isupport-entry "erc-backend" (key &optional single))
|
||||
(declare-function erc--init-cusr-fallback-status "erc" (v h o a q))
|
||||
(declare-function erc-get-buffer "erc" (target &optional proc))
|
||||
(declare-function erc-server-buffer "erc" nil)
|
||||
(declare-function widget-apply-action "wid-edit" (widget &optional event))
|
||||
|
|
@ -76,11 +77,11 @@
|
|||
make-erc-channel-user
|
||||
( &key voice halfop op admin owner
|
||||
last-message-time
|
||||
&aux (status (+ (if voice 1 0)
|
||||
(if halfop 2 0)
|
||||
(if op 4 0)
|
||||
(if admin 8 0)
|
||||
(if owner 16 0)))))
|
||||
&aux (status
|
||||
(if (or voice halfop op admin owner)
|
||||
(erc--init-cusr-fallback-status
|
||||
voice halfop op admin owner)
|
||||
0))))
|
||||
:named)
|
||||
"Object containing channel-specific data for a single user."
|
||||
;; voice halfop op admin owner
|
||||
|
|
@ -140,9 +141,12 @@ For use with the macro `erc--with-isupport-data'."
|
|||
(cl-defstruct (erc--parsed-prefix (:include erc--isupport-data))
|
||||
"Server-local data for recognized membership-status prefixes.
|
||||
Derived from the advertised \"PREFIX\" ISUPPORT parameter."
|
||||
(letters "qaohv" :type string)
|
||||
(statuses "~&@%+" :type string)
|
||||
(alist nil :type (list-of cons)))
|
||||
( letters "vhoaq" :type string
|
||||
:documentation "Status letters ranked lowest to highest.")
|
||||
( statuses "+%@&~" :type string
|
||||
:documentation "Status prefixes ranked lowest to highest.")
|
||||
( alist nil :type (list-of cons)
|
||||
:documentation "Alist of letters-prefix pairs."))
|
||||
|
||||
(cl-defstruct (erc--channel-mode-types (:include erc--isupport-data))
|
||||
"Server-local \"CHANMODES\" data."
|
||||
|
|
@ -594,6 +598,10 @@ the resulting variables will end up with more useful doc strings."
|
|||
(debug (symbolp [&rest [keywordp form]] &rest (symbolp . form))))
|
||||
`(erc--define-catalog ,language ,entries))
|
||||
|
||||
(define-inline erc--strpos (char string)
|
||||
"Return position of CHAR in STRING or nil if not found."
|
||||
(inline-quote (string-search (string ,char) ,string)))
|
||||
|
||||
(defmacro erc--doarray (spec &rest body)
|
||||
"Map over ARRAY, running BODY with VAR bound to iteration element.
|
||||
Behave more or less like `seq-doseq', but tailor operations for
|
||||
|
|
|
|||
360
lisp/erc/erc.el
360
lisp/erc/erc.el
|
|
@ -598,28 +598,52 @@ Removes all users in the current channel. This is called by
|
|||
erc-channel-users)
|
||||
(clrhash erc-channel-users)))
|
||||
|
||||
(defmacro erc--define-channel-user-status-compat-getter (name n)
|
||||
(defmacro erc--define-channel-user-status-compat-getter (name c d)
|
||||
"Define a gv getter for historical `erc-channel-user' status slot NAME.
|
||||
Expect NAME to be a string and N to be its associated power-of-2
|
||||
\"enumerated flag\" integer."
|
||||
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."
|
||||
`(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)))
|
||||
(,'\`(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))))
|
||||
(logior val n)
|
||||
(logand val (lognot n))))
|
||||
,',v))))))
|
||||
(= ,n (logand ,n (erc-channel-user-status u)))))
|
||||
(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" 1)
|
||||
(erc--define-channel-user-status-compat-getter "halfop" 2)
|
||||
(erc--define-channel-user-status-compat-getter "op" 4)
|
||||
(erc--define-channel-user-status-compat-getter "admin" 8)
|
||||
(erc--define-channel-user-status-compat-getter "owner" 16)
|
||||
(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."
|
||||
|
|
@ -3900,6 +3924,10 @@ for other purposes.")
|
|||
|
||||
(defun erc-send-input-line (target line &optional force)
|
||||
"Send LINE to TARGET."
|
||||
(when-let ((target)
|
||||
(cmem (erc-get-channel-member (erc-current-nick))))
|
||||
(setf (erc-channel-user-last-message-time (cdr cmem))
|
||||
(erc-compat--current-lisp-time)))
|
||||
(when (and (not erc--allow-empty-outgoing-lines-p) (string= line "\n"))
|
||||
(setq line " \n"))
|
||||
(erc-message "PRIVMSG" (concat target " " line) force))
|
||||
|
|
@ -6141,17 +6169,15 @@ return a possibly empty string."
|
|||
(catch 'done
|
||||
(pcase-dolist (`(,letter . ,pfx)
|
||||
(erc--parsed-prefix-alist pfx-obj))
|
||||
(pcase letter
|
||||
((and ?q (guard (erc-channel-user-owner nick-or-cusr)))
|
||||
(throw 'done (propertize (string pfx) 'help-echo "owner")))
|
||||
((and ?a (guard (erc-channel-user-admin nick-or-cusr)))
|
||||
(throw 'done (propertize (string pfx) 'help-echo "admin")))
|
||||
((and ?o (guard (erc-channel-user-op nick-or-cusr)))
|
||||
(throw 'done (propertize (string pfx) 'help-echo "operator")))
|
||||
((and ?h (guard (erc-channel-user-halfop nick-or-cusr)))
|
||||
(throw 'done (propertize (string pfx) 'help-echo "half-op")))
|
||||
((and ?v (guard (erc-channel-user-voice nick-or-cusr)))
|
||||
(throw 'done (propertize (string pfx) 'help-echo "voice")))))
|
||||
(when (erc--cusr-status-p nick-or-cusr letter)
|
||||
(throw 'done
|
||||
(pcase letter
|
||||
(?q (propertize (string pfx) 'help-echo "owner"))
|
||||
(?a (propertize (string pfx) 'help-echo "admin"))
|
||||
(?o (propertize (string pfx) 'help-echo "operator"))
|
||||
(?h (propertize (string pfx) 'help-echo "half-op"))
|
||||
(?v (propertize (string pfx) 'help-echo "voice"))
|
||||
(_ (string pfx))))))
|
||||
"")))
|
||||
(t
|
||||
(cond ((erc-channel-user-owner nick-or-cusr)
|
||||
|
|
@ -6763,12 +6789,52 @@ parameter advertised by the current server, with the original
|
|||
ordering intact. If no such parameter has yet arrived, return a
|
||||
stand-in from the fallback value \"(qaohv)~&@%+\"."
|
||||
(erc--with-isupport-data PREFIX erc--parsed-prefix
|
||||
(let ((alist (nreverse (erc-parse-prefix))))
|
||||
(let ((alist (erc-parse-prefix)))
|
||||
(make-erc--parsed-prefix
|
||||
:key key
|
||||
:letters (apply #'string (map-keys alist))
|
||||
:statuses (apply #'string (map-values alist))
|
||||
:alist alist))))
|
||||
:alist (nreverse alist)))))
|
||||
|
||||
(defun erc--get-prefix-flag (char &optional parsed-prefix from-prefix-p)
|
||||
"Return numeric rank for CHAR or nil if unknown.
|
||||
For example, given letters \"qaohv\" return 1 for ?v, 2 for ?h,
|
||||
and 4 for ?o, etc. If given, expect PARSED-PREFIX to be a
|
||||
`erc--parse-prefix' object. With FROM-PREFIX-P, expect CHAR to
|
||||
be a prefix instead."
|
||||
(and-let* ((obj (or parsed-prefix (erc--parsed-prefix)))
|
||||
(pos (erc--strpos char (if from-prefix-p
|
||||
(erc--parsed-prefix-statuses obj)
|
||||
(erc--parsed-prefix-letters obj)))))
|
||||
(ash 1 pos)))
|
||||
|
||||
(defun erc--init-cusr-fallback-status (voice halfop op admin owner)
|
||||
"Return channel-membership based on traditional status semantics.
|
||||
Massage boolean switches VOICE, HALFOP, OP, ADMIN, and OWNER into
|
||||
an internal numeric value suitable for the `status' slot of a new
|
||||
`erc-channel-user' object."
|
||||
(let ((pfx (erc--parsed-prefix)))
|
||||
(+ (if voice (if pfx (or (erc--get-prefix-flag ?v pfx) 0) 1) 0)
|
||||
(if halfop (if pfx (or (erc--get-prefix-flag ?h pfx) 0) 2) 0)
|
||||
(if op (if pfx (or (erc--get-prefix-flag ?o pfx) 0) 4) 0)
|
||||
(if admin (if pfx (or (erc--get-prefix-flag ?a pfx) 0) 8) 0)
|
||||
(if owner (if pfx (or (erc--get-prefix-flag ?q pfx) 0) 16) 0))))
|
||||
|
||||
(defun erc--compute-cusr-fallback-status (current v h o a q)
|
||||
"Return current channel membership after toggling V H O A Q as requested.
|
||||
Assume `erc--parsed-prefix' is non-nil in the current buffer.
|
||||
Expect status switches V, H, O, A, Q, when non-nil, to be the
|
||||
symbol `on' or `off'. Return an internal numeric value suitable
|
||||
for the `status' slot of an `erc-channel-user' object."
|
||||
(let (on off)
|
||||
(when v (push (or (erc--get-prefix-flag ?v) 0) (if (eq v 'on) on off)))
|
||||
(when h (push (or (erc--get-prefix-flag ?h) 0) (if (eq h 'on) on off)))
|
||||
(when o (push (or (erc--get-prefix-flag ?o) 0) (if (eq o 'on) on off)))
|
||||
(when a (push (or (erc--get-prefix-flag ?a) 0) (if (eq a 'on) on off)))
|
||||
(when q (push (or (erc--get-prefix-flag ?q) 0) (if (eq q 'on) on off)))
|
||||
(when on (setq current (apply #'logior current on)))
|
||||
(when off (setq current (apply #'logand current (mapcar #'lognot off)))))
|
||||
current)
|
||||
|
||||
(defcustom erc-channel-members-changed-hook nil
|
||||
"This hook is called every time the variable `channel-members' changes.
|
||||
|
|
@ -6776,48 +6842,40 @@ The buffer where the change happened is current while this hook is called."
|
|||
:group 'erc-hooks
|
||||
:type 'hook)
|
||||
|
||||
(defun erc-channel-receive-names (names-string)
|
||||
"This function is for internal use only.
|
||||
(defun erc--partition-prefixed-names (name)
|
||||
"From NAME, return a list of (STATUS NICK LOGIN HOST).
|
||||
Expect NAME to be a prefixed name, like @bob."
|
||||
(unless (string-empty-p name)
|
||||
(let* ((status (erc--get-prefix-flag (aref name 0) nil 'from-prefix-p))
|
||||
(nick (if status (substring name 1) name)))
|
||||
(unless (string-empty-p nick)
|
||||
(list status nick nil nil)))))
|
||||
|
||||
Update `erc-channel-users' according to NAMES-STRING.
|
||||
NAMES-STRING is a string listing some of the names on the
|
||||
channel."
|
||||
(let* ((prefix (erc--parsed-prefix-alist (erc--parsed-prefix)))
|
||||
(voice-ch (cdr (assq ?v prefix)))
|
||||
(op-ch (cdr (assq ?o prefix)))
|
||||
(hop-ch (cdr (assq ?h prefix)))
|
||||
(adm-ch (cdr (assq ?a prefix)))
|
||||
(own-ch (cdr (assq ?q prefix)))
|
||||
(names (delete "" (split-string names-string)))
|
||||
name op voice halfop admin owner)
|
||||
(let ((erc-channel-members-changed-hook nil))
|
||||
(dolist (item names)
|
||||
(let ((updatep t)
|
||||
(ch (aref item 0)))
|
||||
(setq name item op 'off voice 'off halfop 'off admin 'off owner 'off)
|
||||
(if (rassq ch prefix)
|
||||
(if (= (length item) 1)
|
||||
(setq updatep nil)
|
||||
(setq name (substring item 1))
|
||||
(setf (pcase ch
|
||||
((pred (eq voice-ch)) voice)
|
||||
((pred (eq hop-ch)) halfop)
|
||||
((pred (eq op-ch)) op)
|
||||
((pred (eq adm-ch)) admin)
|
||||
((pred (eq own-ch)) owner)
|
||||
(_ (message "Unknown prefix char `%S'" ch) voice))
|
||||
'on)))
|
||||
(when updatep
|
||||
(defun erc-channel-receive-names (names-string)
|
||||
"Update `erc-channel-members' from NAMES-STRING.
|
||||
Expect NAMES-STRING to resemble the trailing argument of a 353
|
||||
RPL_NAMREPLY. Call internal handlers for parsing individual
|
||||
names, whose expected composition may differ depending on enabled
|
||||
extensions."
|
||||
(let ((names (delete "" (split-string names-string)))
|
||||
(erc-channel-members-changed-hook nil))
|
||||
(dolist (name names)
|
||||
(when-let ((args (erc--partition-prefixed-names name)))
|
||||
(pcase-let* ((`(,status ,nick ,login ,host) args)
|
||||
(cmem (erc-get-channel-user nick)))
|
||||
(progn
|
||||
;; If we didn't issue the NAMES request (consider two clients
|
||||
;; talking to an IRC proxy), `erc-channel-begin-receiving-names'
|
||||
;; will not have been called, so we have to do it here.
|
||||
(unless erc-channel-new-member-names
|
||||
(erc-channel-begin-receiving-names))
|
||||
(puthash (erc-downcase name) t
|
||||
erc-channel-new-member-names)
|
||||
(erc-update-current-channel-member
|
||||
name name t voice halfop op admin owner)))))
|
||||
(run-hooks 'erc-channel-members-changed-hook)))
|
||||
(puthash (erc-downcase nick) t erc-channel-new-member-names)
|
||||
(if cmem
|
||||
(erc--update-current-channel-member cmem status nil
|
||||
nick host login)
|
||||
(erc--create-current-channel-member nick status nil
|
||||
nick host login)))))))
|
||||
(run-hooks 'erc-channel-members-changed-hook))
|
||||
|
||||
(defun erc-update-user-nick (nick &optional new-nick
|
||||
host login full-name info)
|
||||
|
|
@ -6869,17 +6927,85 @@ which USER is a member, and t is returned."
|
|||
(run-hooks 'erc-channel-members-changed-hook))))))
|
||||
changed))
|
||||
|
||||
(defun erc--create-current-channel-member
|
||||
(nick status timep &optional new-nick host login full-name info)
|
||||
"Add an `erc-channel-member' entry for NICK.
|
||||
Create a new `erc-server-users' entry if necessary, and ensure
|
||||
`erc-channel-members-changed-hook' runs exactly once, regardless.
|
||||
Pass STATUS to the `erc-channel-user' constructor. With TIMEP,
|
||||
assume NICK has just spoken, and initialize `last-message-time'.
|
||||
Pass NEW-NICK, HOST, LOGIN, FULL-NAME, and INFO to
|
||||
`erc-update-user' if a server user exists and otherwise to the
|
||||
`erc-server-user' constructor."
|
||||
(cl-assert (null (erc-get-channel-member nick)))
|
||||
(let* ((user-changed-p nil)
|
||||
(down (erc-downcase nick))
|
||||
(user (gethash down (erc-with-server-buffer erc-server-users))))
|
||||
(if user
|
||||
(progn
|
||||
(cl-pushnew (current-buffer) (erc-server-user-buffers user))
|
||||
;; Update *after* ^ so hook has chance to run.
|
||||
(setf user-changed-p (erc-update-user user new-nick host login
|
||||
full-name info)))
|
||||
(erc-add-server-user nick
|
||||
(setq user (make-erc-server-user
|
||||
:nickname (or new-nick nick)
|
||||
:host host
|
||||
:full-name full-name
|
||||
:login login
|
||||
:info nil
|
||||
:buffers (list (current-buffer))))))
|
||||
(let ((cusr (erc-channel-user--make
|
||||
:status (or status 0)
|
||||
:last-message-time (and timep
|
||||
(erc-compat--current-lisp-time)))))
|
||||
(puthash down (cons user cusr) erc-channel-users))
|
||||
;; An existing `cusr' was changed or a new one was added, and
|
||||
;; `user' was not updated, though possibly just created (since
|
||||
;; `erc-update-user' runs this same hook in all a user's buffers).
|
||||
(unless user-changed-p
|
||||
(run-hooks 'erc-channel-members-changed-hook))
|
||||
t))
|
||||
|
||||
(defun erc--update-current-channel-member (cmem status timep &rest user-args)
|
||||
"Update existing `erc-channel-member' entry.
|
||||
Set the `status' slot of the entry's `erc-channel-user' side to
|
||||
STATUS and, with TIMEP, update its `last-message-time'. When
|
||||
actual changes are made, run `erc-channel-members-changed-hook',
|
||||
and return non-nil."
|
||||
(cl-assert cmem)
|
||||
(let ((cusr (cdr cmem))
|
||||
(user (car cmem))
|
||||
cusr-changed-p user-changed-p)
|
||||
(when (and status (/= status (erc-channel-user-status cusr)))
|
||||
(setf (erc-channel-user-status cusr) status
|
||||
cusr-changed-p t))
|
||||
(when timep
|
||||
(setf (erc-channel-user-last-message-time cusr)
|
||||
(erc-compat--current-lisp-time)))
|
||||
;; Ensure `erc-channel-members-changed-hook' runs on change.
|
||||
(cl-assert (memq (current-buffer) (erc-server-user-buffers user)))
|
||||
(setq user-changed-p (apply #'erc-update-user user user-args))
|
||||
;; An existing `cusr' was changed or a new one was added, and
|
||||
;; `user' was not updated, though possibly just created (since
|
||||
;; `erc-update-user' runs this same hook in all a user's buffers).
|
||||
(when (and cusr-changed-p (null user-changed-p))
|
||||
(run-hooks 'erc-channel-members-changed-hook))
|
||||
(erc-log (format "update-member: user = %S, cusr = %S" user cusr))
|
||||
(or cusr-changed-p user-changed-p)))
|
||||
|
||||
(defun erc-update-current-channel-member
|
||||
(nick new-nick &optional addp voice halfop op admin owner host login full-name info
|
||||
update-message-time)
|
||||
(nick new-nick &optional addp voice halfop op admin owner host login
|
||||
full-name info update-message-time)
|
||||
"Update or create entry for NICK in current `erc-channel-members' table.
|
||||
With ADDP, ensure an entry exists. If one already does, call
|
||||
`erc-update-user' to handle updates to HOST, LOGIN, FULL-NAME,
|
||||
INFO, and NEW-NICK. Expect any non-nil membership status
|
||||
switches among VOICE, HALFOP, OP, ADMIN, and OWNER to be the
|
||||
symbol `on' or `off' when needing to influence a new or existing
|
||||
`erc-channel-user' object's `status' slot. Likewise, when
|
||||
UPDATE-MESSAGE-TIME is non-nil, update or initialize the
|
||||
With ADDP, ensure an entry exists. When an entry does exist or
|
||||
when ADDP is non-nil and an `erc-server-users' entry already
|
||||
exists, call `erc-update-user' with NEW-NICK, HOST, LOGIN,
|
||||
FULL-NAME, and INFO. Expect any non-nil membership
|
||||
status switches among VOICE, HALFOP, OP, ADMIN, and OWNER to be
|
||||
the symbol `on' or `off' when needing to influence a new or
|
||||
existing `erc-channel-user' object's `status' slot. Likewise,
|
||||
when UPDATE-MESSAGE-TIME is non-nil, update or initialize the
|
||||
`last-message-time' slot to the current-time. If changes occur,
|
||||
including creation, run `erc-channel-members-changed-hook'.
|
||||
Return non-nil when meaningful changes, including creation, have
|
||||
|
|
@ -6889,62 +7015,26 @@ Without ADDP, do nothing unless a `erc-channel-members' entry
|
|||
exists. When it doesn't, assume the sender is a non-joined
|
||||
entity, like the server itself or a historical speaker, or assume
|
||||
the prior buffer for the channel was killed without parting."
|
||||
(let* (cusr-changed-p
|
||||
user-changed-p
|
||||
(cmem (erc-get-channel-member nick))
|
||||
(cusr (cdr cmem))
|
||||
(down (erc-downcase nick))
|
||||
(user (or (car cmem)
|
||||
(gethash down (erc-with-server-buffer erc-server-users)))))
|
||||
(if cusr
|
||||
(progn
|
||||
(erc-log (format "update-member: user = %S, cusr = %S" user cusr))
|
||||
(when-let (((or voice halfop op admin owner))
|
||||
(existing (erc-channel-user-status cusr)))
|
||||
(when voice (setf (erc-channel-user-voice cusr) (eq voice 'on)))
|
||||
(when halfop (setf (erc-channel-user-halfop cusr) (eq halfop 'on)))
|
||||
(when op (setf (erc-channel-user-op cusr) (eq op 'on)))
|
||||
(when admin (setf (erc-channel-user-admin cusr) (eq admin 'on)))
|
||||
(when owner (setf (erc-channel-user-owner cusr) (eq owner 'on)))
|
||||
(setq cusr-changed-p (= existing (erc-channel-user-status cusr))))
|
||||
(when update-message-time
|
||||
(setf (erc-channel-user-last-message-time cusr) (current-time)))
|
||||
;; Assume `user' exists and its `buffers' slot contains the
|
||||
;; current buffer so that `erc-channel-members-changed-hook'
|
||||
;; will run if changes are made.
|
||||
(setq user-changed-p
|
||||
(erc-update-user user new-nick
|
||||
host login full-name info)))
|
||||
(when addp
|
||||
(if (null user)
|
||||
(progn
|
||||
(setq user (make-erc-server-user
|
||||
:nickname nick
|
||||
:host host
|
||||
:full-name full-name
|
||||
:login login
|
||||
:info info
|
||||
:buffers (list (current-buffer))))
|
||||
(erc-add-server-user nick user))
|
||||
(setf (erc-server-user-buffers user)
|
||||
(cons (current-buffer)
|
||||
(erc-server-user-buffers user))))
|
||||
(setq cusr (make-erc-channel-user
|
||||
:voice (and voice (eq voice 'on))
|
||||
:halfop (and halfop (eq halfop 'on))
|
||||
:op (and op (eq op 'on))
|
||||
:admin (and admin (eq admin 'on))
|
||||
:owner (and owner (eq owner 'on))
|
||||
:last-message-time (if update-message-time
|
||||
(current-time))))
|
||||
(puthash down (cons user cusr) erc-channel-users)
|
||||
(setq cusr-changed-p t)))
|
||||
;; An existing `cusr' was changed or a new one was added, and
|
||||
;; `user' was not updated, though possibly just created (since
|
||||
;; `erc-update-user' runs this same hook in all a user's buffers).
|
||||
(when (and cusr-changed-p (null user-changed-p))
|
||||
(run-hooks 'erc-channel-members-changed-hook))
|
||||
(or cusr-changed-p user-changed-p)))
|
||||
(let* ((cmem (erc-get-channel-member nick))
|
||||
(status (and (or voice halfop op admin owner)
|
||||
(if cmem
|
||||
(erc--compute-cusr-fallback-status
|
||||
(erc-channel-user-status (cdr cmem))
|
||||
voice halfop op admin owner)
|
||||
(erc--init-cusr-fallback-status
|
||||
(and voice (eq voice 'on))
|
||||
(and halfop (eq halfop 'on))
|
||||
(and op (eq op 'on))
|
||||
(and admin (eq admin 'on))
|
||||
(and owner (eq owner 'on)))))))
|
||||
(if cmem
|
||||
(erc--update-current-channel-member cmem status update-message-time
|
||||
new-nick host login
|
||||
full-name info)
|
||||
(when addp
|
||||
(erc--create-current-channel-member nick status update-message-time
|
||||
new-nick host login
|
||||
full-name info)))))
|
||||
|
||||
(defun erc-update-channel-member (channel nick new-nick
|
||||
&optional add voice halfop op admin owner host login
|
||||
|
|
@ -7134,16 +7224,6 @@ person who changed the 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-local erc--channel-modes nil
|
||||
"When non-nil, a hash table of current channel modes.
|
||||
Keys are characters. Values are either a string, for types A-C,
|
||||
|
|
@ -7189,7 +7269,7 @@ complement relevant letters in STRING."
|
|||
(cond ((= ?+ c) (setq +p t))
|
||||
((= ?- c) (setq +p nil))
|
||||
((and status-letters (string-search (string c) status-letters))
|
||||
(erc--update-membership-prefix (pop args) c (if +p 'on 'off)))
|
||||
(erc--cusr-change-status (pop args) c +p))
|
||||
((and-let* ((group (or (aref table c) (and fallbackp ?d))))
|
||||
(erc--handle-channel-mode group c +p
|
||||
(and (/= group ?d)
|
||||
|
|
@ -7511,6 +7591,12 @@ See associated unit test for precise behavior."
|
|||
(match-string 2 string)
|
||||
(match-string 3 string))))
|
||||
|
||||
(defun erc--shuffle-nuh-nickward (nick login host)
|
||||
"Interpret results of `erc--parse-nuh', promoting loners to nicks."
|
||||
(cond (nick (cl-assert (null login)) (list nick login host))
|
||||
((and (null login) host) (list host nil nil))
|
||||
((and login (null host)) (list login nil nil))))
|
||||
|
||||
(defun erc-extract-nick (string)
|
||||
"Return the nick corresponding to a user specification STRING.
|
||||
|
||||
|
|
|
|||
|
|
@ -81,4 +81,62 @@
|
|||
(should-not erc-channel-user-limit)
|
||||
(funcall expect 10 "<Chad> after"))))
|
||||
|
||||
;; This asserts proper recognition of nonstandard prefixes advertised
|
||||
;; via the "PREFIX=" ISUPPORT parameter. Note that without the IRCv3
|
||||
;; `multi-prefix' extension, we can't easily sync a user's channel
|
||||
;; membership status on receipt of a 352/353 by parsing the "flags"
|
||||
;; parameter because even though servers remember multiple prefixes,
|
||||
;; they only ever return the one with the highest rank. For example,
|
||||
;; if on receipt of a 352, we were to "update" someone we believe to
|
||||
;; be @+ by changing them to a to @, we'd be guilty of willful
|
||||
;; munging. And if they later lose that @, we'd then see them as null
|
||||
;; when in fact they're still +. However, we *could* use a single
|
||||
;; degenerate prefix to "validate" an existing record to ensure
|
||||
;; correctness of our processing logic, but it's unclear how such a
|
||||
;; discrepancy ought to be handled beyond asking the user to file a
|
||||
;; bug.
|
||||
(ert-deftest erc-scenarios-base-chan-modes--speaker-status ()
|
||||
: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 'speaker-status))
|
||||
(erc-show-speaker-membership-status t)
|
||||
(erc-autojoin-channels-alist '(("." "#chan")))
|
||||
(expect (erc-d-t-make-expecter)))
|
||||
|
||||
(ert-info ("Connect to foonet")
|
||||
(with-current-buffer (erc :server "127.0.0.1"
|
||||
:port (process-contact dumb-server :service)
|
||||
:nick "tester"
|
||||
:user "tester")
|
||||
(funcall expect 5 "Here on foonet, we provide services")))
|
||||
|
||||
(with-current-buffer (erc-d-t-wait-for 5 (get-buffer "#chan"))
|
||||
|
||||
(ert-info ("Prefixes printed correctly in 353")
|
||||
(funcall expect 10 "chan: +alice @fsbot -bob !foop"))
|
||||
|
||||
(ert-info ("Speakers honor option `erc-show-speaker-membership-status'")
|
||||
(funcall expect 10 "<-bob> alice: Of that which hath")
|
||||
(funcall expect 10 "<+alice> Hie you, make haste")
|
||||
(funcall expect 10 "<!foop> hi"))
|
||||
|
||||
(ert-info ("Status conferred and rescinded")
|
||||
(funcall expect 10 "*** foop (user@netadmin.example.net) has changed ")
|
||||
(funcall expect 10 "mode for #chan to +v bob")
|
||||
(funcall expect 10 "<+bob> alice: Fair as a text B")
|
||||
(funcall expect 10 "<+alice> bob: Even as Apemantus")
|
||||
(funcall expect 10 "mode for #chan to -v bob")
|
||||
(funcall expect 10 "<-bob> alice: That's the way")
|
||||
(funcall expect 10 "<+alice> Give it the beasts"))
|
||||
|
||||
;; If it had instead overwritten it, our two states would be
|
||||
;; out of sync. (See comment above.)
|
||||
(ert-info ("/WHO output confirms server shadowed V status")
|
||||
(erc-scenarios-common-say "/who #chan")
|
||||
(funcall expect 10 '(: "bob" (+ " ") "H-"))
|
||||
(funcall expect 10 "<-bob> alice: Remains in danger")
|
||||
(erc-cmd-QUIT "")))))
|
||||
|
||||
;;; erc-scenarios-base-chan-modes.el ends here
|
||||
|
|
|
|||
|
|
@ -674,7 +674,7 @@
|
|||
;; checking if null beforehand.
|
||||
(should-not erc--parsed-prefix)
|
||||
(should (equal (erc--parsed-prefix)
|
||||
#s(erc--parsed-prefix nil "qaohv" "~&@%+"
|
||||
#s(erc--parsed-prefix nil "vhoaq" "+%@&~"
|
||||
((?q . ?~) (?a . ?&)
|
||||
(?o . ?@) (?h . ?%) (?v . ?+)))))
|
||||
(let ((cached (should erc--parsed-prefix)))
|
||||
|
|
@ -696,7 +696,7 @@
|
|||
(should (equal expected (erc--parsed-prefix-alist erc--parsed-prefix)))
|
||||
(setq cached erc--parsed-prefix)
|
||||
(should (equal cached
|
||||
#s(erc--parsed-prefix ("(ov)@+") "ov" "@+"
|
||||
#s(erc--parsed-prefix ("(ov)@+") "vo" "+@"
|
||||
((?o . ?@) (?v . ?+)))))
|
||||
;; Second target buffer reuses cached value.
|
||||
(with-temp-buffer
|
||||
|
|
@ -714,6 +714,88 @@
|
|||
(erc-with-server-buffer erc--parsed-prefix))
|
||||
'((?q . ?~) (?h . ?%)))))))
|
||||
|
||||
(ert-deftest erc--get-prefix-flag ()
|
||||
(erc-tests-common-make-server-buf (buffer-name))
|
||||
(should-not erc--parsed-prefix)
|
||||
(should (= (erc--get-prefix-flag ?v) 1))
|
||||
(should (= (erc--get-prefix-flag ?h) 2))
|
||||
(should (= (erc--get-prefix-flag ?o) 4))
|
||||
(should (= (erc--get-prefix-flag ?a) 8))
|
||||
(should (= (erc--get-prefix-flag ?q) 16))
|
||||
|
||||
(ert-info ("With optional `from-prefix-p'")
|
||||
(should (= (erc--get-prefix-flag ?+ nil 'fpp) 1))
|
||||
(should (= (erc--get-prefix-flag ?% nil 'fpp) 2))
|
||||
(should (= (erc--get-prefix-flag ?@ nil 'fpp) 4))
|
||||
(should (= (erc--get-prefix-flag ?& nil 'fpp) 8))
|
||||
(should (= (erc--get-prefix-flag ?~ nil 'fpp) 16)))
|
||||
(should erc--parsed-prefix))
|
||||
|
||||
(ert-deftest erc--init-cusr-fallback-status ()
|
||||
;; Fallback behavior active because no `erc--parsed-prefix'.
|
||||
(should-not erc--parsed-prefix)
|
||||
(should (= 0 (erc--init-cusr-fallback-status nil nil nil nil nil)))
|
||||
(should (= 1 (erc--init-cusr-fallback-status t nil nil nil nil)))
|
||||
(should (= 4 (erc--init-cusr-fallback-status nil nil t nil nil)))
|
||||
(should-not erc--parsed-prefix) ; not created in non-ERC buffer.
|
||||
|
||||
;; Uses advertised server parameter.
|
||||
(erc-tests-common-make-server-buf (buffer-name))
|
||||
(setq erc-server-parameters '(("PREFIX" . "(YqaohvV)!~&@%+-")))
|
||||
(should (= 0 (erc--init-cusr-fallback-status nil nil nil nil nil)))
|
||||
(should (= 2 (erc--init-cusr-fallback-status t nil nil nil nil)))
|
||||
(should (= 8 (erc--init-cusr-fallback-status nil nil t nil nil)))
|
||||
(should erc--parsed-prefix))
|
||||
|
||||
(ert-deftest erc--compute-cusr-fallback-status ()
|
||||
;; Useless without an `erc--parsed-prefix'.
|
||||
(should (= 0 (erc--compute-cusr-fallback-status 0 nil nil nil nil nil)))
|
||||
(should (= 0 (erc--compute-cusr-fallback-status 0 'on 'on 'on 'on 'on)))
|
||||
|
||||
(erc-tests-common-make-server-buf (buffer-name))
|
||||
(should (= 0 (erc--compute-cusr-fallback-status 0 nil nil nil nil nil)))
|
||||
(should (= 1 (erc--compute-cusr-fallback-status 0 'on nil nil nil nil)))
|
||||
(should (= 1 (erc--compute-cusr-fallback-status 0 'on 'off 'off 'off 'off)))
|
||||
(should (= 1 (erc--compute-cusr-fallback-status 1 'on 'off 'off 'off 'off)))
|
||||
(should (= 1 (erc--compute-cusr-fallback-status 1 nil nil nil nil nil)))
|
||||
(should (= 1 (erc--compute-cusr-fallback-status 3 nil 'off nil nil nil)))
|
||||
(should (= 1 (erc--compute-cusr-fallback-status 7 nil 'off 'off nil nil)))
|
||||
(should (= 4 (erc--compute-cusr-fallback-status 1 'off nil 'on nil nil))))
|
||||
|
||||
(ert-deftest erc--cusr-status-p ()
|
||||
(erc-tests-common-make-server-buf (buffer-name))
|
||||
(should-not erc--parsed-prefix)
|
||||
(let ((cusr (make-erc-channel-user :voice t :op t)))
|
||||
(should-not (erc--cusr-status-p cusr ?q))
|
||||
(should-not (erc--cusr-status-p cusr ?a))
|
||||
(should-not (erc--cusr-status-p cusr ?h))
|
||||
(should (erc--cusr-status-p cusr ?o))
|
||||
(should (erc--cusr-status-p cusr ?v)))
|
||||
(should erc--parsed-prefix))
|
||||
|
||||
(ert-deftest erc--cusr-change-status ()
|
||||
(erc-tests-common-make-server-buf (buffer-name))
|
||||
(let ((cusr (make-erc-channel-user)))
|
||||
(should-not (erc--cusr-status-p cusr ?o))
|
||||
(should-not (erc--cusr-status-p cusr ?v))
|
||||
(erc--cusr-change-status cusr ?o t)
|
||||
(erc--cusr-change-status cusr ?v t)
|
||||
(should (erc--cusr-status-p cusr ?o))
|
||||
(should (erc--cusr-status-p cusr ?v))
|
||||
|
||||
(ert-info ("Reset with optional param")
|
||||
(erc--cusr-change-status cusr ?q t 'reset)
|
||||
(should-not (erc--cusr-status-p cusr ?o))
|
||||
(should-not (erc--cusr-status-p cusr ?v))
|
||||
(should (erc--cusr-status-p cusr ?q)))
|
||||
|
||||
(ert-info ("Clear with optional param")
|
||||
(erc--cusr-change-status cusr ?v t)
|
||||
(should (erc--cusr-status-p cusr ?v))
|
||||
(erc--cusr-change-status cusr ?q nil 'reset)
|
||||
(should-not (erc--cusr-status-p cusr ?v))
|
||||
(should-not (erc--cusr-status-p cusr ?q)))))
|
||||
|
||||
;; This exists as a reference to assert legacy behavior in order to
|
||||
;; preserve and incorporate it as a fallback in the 5.6+ replacement.
|
||||
(ert-deftest erc-parse-modes ()
|
||||
|
|
@ -737,12 +819,9 @@
|
|||
(should (equal (erc-parse-modes "-l") '(nil nil (("l" off nil))))))))
|
||||
|
||||
(ert-deftest erc--update-channel-modes ()
|
||||
(erc-mode)
|
||||
(erc-tests-common-make-server-buf)
|
||||
(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-common-init-server-proc "sleep" "1")
|
||||
|
||||
(let ((orig-handle-fn (symbol-function 'erc--handle-channel-mode))
|
||||
calls)
|
||||
|
|
@ -1715,13 +1794,13 @@
|
|||
;; regardless of whether a command handler is summoned.
|
||||
|
||||
(ert-deftest erc-process-input-line ()
|
||||
(let (erc-server-last-sent-time
|
||||
erc-server-flood-queue
|
||||
(orig-erc-cmd-MSG (symbol-function 'erc-cmd-MSG))
|
||||
(erc-default-recipients '("#chan"))
|
||||
(erc-tests-common-make-server-buf)
|
||||
(let ((orig-erc-cmd-MSG (symbol-function 'erc-cmd-MSG))
|
||||
(pop-flood-queue (lambda () (erc-with-server-buffer
|
||||
(pop erc-server-flood-queue))))
|
||||
calls)
|
||||
(with-temp-buffer
|
||||
(erc-tests-common-init-server-proc "sleep" "1")
|
||||
(setq erc-server-current-nick "tester")
|
||||
(with-current-buffer (erc--open-target "#chan")
|
||||
(cl-letf (((symbol-function 'erc-cmd-MSG)
|
||||
(lambda (line)
|
||||
(push line calls)
|
||||
|
|
@ -1735,49 +1814,50 @@
|
|||
(ert-info ("Baseline")
|
||||
(erc-process-input-line "/msg #chan hi\n")
|
||||
(should (equal (pop calls) " #chan hi"))
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("PRIVMSG #chan :hi\r\n" . utf-8))))
|
||||
|
||||
(ert-info ("Quote preserves line intact")
|
||||
(erc-process-input-line "/QUOTE FAKE foo bar\n")
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("FAKE foo bar\r\n" . utf-8))))
|
||||
|
||||
(ert-info ("Unknown command respected")
|
||||
(erc-process-input-line "/FAKE foo bar\n")
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("FAKE foo bar\r\n" . utf-8))))
|
||||
|
||||
(ert-info ("Spaces preserved")
|
||||
(erc-process-input-line "/msg #chan hi you\n")
|
||||
(should (equal (pop calls) " #chan hi you"))
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("PRIVMSG #chan :hi you\r\n" . utf-8))))
|
||||
|
||||
(ert-info ("Empty line honored")
|
||||
(erc-process-input-line "/msg #chan\n")
|
||||
(should (equal (pop calls) " #chan"))
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("PRIVMSG #chan :\r\n" . utf-8)))))
|
||||
|
||||
(ert-info ("Implicit cmd via `erc-send-input-line-function'")
|
||||
|
||||
(ert-info ("Baseline")
|
||||
(erc-process-input-line "hi\n")
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("PRIVMSG #chan :hi\r\n" . utf-8))))
|
||||
|
||||
(ert-info ("Spaces preserved")
|
||||
(erc-process-input-line "hi you\n")
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("PRIVMSG #chan :hi you\r\n" . utf-8))))
|
||||
|
||||
(ert-info ("Empty line transmitted with injected-space kludge")
|
||||
(erc-process-input-line "\n")
|
||||
(should (equal (pop erc-server-flood-queue)
|
||||
(should (equal (funcall pop-flood-queue)
|
||||
'("PRIVMSG #chan : \r\n" . utf-8))))
|
||||
|
||||
(should-not calls))))))
|
||||
(should-not calls)))))
|
||||
(erc-tests-common-kill-buffers))
|
||||
|
||||
(ert-deftest erc--get-inserted-msg-beg/basic ()
|
||||
(erc-tests-common-assert-get-inserted-msg/basic
|
||||
|
|
|
|||
69
test/lisp/erc/resources/base/modes/speaker-status.eld
Normal file
69
test/lisp/erc/resources/base/modes/speaker-status.eld
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
;; -*- mode: lisp-data; -*-
|
||||
((nick 10 "NICK tester"))
|
||||
((user 10 "USER tester 0 * :unknown")
|
||||
(0.00 ":irc.example.net NOTICE * :*** Looking up your hostname...")
|
||||
(0.00 ":irc.example.net NOTICE tester :*** Could not resolve your hostname: Domain not found; using your IP address (10.0.2.100) instead.")
|
||||
(0.09 ":irc.example.net 001 tester :Welcome to the foonet IRC Network tester!tester@10.0.2.100")
|
||||
(0.01 ":irc.example.net 002 tester :Your host is irc.example.net, running version InspIRCd-3")
|
||||
(0.01 ":irc.example.net 003 tester :This server was created 07:50:59 Jan 22 2024")
|
||||
(0.03 ":irc.example.net 004 tester irc.example.net InspIRCd-3 BIRcgikorsw ACHIKMORTVXabcefghijklmnopqrstvyz :HIVXabefghjkloqvy")
|
||||
(0.00 ":irc.example.net 005 tester ACCEPT=30 AWAYLEN=200 BOT=B CALLERID=g CASEMAPPING=ascii CHANLIMIT=#:20 CHANMODES=IXbeg,k,Hfjl,ACKMORTcimnprstz CHANNELLEN=64 CHANTYPES=# ELIST=CMNTU ESILENCE=CcdiNnPpTtx EXCEPTS=e :are supported by this server")
|
||||
(0.01 ":irc.example.net 005 tester EXTBAN=,ACORTUacjrwz HOSTLEN=64 INVEX=I KEYLEN=32 KICKLEN=255 LINELEN=512 MAXLIST=I:100,X:100,b:100,e:100,g:100 MAXTARGETS=20 MODES=20 MONITOR=30 NAMELEN=128 NAMESX NETWORK=foonet :are supported by this server")
|
||||
(0.01 ":irc.example.net 005 tester NICKLEN=30 PREFIX=(yqaohvV)!~&@%+- SAFELIST SILENCE=32 STATUSMSG=!~&@%+- TOPICLEN=307 UHNAMES USERIP USERLEN=10 USERMODES=,,s,BIRcgikorw WHOX :are supported by this server")
|
||||
(0.01 ":irc.example.net 251 tester :There are 2 users and 2 invisible on 2 servers")
|
||||
(0.00 ":irc.example.net 252 tester 1 :operator(s) online")
|
||||
(0.00 ":irc.example.net 253 tester 1 :unknown connections")
|
||||
(0.00 ":irc.example.net 254 tester 2 :channels formed")
|
||||
(0.00 ":irc.example.net 255 tester :I have 4 clients and 1 servers")
|
||||
(0.00 ":irc.example.net 265 tester :Current local users: 4 Max: 5")
|
||||
(0.00 ":irc.example.net 266 tester :Current global users: 4 Max: 5")
|
||||
(0.00 ":irc.example.net 375 tester :irc.example.net message of the day")
|
||||
(0.00 ":irc.example.net 372 tester : https://github.com/inspircd/inspircd-docker/issues")
|
||||
(0.00 ":irc.example.net 372 tester : ")
|
||||
(0.00 ":irc.example.net 372 tester : Have fun with the image!")
|
||||
(0.00 ":irc.example.net 376 tester :End of message of the day.")
|
||||
(0.00 ":irc.example.net 501 tester x :is not a recognised user mode.")
|
||||
(0.00 ":NickServ!NickServ@services.int NOTICE tester :Welcome to foonet, tester! Here on foonet, we provide services to enable the registration of nicknames and channels! For details, type \2/msg NickServ help\2 and \2/msg ChanServ help\2."))
|
||||
|
||||
((mode 10 "MODE tester +i")
|
||||
(0.01 ":tester!tester@10.0.2.100 MODE tester :+i"))
|
||||
|
||||
((join 10 "JOIN #chan")
|
||||
(0.02 ":tester!tester@10.0.2.100 JOIN :#chan")
|
||||
(0.02 ":irc.example.net 353 tester = #chan :+alice @fsbot -bob !foop tester")
|
||||
(0.03 ":irc.example.net 366 tester #chan :End of /NAMES list.")
|
||||
(0.00 ":bob!bob@localhost PRIVMSG #chan :tester, welcome!")
|
||||
(0.01 ":alice!alice@localhost PRIVMSG #chan :tester, welcome!"))
|
||||
|
||||
((mode-chan 10 "MODE #chan")
|
||||
(0.00 ":irc.example.net 324 tester #chan :+nt")
|
||||
(0.01 ":irc.example.net 329 tester #chan :1705909863")
|
||||
(0.03 ":bob!bob@localhost PRIVMSG #chan :alice: Of that which hath so faithfully been paid.")
|
||||
(0.03 ":alice!alice@localhost PRIVMSG #chan :Hie you, make haste, for it grows very late.")
|
||||
(0.03 ":foop!user@netadmin.example.net PRIVMSG #chan :hi")
|
||||
;; (0.07 ":alice!alice@localhost PRIVMSG #chan :bob: And make a clear way to the gods.")
|
||||
;; (0.04 ":bob!bob@localhost PRIVMSG #chan :Why, that they have; and bid them so be gone.")
|
||||
;; (0.08 ":bob!bob@localhost PRIVMSG #chan :alice: Now stay your strife: what shall be is dispatch'd.")
|
||||
(0.06 ":foop!user@netadmin.example.net MODE #chan +v :bob")
|
||||
(0.05 ":bob!bob@localhost PRIVMSG #chan :alice: Fair as a text B in a copy-book.")
|
||||
(0.07 ":alice!alice@localhost PRIVMSG #chan :bob: Even as Apemantus does now; hate a lord with my heart.")
|
||||
(0.03 ":bob!bob@localhost PRIVMSG #chan :Then here is a supplication for you. And when you come to him, at the first approach you must kneel; then kiss his foot; then deliver up your pigeons; and then look for your reward. I'll be at hand, sir; see you do it bravely.")
|
||||
(0.05 ":foop!user@netadmin.example.net MODE #chan -v :bob")
|
||||
(0.04 ":bob!bob@localhost PRIVMSG #chan :alice: That's the way: for women are light at midnight.")
|
||||
(0.04 ":alice!alice@localhost PRIVMSG #chan :Give it the beasts, to be rid of the men.")
|
||||
;; (0.02 ":alice!alice@localhost PRIVMSG #chan :bob: Here comes young Master Ganymede, my new mistress's brother.")
|
||||
)
|
||||
|
||||
((who-chan 10 "who #chan")
|
||||
(0.03 ":irc.example.net 352 tester #chan alice localhost irc.example.net alice H+ :0 Irc bot based on irc3 http://irc3.readthedocs.io")
|
||||
(0.03 ":irc.example.net 352 tester #chan fsbot localhost irc.example.net fsbot H@ :0 fsbot")
|
||||
(0.01 ":irc.example.net 352 tester #chan bob localhost irc.example.net bob H- :0 Irc bot based on irc3 http://irc3.readthedocs.io")
|
||||
(0.01 ":irc.example.net 352 tester #chan user netadmin.example.net irc.example.net foop H*! :0 unknown")
|
||||
(0.01 ":irc.example.net 352 tester #chan tester 10.0.2.100 irc.example.net tester H :0 unknown")
|
||||
(0.01 ":irc.example.net 315 tester #chan :End of /WHO list.")
|
||||
;; (0.09 ":bob!bob@localhost PRIVMSG #chan :alice: Shall nothing wrong him. Thus it is, general.")
|
||||
;; (0.04 ":alice!alice@localhost PRIVMSG #chan :bob: His father and I were soldiers together; to whom I have been often bound for no less than my life. Here comes the Briton: let him be so entertained amongst you as suits, with gentlemen of your knowing, to a stranger of his quality.")
|
||||
(0.04 ":bob!bob@localhost PRIVMSG #chan :alice: Remains in danger of her former tooth."))
|
||||
|
||||
((quit 10 "QUIT :\2ERC\2")
|
||||
(0.03 "ERROR :Closing link: (tester@10.0.2.100) [Quit: \2ERC\2 5.x (IRC client for GNU Emacs)]"))
|
||||
Loading…
Reference in a new issue