Subject: Re: bug#68401: 30.0.50; ERC 5.6-git: `erc-cmd-GMSG', `erc-cmd-AMSG', `erc-cmd-GME', `erc-cmd-AME'. 2nd attempt

From: "J.P." <[email-masked]>
Date: Thu, 18 Jan 2024 18:58:51 -0800

Emanuel Berg <[email-masked]> writes:

>> From fa8ae9dcc306d16cccdd6aa7c2bac242b90adbdb Mon Sep 17 00:00:00 2001
> From: Emanuel Berg <[email-masked]>
> Date: Sat, 13 Jan 2024 03:40:05 +0100
> Subject: [PATCH] Functions for ERC.

Emacs doesn't seem to be very picky about a commit's subject line, but
I'd prefer those for ERC that aren't mechanical or administrative in
nature to be somewhat unique and distinguishable at a glance, such as

  Make erc-cmd-AMSG session-local, add /GMSG /AME /GME

> `erc-cmd-GMSG', `erc-cmd-GME' and `erc-cmd-AME' was added.
> `erc-cmd-AMSG' was changed so that it does what the docstring says.
> * lisp/erc/erc.el: functions were added/modified to/in this file.
>
> (bug#68401)

I believe the guidelines call for a commit body to be formatted as a
change log entry, for example:

  * lisp/erc/erc.el (erc-cmd-AMSG): Make good on behavior described in
  the doc string by limiting damage to the current connection.
  (erc-cmd-GMSG, erc-cmd-GME, erc-cmd-AME): New functions, all IRC
  "slash commands".  (Bug#68401)

> ---
>  lisp/erc/erc.el | 38 +++++++++++++++++++++++++++++++++-----
>  1 file changed, 33 insertions(+), 5 deletions(-)
>
> diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
> index 478683a77f5..aeb7722b563 100644
> --- a/lisp/erc/erc.el
> +++ b/lisp/erc/erc.el
> @@ -4016,16 +4016,44 @@ erc--split-string-shell-cmd
>  ;;                    Input commands handlers
>  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>  
> -(defun erc-cmd-AMSG (line)
> -  "Send LINE to all channels of the current server that you are on."
> -  (interactive "sSend to all channels you're on: ")
> +(defun erc-cmd-GMSG (line)
> +  "Send LINE to all channels on all networks you are on.
> +Interactively, prompt for the line of text to send."
> +  (interactive "sSend to all channels: ")

I question the wisdom of having new slash commands serve double duty as
interactive Emacs commands (at least those handling chat input). This
reservation has nothing to do with M-x erc-cmd-FOO <RET> being less
faithful (or whatever) to the traditional IRC experience than /FOO
<RET>. Rather, it stems from a need to prioritize consistent feedback
and promote maintainability by only having a single path for chat input
to reach the server (except under special circumstances).

Normally, when a user submits chat input at the prompt, ERC engages in a
series of validation checks before pushing a message out the door. These
steps are bypassed when someone invokes what's normally a slash command
via M-x. For example, if you /DISCONNECT and issue an /AMSG at the
prompt, you'll see "Process not running" in the echo area, and the input
will remain there for further editing, killing, etc. However, if you run
M-x erc-cmd-AMSG <RET>, the message will be inserted in all target
buffers, even though nothing is actually sent, which is misleading.
Obviously, we can't make `erc-cmd-AMSG' non-interactive because it's
been `commandp' forever. But new related commands don't have to follow
its (IMO flawed) example.

As far as counterarguments go, the only one that comes to mind for
making these `commandp' is that doing so also makes managing interactive
menus for modules like `bufbar', `nickbar', and `button' easier. For
example, at first glance, making `erc-cmd-KICK' interactive would appear
to streamline its inclusion in `erc-nick-popup-alist' and obviate the
need for an `erc-button-cmd-KICK'. However, if you look closely at this
arrangement, you'll see that even if `erc-cmd-KICK' were made a proper
Emacs command, a button-specific wrapper would still be necessary
because it makes special accommodations for the potential lack of a
channel context from which to draw membership rolls for completion. Such
a thing isn't necessary when issuing a /KICK <TAB> at the prompt because
the function `pcomplete/erc-mode/KICK' knows it's already running in a
channel.

>    (setq line (erc-trim-string line))

It might be nice to remove at most one space, for cases where a user
wants to send preformatted text. OTOH, normal /MSG doesn't do this, so
perhaps we shouldn't here either.

>    (erc-with-all-buffers-of-server nil
> -    (lambda ()
> -      (erc-channel-p (erc-default-target)))
> +    (lambda () (erc-channel-p (erc-default-target)))
> +    (erc-send-message line)))

Without first checking for connectivity, we run into another situation
in which messages may be inserted but not sent, similar to the bit about
commands being potentially "misleading," above. The most obvious way to
solve this is to check for "physical" connectivity with something like:

  (erc-with-all-buffers-of-server nil #'erc-server-process-alive
    (when (and erc--target (erc--current-buffer-joined-p))
      (erc-send-message line))))

Alternatively, you can check for "logical" connectivity, which is
probably more in keeping with traditional design principles:

  (erc-with-all-buffers-of-server nil nil
    (when (and erc-server-connected erc--target (erc--current-buffer-joined-p))
      (erc-send-message line))))

One minor downside of this second method is that IRC adjacent protocols
and aberrant proxy servers that happen to skip 376/422 and also provide
some (possibly &local) "control channel" won't be detected. (BTW, you
won't be needing the `erc--target' in either example if you rebase atop
the latest master.)

> +(put 'erc-cmd-GMSG 'do-not-parse-args t)
> +
> +(defun erc-cmd-AMSG (line)
> +  "Send LINE to all channels of the current network.
> +Interactively, prompt for the line of text to send."
> +  (interactive "sSend to all channels on this network: ")
> +  (setq line (erc-trim-string line))
> +  (erc-with-all-buffers-of-server erc-server-process
> +    (lambda () (erc-channel-p (erc-default-target)))

         ^ Indentation. This macro is declared "indent 2"

>      (erc-send-message line)))
>  (put 'erc-cmd-AMSG 'do-not-parse-args t)
>  
> +(defun erc-cmd-GME (line)
> +  "Send LINE as an action to all channels on all networks you are on.
> +Interactively, prompt for the line of text to send."
> +  (interactive "sSend action to all channels: ")

This command currently fails when invoked interactively. For example, if
I run M-x erc-cmd-GME <RET> hi <RET> from any ERC buffer belonging to a
connected session, nothing appears in the server logs or any ERC buffer.
This needs addressing if you're intent on keeping these interactive,
which I'm rather against for reasons previously noted.

> +  (erc-with-all-buffers-of-server nil
> +    (lambda () (erc-channel-p (erc-default-target)))
> +    (erc-cmd-ME line) ))

This currently suffers from the same problem as /GMSG regarding
disconnected buffers. However you address this, it's probably best to
use the same approach for fixing both functions.

> +(put 'erc-cmd-GME 'do-not-parse-args t)
> +
> +(defun erc-cmd-AME (line)
> +  "Send LINE as an action to all channels on the current network.
> +Interactively, prompt for the line of text to send."
> +  (interactive "sSend action to all channels on this network: ")

This command also appears do to nothing when invoked via M-x.

> +  (erc-with-all-buffers-of-server erc-server-process
> +    (lambda () (erc-channel-p (erc-default-target)))

         ^ Indentation again.

> +    (erc-cmd-ME line) ))
> +(put 'erc-cmd-AME 'do-not-parse-args t)
> +
>  (defun erc-cmd-SAY (line)
>    "Send LINE to the current query or channel as a message, not a command.
>  
> -- 
> 2.39.2

The attached patch is a unit test for all four commands. It doesn't
bother asserting M-x behavior (because see above). Please try to make it
pass without changing the test itself (unless there's a bug).

  $ git am /tmp/0002-5.x-Add...etc.patch

  $ make -C test lisp/erc/erc-scenarios-misc-commands.log \
    SELECTOR=erc-scenarios-misc-commands--AMSG-GMSG-AME-GME

>From 0dfe03ad58e6d1edd47eace6faddeeb1733f6b37 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 15 Jan 2024 22:40:44 -0800
Subject: [PATCH 2/2] [5.x] Add tests for ERC slash commands /AMSG, /GMSG, etc.

* test/lisp/erc/erc-scenarios-misc-commands.el
(erc-scenarios-misc-commands--AMSG-GMSG-AME-GME): New test.
* test/lisp/erc/resources/commands/amsg-barnet.eld: New file.
* test/lisp/erc/resources/commands/amsg-foonet.eld: New file.
(Bug#68401)
---
 test/lisp/erc/erc-scenarios-misc-commands.el  | 84 +++++++++++++++++++
 .../erc/resources/commands/amsg-barnet.eld    | 52 ++++++++++++
 .../erc/resources/commands/amsg-foonet.eld    | 52 ++++++++++++
 3 files changed, 188 insertions(+)
 create mode 100644 test/lisp/erc/resources/commands/amsg-barnet.eld
 create mode 100644 test/lisp/erc/resources/commands/amsg-foonet.eld

diff --git a/test/lisp/erc/erc-scenarios-misc-commands.el b/test/lisp/erc/erc-scenarios-misc-commands.el
index d6ed53b5358..c6bb610b9df 100644
--- a/test/lisp/erc/erc-scenarios-misc-commands.el
+++ b/test/lisp/erc/erc-scenarios-misc-commands.el
@@ -123,4 +123,88 @@ erc-scenarios-misc-commands--VHOST
         (should (string= (erc-server-user-host (erc-get-server-user "tester"))
                          "some.host.test.cc"))))))
 
+;; This tests four related slash commands, /AMSG, /GMSG, /AME, /GME,
+;; the latter three introduced by bug#68401.  It mainly asserts
+;; correct routing behavior, especially not sending or inserting
+;; messages in buffers belonging to disconnected sessions.  Left
+;; unaddressed are interactions with the `command-indicator' module
+;; (`erc-noncommands-list') and whatever future `echo-message'
+;; implementation manifests out of bug#49860.
+(ert-deftest erc-scenarios-misc-commands--AMSG-GMSG-AME-GME ()
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "commands")
+       (erc-server-flood-penalty 0.1)
+       (dumb-server-foonet (erc-d-run "localhost" t "srv-foonet" 'amsg-foonet))
+       (dumb-server-barnet (erc-d-run "localhost" t "srv-barnet" 'amsg-barnet))
+       (expect (erc-d-t-make-expecter)))
+
+    (ert-info ("Connect to foonet and join #foo")
+      (with-current-buffer
+          (erc :server "127.0.0.1"
+               :port (process-contact dumb-server-foonet :service)
+               :nick "tester")
+        (funcall expect 10 "debug mode")
+        (erc-cmd-JOIN "#foo")))
+
+    (ert-info ("Connect to barnet and join #bar")
+      (with-current-buffer
+          (erc :server "127.0.0.1"
+               :port (process-contact dumb-server-barnet :service)
+               :nick "tester")
+        (funcall expect 10 "debug mode")
+        (erc-cmd-JOIN "#bar")))
+
+    (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#foo"))
+      (funcall expect 10 "welcome"))
+    (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#bar"))
+      (funcall expect 10 "welcome"))
+
+    (ert-info ("/AMSG only sent to issuing context's server")
+      (with-current-buffer "foonet"
+        (erc-scenarios-common-say "/amsg 1 foonet only"))
+      (with-current-buffer "barnet"
+        (erc-scenarios-common-say "/amsg 2 barnet only"))
+      (with-current-buffer "#foo"
+        (funcall expect 10 "<tester> 1 foonet only")
+        (funcall expect 10 "<alice> bob: Our queen and all"))
+      (with-current-buffer "#bar"
+        (funcall expect 10 "<tester> 2 barnet only")
+        (funcall expect 10 "<joe> mike: And secretly to greet")))
+
+    (ert-info ("/AME only sent to issuing context's server")
+      (with-current-buffer "foonet"
+        (erc-scenarios-common-say "/ame 3 foonet only"))
+      (with-current-buffer "barnet"
+        (erc-scenarios-common-say "/ame 4 barnet only"))
+      (with-current-buffer "#foo"
+        (funcall expect 10 "* tester 3 foonet only")
+        (funcall expect 10 "<alice> bob: You have discharged this"))
+      (with-current-buffer "#bar"
+        (funcall expect 10 "* tester 4 barnet only")
+        (funcall expect 10 "<joe> mike: That same Berowne")))
+
+    (ert-info ("/GMSG sent to all servers")
+      (with-current-buffer "foonet"
+        (erc-scenarios-common-say "/gmsg 5 all nets"))
+      (with-current-buffer "#bar"
+        (funcall expect 10 "<tester> 5 all nets")
+        (funcall expect 10 "<joe> mike: Mehercle! if their sons")))
+
+    (ert-info ("/GMSG sent only to connected servers")
+      (with-current-buffer "barnet"
+        (erc-cmd-QUIT "")
+        (funcall expect 10 "ERC finished"))
+      (with-current-buffer "#foo"
+        (funcall expect 10 "<tester> 5 all nets")
+        (funcall expect 10 "<alice> bob: Stand you!"))
+      (with-current-buffer "foonet"
+        (erc-scenarios-common-say "/gmsg 6 all live nets"))
+      ;; Message *not* inserted in disconnected buffer.
+      (with-current-buffer "#bar"
+        (funcall expect -0.1 "<tester> 6 all live nets")))
+
+    (with-current-buffer "#foo"
+      (funcall expect 10 "<tester> 6 all live nets")
+      (funcall expect 10 "<bob> alice: Live, and be prosperous;"))))
+
 ;;; erc-scenarios-misc-commands.el ends here
diff --git a/test/lisp/erc/resources/commands/amsg-barnet.eld b/test/lisp/erc/resources/commands/amsg-barnet.eld
new file mode 100644
index 00000000000..a1d58b3d402
--- /dev/null
+++ b/test/lisp/erc/resources/commands/amsg-barnet.eld
@@ -0,0 +1,52 @@
+;; -*- mode: lisp-data; -*-
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :unknown")
+ (0 ":irc.barnet.org 001 tester :Welcome to the barnet IRC Network tester")
+ (0 ":irc.barnet.org 002 tester :Your host is irc.barnet.org, running version oragono-2.6.0-7481bf0385b95b16")
+ (0 ":irc.barnet.org 003 tester :This server was created Tue, 04 May 2021 05:06:19 UTC")
+ (0 ":irc.barnet.org 004 tester irc.barnet.org oragono-2.6.0-7481bf0385b95b16 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0 ":irc.barnet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this server")
+ (0 ":irc.barnet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=barnet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0 ":irc.barnet.org 005 tester draft/CHATHISTORY=100 :are supported by this server")
+ (0 ":irc.barnet.org 251 tester :There are 0 users and 3 invisible on 1 server(s)")
+ (0 ":irc.barnet.org 252 tester 0 :IRC Operators online")
+ (0 ":irc.barnet.org 253 tester 0 :unregistered connections")
+ (0 ":irc.barnet.org 254 tester 1 :channels formed")
+ (0 ":irc.barnet.org 255 tester :I have 3 clients and 0 servers")
+ (0 ":irc.barnet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0 ":irc.barnet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0 ":irc.barnet.org 422 tester :MOTD File is missing"))
+
+((mode-user 10 "MODE tester +i")
+ (0 ":irc.barnet.org 221 tester +i")
+ (0 ":irc.barnet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
+
+((join 10 "JOIN #bar")
+ (0 ":[email-masked] JOIN #bar")
+ (0 ":irc.barnet.org 353 tester = #bar :@mike joe tester")
+ (0 ":irc.barnet.org 366 tester #bar :End of NAMES list"))
+
+((mode-bar 10 "MODE #bar")
+ (0 ":irc.barnet.org 324 tester #bar +nt")
+ (0 ":irc.barnet.org 329 tester #bar 1620104779")
+ (0.1 ":[email-masked] PRIVMSG #bar :tester, welcome!")
+ (0.1 ":[email-masked] PRIVMSG #bar :tester, welcome!")
+ (0.1 ":[email-masked] PRIVMSG #bar :joe: Whipp'd first, sir, and hang'd after.")
+ (0.1 ":[email-masked] PRIVMSG #bar :mike: We have yet many among us can gripe as hard as Cassibelan; I do not say I am one, but I have a hand. Why tribute ? why should we pay tribute ? If C sar can hide the sun from us with a blanket, or put the moon in his pocket, we will pay him tribute for light; else, sir, no more tribute, pray you now."))
+
+((privmsg-2 10 "PRIVMSG #bar :2 barnet only")
+ (0.1 ":[email-masked] PRIVMSG #bar :joe: Double and treble admonition, and still forfeit in the same kind ? This would make mercy swear, and play the tyrant.")
+ (0.1 ":[email-masked] PRIVMSG #bar :mike: And secretly to greet the empress' friends."))
+
+((privmsg-4 10 "PRIVMSG #bar :\1ACTION 4 barnet only\1")
+ (0.1 ":[email-masked] PRIVMSG #bar :joe: You have not been inquired after: I have sat here all day.")
+ (0.1 ":[email-masked] PRIVMSG #bar :mike: That same Berowne I'll torture ere I go."))
+
+((privmsg-5 10 "PRIVMSG #bar :5 all nets")
+ (0.1 ":[email-masked] PRIVMSG #bar :joe: For mine own part,no offence to the general, nor any man of quality,I hope to be saved.")
+ (0.1 ":[email-masked] PRIVMSG #bar :mike: Mehercle! if their sons be ingenuous, they shall want no instruction; if their daughters be capable, I will put it to them. But, vir sapit qui pauca loquitur. A soul feminine saluteth us."))
+
+((quit 5 "QUIT :\2ERC\2")
+ (0 ":[email-masked] QUIT :Quit"))
+
+((drop 0 DROP))
diff --git a/test/lisp/erc/resources/commands/amsg-foonet.eld b/test/lisp/erc/resources/commands/amsg-foonet.eld
new file mode 100644
index 00000000000..d5cf15ddf7c
--- /dev/null
+++ b/test/lisp/erc/resources/commands/amsg-foonet.eld
@@ -0,0 +1,52 @@
+;; -*- mode: lisp-data; -*-
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :unknown")
+ (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
+ (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version oragono-2.6.0-7481bf0385b95b16")
+ (0 ":irc.foonet.org 003 tester :This server was created Tue, 04 May 2021 05:06:18 UTC")
+ (0 ":irc.foonet.org 004 tester irc.foonet.org oragono-2.6.0-7481bf0385b95b16 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX KICKLEN=390 :are supported by this server")
+ (0 ":irc.foonet.org 005 tester MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:1,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8MAPPING=rfc8265 UTF8ONLY WHOX :are supported by this server")
+ (0 ":irc.foonet.org 005 tester draft/CHATHISTORY=100 :are supported by this server")
+ (0 ":irc.foonet.org 251 tester :There are 0 users and 3 invisible on 1 server(s)")
+ (0 ":irc.foonet.org 252 tester 0 :IRC Operators online")
+ (0 ":irc.foonet.org 253 tester 0 :unregistered connections")
+ (0 ":irc.foonet.org 254 tester 1 :channels formed")
+ (0 ":irc.foonet.org 255 tester :I have 3 clients and 0 servers")
+ (0 ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0 ":irc.foonet.org 422 tester :MOTD File is missing"))
+
+((mode-user 10 "MODE tester +i")
+ (0 ":irc.foonet.org 221 tester +i")
+ (0 ":irc.foonet.org NOTICE tester :This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
+
+((join 10 "JOIN #foo")
+ (0 ":[email-masked] JOIN #foo")
+ (0 ":irc.foonet.org 353 tester = #foo :alice tester @bob")
+ (0 ":irc.foonet.org 366 tester #foo :End of NAMES list"))
+
+((mode-foo 10 "MODE #foo")
+ (0 ":irc.foonet.org 324 tester #foo +nt")
+ (0 ":irc.foonet.org 329 tester #foo 1620104779")
+ (0.1 ":[email-masked] PRIVMSG #foo :tester, welcome!")
+ (0.1 ":[email-masked] PRIVMSG #foo :tester, welcome!")
+ (0.1 ":[email-masked] PRIVMSG #foo :alice: But, as it seems, did violence on herself.")
+ (0.1 ":[email-masked] PRIVMSG #foo :bob: Well, this is the forest of Arden."))
+
+((privmsg-1 10 "PRIVMSG #foo :1 foonet only")
+ (0.1 ":[email-masked] PRIVMSG #foo :alice: Signior Iachimo will not from it. Pray, let us follow 'em.")
+ (0.1 ":[email-masked] PRIVMSG #foo :bob: Our queen and all her elves come here anon."))
+
+((privmsg-3 10 "PRIVMSG #foo :\1ACTION 3 foonet only\1")
+ (0.1 ":[email-masked] PRIVMSG #foo :alice: The ground is bloody; search about the churchyard.")
+ (0.1 ":[email-masked] PRIVMSG #foo :bob: You have discharged this honestly: keep it to yourself. Many likelihoods informed me of this before, which hung so tottering in the balance that I could neither believe nor misdoubt. Pray you, leave me: stall this in your bosom; and I thank you for your honest care. I will speak with you further anon."))
+
+((privmsg-5 10 "PRIVMSG #foo :5 all nets")
+ (0.1 ":[email-masked] PRIVMSG #foo :alice: Give me that mattock, and the wrenching iron.")
+ (0.1 ":[email-masked] PRIVMSG #foo :bob: Stand you! You have land enough of your own; but he added to your having, gave you some ground."))
+
+((privmsg-6 10 "PRIVMSG #foo :6 all live nets")
+ (0.1 ":[email-masked] PRIVMSG #foo :alice: Excellent workman! Thou canst not paint a man so bad as is thyself.")
+ (0.1 ":[email-masked] PRIVMSG #foo :bob: And will you, being a man of your breeding, be married under a bush, like a beggar ? Get you to church, and have a good priest that can tell you what marriage is: this fellow will but join you together as they join wainscot; then one of you will prove a shrunk panel, and like green timber, warp, warp.")
+ (0.1 ":[email-masked] PRIVMSG #foo :alice: Live, and be prosperous; and farewell, good fellow."))
-- 
2.42.0