Subject: Re: bug#56514: 29.0.50; Improve ERC's URI scheme integration for irc:// links

From: "J.P." <[email-masked]>
Date: Wed, 13 Jul 2022 07:44:48 -0700

Lars Ingebrigtsen <[email-masked]> writes:

> "J.P." <[email-masked]> writes:
>
>> In the end, I'm hoping other folks will step forward who may be more
>> familiar with the libraries mentioned so that nicer renditions can
>> emerge.
>
> It's a large patch series -- perhaps asking questions about specific
> libraries would be more productive.

Hm, right. Seems I'll have to take my entitled prima dona act elsewhere.

Questions, then (TIA, people):

  1. The first patch strays outside ERC's turf. Should I open a separate
     bug report for it? [1].
  
  2. With how I have things now, we'd use `browse-url-default-handlers'
     to sidestep url.el's loader-finding logic, as performed by
     `url-scheme-get-property'. But that feels a little hacky since my
     new, generalized handler is just a wrapper that calls `url-irc'
     (the loader), which massages the arguments and then calls our
     original (somewhat revamped) handler.

     A cleaner way might be to perhaps make url-irc.el aware of the new
     handler. But for that we'd need `url-scheme-get-property' to map
     all the scheme variants we're interested in, like ircs, irc6, etc.,
     to that same loader. OTOH, that file's pretty ancient, so perhaps
     it's better to just leave it be?

  3. Should I include the actual setup code for the integrations? If so,
     where would that go? My initial plan was to just have it all live
     in the docs, perhaps under a new Info node. BTW (re integrations),
     I also threw in a .desktop file [2], knowing full well that folks
     may just perceive that as more clutter polluting the Emacs tree.
     Should I drop it? People wanting one can just make their own.

  4. I'll approach the Org people separately for this stuff, but just as
     a preview: my main question for them deals with their nonstandard
     "/user" variant of the URI syntax. Specifically, I'd like to know
     how it's supposed to work when a "?key" or multiple comma-separated
     channels (also nonstandard) are present in the URL. I'd also like
     to know how important it is we preserve this feature and how
     amenable they'd be to it (rapidly) going extinct [3].

Other, minor questions remain, some internal to ERC [4], but I'll spare
everyone the trouble for now.

Thanks!

[1] On a system for which `browse-url-can-use-xdg-open' returns non-nil,
    and with point somewhere over some text like "http://[::1]", do

      M-x browse-url-at-point RET

    What happens is that `browse-url-url-at-point' returns
    "http://http://" because `thing-at-point-url-at-point' doesn't seem
    to like IPv6 URLs. This ultimately leads to a

      (call-process "xdg-open" nil 0 nil "http://http://")

    which exits nonzero.

[2] For anyone interested, if your OS supports XDG desktop stuff, you
    can try the included etc/emacs-irc.desktop by doing something like

    a. Change the Exec directive to launch a local emacs -Q

       -Exec=emacs -l erc -f erc--handle-ircs-url %u
       +Exec=/home/me/emacs/master/src/emacs -Q -l erc -f ...
    
    b. $ desktop-file-install --rebuild-mime-info-cache \
         --dir=~/.local/share/applications etc/emacs-irc.desktop
    
    c. $ gtk-launch emacs-irc 'ircs://testnet.ergo.chat/#test'

[3] Not that it matters, but it took a fair bit of surgery across four
    patches to make "/user" behave as originally intended (according to
    my possibly warped impression). However, I'm pretty convinced it can
    only ever work reliably in conjunction with an IRC extension that's
    not (yet) widely adopted by servers and that ERC doesn't yet
    implement (although bug#49860 has something coming down the pike).

[4] There's also the matter of duplicate functionality WRT the autojoin
    module and URL-triggered channel joining. It'd be nice to find a way
    to defer to existing code when a URL specifies a channel. Obviously,
    when a connection already exists or autojoin is already enabled,
    this won't be an issue. But when that's not the case, what's the
    right move? (Disabling autojoin seems mighty popular.)

    One option is just to refuse to open a new connection without
    autojoin. Or, we could prompt the user for permission to enable it.
    Somewhat complicating this is the fact that autojoin (like all
    modules) is only designed to be enabled globally. (I have a patch to
    address this, but it's only aimed at defining new modules as local
    to a network context.)

>From 1ef37c2aeff57b5d81a6ebd64a5a0d505203d923 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Wed, 13 Jul 2022 05:50:50 -0700
Subject: [PATCH 00/10] *** NOT A PATCH ***

*** BLURB HERE ***

F. Jason Park (10):
  Teach thing-at-point to recognize bracketed IPv6 URLs
  Accept bracketed IPv6 hosts in ERC
  Default to TLS port when calling erc-tls in lisp code
  Add optional server param to erc-networks--determine
  Improve new connections in erc-handle-irc-url
  [POC] Make erc-once-with-server-event more nimble
  [POC] Support one-off JOIN handlers in ERC
  [POC] Use erc--join-with-callback in URL handler
  [POC] Demo improved ol-irc integration
  [POC] * etc/emacs-irc.desktop: New file.

 etc/emacs-irc.desktop               |  13 ++
 lisp/erc/erc-backend.el             |   8 +
 lisp/erc/erc-networks.el            |   9 +-
 lisp/erc/erc.el                     | 268 ++++++++++++++++++++++++----
 lisp/thingatpt.el                   |   2 +-
 test/lisp/erc/erc-networks-tests.el |  17 ++
 test/lisp/erc/erc-tests.el          | 136 ++++++++++++++
 test/lisp/thingatpt-tests.el        |   1 +
 8 files changed, 415 insertions(+), 39 deletions(-)
 create mode 100644 etc/emacs-irc.desktop

Interdiff:
diff --git a/etc/emacs-irc.desktop b/etc/emacs-irc.desktop
index ed13e918d2..ebdcda3a07 100644
--- a/etc/emacs-irc.desktop
+++ b/etc/emacs-irc.desktop
@@ -5,7 +5,6 @@ Keywords=ERC;extensible;chat;IRC;client;
 Categories=Network;Chat;IRCClient;
 Comment=GNU Emacs is an extensible, customizable text editor - ERC is a powerful, modular, and extensible IRC client for Emacs
 # FIXME update command line and name once autoloaded
-# Also check if shell-quoting %u is needed, since it likely includes a #
 Exec=emacs -l erc -f erc--handle-ircs-url %u
 Icon=emacs
 MimeType=x-scheme-handler/irc;x-scheme-handler/ircs;
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index bc7a7d14dc..2ead0c9ba5 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -532,12 +532,20 @@ erc-open-network-stream
   (let ((p (plist-put parameters :nowait t)))
     (apply #'open-network-stream name buffer host service p)))
 
+(defvar erc--server-connect-dumb-ipv6-regexp
+  ;; Likely gives false positives and false negatives
+  (rx bot "["
+      (group (+ (or (any xdigit digit ":.") (: "%" (+ alnum)))))
+      "]" eot))
+
 (defun erc-server-connect (server port buffer &optional client-certificate)
   "Perform the connection and login using the specified SERVER and PORT.
 We will store server variables in the buffer given by BUFFER.
 CLIENT-CERTIFICATE may optionally be used to specify a TLS client
 certificate to use for authentication when connecting over
 TLS (see `erc-session-client-certificate' for more details)."
+  (when (string-match erc--server-connect-dumb-ipv6-regexp server)
+    (setq server (match-string 1 server)))
   (let ((msg (erc-format-message 'connect ?S server ?p port)) process
         (args `(,(format "erc-%s-%s" server port) nil ,server ,port)))
     (when client-certificate
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index efa88bfff5..7137a7b401 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -2364,8 +2364,11 @@ erc-select-read-args
     (setq user-input (read-string
                       "IRC server: "
                       (erc-compute-server) 'erc-server-history-list))
-
-    (if (string-match "\\(.*\\):\\(.*\\)\\'" user-input)
+    (if (and (string-match (rx (or (: (* (not "[")) ":" (* any))
+                                   (group (+ any)))
+                               ":" (group (+ (not (any ":]")))) eot)
+                           user-input)
+             (match-string 1 user-input))
         (setq port (erc-string-to-port (match-string 2 user-input))
               user-input (match-string 1 user-input))
       (setq port
@@ -7481,10 +7484,9 @@ erc-handle-irc-url
 If ERC is already connected to HOST:PORT, simply /join CHANNEL.
 Otherwise, connect to HOST:PORT as NICK and /join CHANNEL.
 
-Note: calling this function with NICK and/or PASSWORD is
-deprecated and results in a warning.  Moreover, ERC no longer
-attempts to establish new connections without human intervention,
-although opting in may eventually be allowed."
+Note that ERC no longer attempts to establish new connections
+without human intervention, although opting in may eventually be
+allowed."
   (when (eql port 0) (setq port nil))
   (let* ((net (erc-networks--determine host))
          (server-buffer
@@ -7503,9 +7505,6 @@ erc-handle-irc-url
                                     (= (erc-normalize-port erc-session-port)
                                        port)))))))))
          key deferred)
-    (when (or nick password)
-      (display-warning 'erc (concat "Calling `erc-handle-irc-url' with a nick "
-                                    "or a password argument is deprecated.")))
     (unless server-buffer
       (unless connect-fn
         (user-error "Existing session for %s not found." host))
@@ -7518,7 +7517,7 @@ erc-handle-irc-url
       ;; These aren't percent-decoded by default
       (when (string-prefix-p "%" channel)
         (setq channel (url-unhex-string channel)))
-      (cl-multiple-value-setq (channel key) (split-string channel "?"))
+      (cl-multiple-value-setq (channel key) (split-string channel "[?]"))
       (if deferred
           ;; Alternatively, we could make this a defmethod, so when
           ;; autojoin is loaded, it can do its own thing.  Also, as
@@ -7532,37 +7531,44 @@ erc-handle-irc-url
         (with-current-buffer server-buffer
           (erc--join-with-callback channel key on-join))))))
 
-;; XXX ERASE ME
-;;
-;; The final spec was simplified from the 2003 Butcher draft and
-;; doesn't allow an auth@ component or trailing ,flags or &options.
-;; Because of this, we shouldn't just connect and risk exposing
-;; whatever's returned by `user-login-name'.
+;; XXX ERASE ME (possibly use as basis for new section in info doc)
 ;;
 ;; For now, as a demo, users must require erc and do something like:
 ;;
 ;;   (add-to-list 'browse-url-default-handlers
-;;                '("\\`ircs?://" . erc--handle-ircs-url))
+;;                '("\\`irc6?s?://" . erc--handle-ircs-url))
 ;;
 ;; Libraries that optionally depend on browse-url, like eww, etc. need
 ;; an extra hand as well:
 ;;
 ;;   (setq eww-use-browse-url
-;;         (concat eww-use-browse-url "\\|\\`ircs?:"))
+;;         (concat eww-use-browse-url "\\|\\`irc6?s?:"))
 ;;
 ;; Those that don't use browse-url get the same handler:
 ;;
-;;   (push '("\\bircs?://[a-z.@_+0-9%=?&/#-]+"
-;;           0 t erc--handle-ircs-url 0)
-;;         gnus-button-alist)
+;;   (add-to-list 'gnus-button-alist
+;;                '("\\birc6?s?://[][a-z0-9.,@_:+%?&/#-]+"
+;;                  0 t erc--handle-ircs-url 0))
 ;;
 ;; Finally, insert something like "ircs://testnet.ergo.chat/#test"
 ;; where appropriate and perform a suitable action.
+
+;; The two variables below are contenders for exporting as user
+;; options.  The rationale for separate functions here instead of,
+;; say, a single option granting ERC permission to connect
+;; automatically is that, since ERC doesn't lacks any concept of
+;; configured server profiles, it has no idea what values to give for
+;; connection parameters, like nick, user, etc.
+;;
+;; Also, the current spec was simplified from the 2003 Butcher draft
+;; and doesn't explicitly allow for an auth[:password]@ component (or
+;; trailing ,flags or &options, for that matter).  Regardless, even
+;; when provided, we shouldn't just connect and risk exposing
+;; whatever's returned by `user-login-name', right?
 ;;
 ;; https://www.iana.org/assignments/uri-schemes
 ;; https://datatracker.ietf.org/doc/html/draft-butcher-irc-url#section-6
 
-;; Contenders for exporting as user options.
 (defvar erc--url-irc-connect-function nil)
 (defvar erc--url-ircs-connect-function nil)
 
@@ -7586,7 +7592,7 @@ erc--handle-ircs-url
   (require 'url-parse)
   (unless (url-p url)
     (setq url (url-generic-parse-url url)))
-  (let* ((ircsp (string-match "ircs" (url-type url)))
+  (let* ((ircsp (string-suffix-p "s" (url-type url)))
          (fn (or (if ircsp
                      erc--url-ircs-connect-function
                    erc--url-irc-connect-function)
@@ -7637,6 +7643,7 @@ erc--handle-url-org-follow-ircs
 ;; present.  The following is only for demo purposes.
 
 (defun erc--org-init ()
+  ;; TODO also add irc6 and irc6s (possibly nonstandard)
   (require 'ol-irc)
   (org-link-set-parameters
    "irc"
diff --git a/lisp/thingatpt.el b/lisp/thingatpt.el
index b3dca5890f..5e597df6ff 100644
--- a/lisp/thingatpt.el
+++ b/lisp/thingatpt.el
@@ -430,7 +430,7 @@ thing-at-point-bounds-of-url-at-point
       ;; Otherwise, find the bounds within which a URI may exist.  The
       ;; method is similar to `ffap-string-at-point'.  Note that URIs
       ;; may contain parentheses but may not contain spaces (RFC3986).
-      (let* ((allowed-chars "--:=&?$+@-Z_[:alpha:]~#,%;*()!'")
+      (let* ((allowed-chars "--:=&?$+@-Z_[:alpha:]~#,%;*()!'[]")
 	     (skip-before "^[0-9a-zA-Z]")
 	     (skip-after  ":;.,!?'")
 	     (pt (point))
diff --git a/test/lisp/thingatpt-tests.el b/test/lisp/thingatpt-tests.el
index b6d0b1446a..b5f4ea8cdc 100644
--- a/test/lisp/thingatpt-tests.el
+++ b/test/lisp/thingatpt-tests.el
@@ -44,6 +44,7 @@ thing-at-point-test-data
     ;; Non alphanumeric characters can be found in URIs
     ("ftp://example.net/~foo!;#bar=baz&goo=bob" 3 url "ftp://example.net/~foo!;#bar=baz&goo=bob")
     ("bzr+ssh://[email-masked]:5/a%20d,5" 34 url "bzr+ssh://[email-masked]:5/a%20d,5")
+    ("http://[::1]:8000/foo" 10 url "http://[::1]:8000/foo")
     ;; <url:...> markup
     ("Url: <url:foo://1.example.com>..." 8 url "foo://1.example.com")
     ("Url: <url:foo://2.example.com>..." 30 url "foo://2.example.com")
-- 
2.36.1

>From 1467585c21d1bebbb3823d4c71b9265c358e1b46 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Wed, 13 Jul 2022 01:54:19 -0700
Subject: [PATCH 01/10] Teach thing-at-point to recognize bracketed IPv6 URLs

* lisp/thingatpt.el (thing-at-point-bounds-of-url-at-point): Allow
brackets.

* test/lisp/thingatpt-tests.el (thing-at-point-test-data): Add case
for IPv6 URL.
---
 lisp/thingatpt.el            | 2 +-
 test/lisp/thingatpt-tests.el | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/lisp/thingatpt.el b/lisp/thingatpt.el
index b3dca5890f..5e597df6ff 100644
--- a/lisp/thingatpt.el
+++ b/lisp/thingatpt.el
@@ -430,7 +430,7 @@ thing-at-point-bounds-of-url-at-point
       ;; Otherwise, find the bounds within which a URI may exist.  The
       ;; method is similar to `ffap-string-at-point'.  Note that URIs
       ;; may contain parentheses but may not contain spaces (RFC3986).
-      (let* ((allowed-chars "--:=&?$+@-Z_[:alpha:]~#,%;*()!'")
+      (let* ((allowed-chars "--:=&?$+@-Z_[:alpha:]~#,%;*()!'[]")
 	     (skip-before "^[0-9a-zA-Z]")
 	     (skip-after  ":;.,!?'")
 	     (pt (point))
diff --git a/test/lisp/thingatpt-tests.el b/test/lisp/thingatpt-tests.el
index b6d0b1446a..b5f4ea8cdc 100644
--- a/test/lisp/thingatpt-tests.el
+++ b/test/lisp/thingatpt-tests.el
@@ -44,6 +44,7 @@ thing-at-point-test-data
     ;; Non alphanumeric characters can be found in URIs
     ("ftp://example.net/~foo!;#bar=baz&goo=bob" 3 url "ftp://example.net/~foo!;#bar=baz&goo=bob")
     ("bzr+ssh://[email-masked]:5/a%20d,5" 34 url "bzr+ssh://[email-masked]:5/a%20d,5")
+    ("http://[::1]:8000/foo" 10 url "http://[::1]:8000/foo")
     ;; <url:...> markup
     ("Url: <url:foo://1.example.com>..." 8 url "foo://1.example.com")
     ("Url: <url:foo://2.example.com>..." 30 url "foo://2.example.com")
-- 
2.36.1

>From eb3c13c5a09634c2ae71bf3dc97bd32bcf1bef0f Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Wed, 13 Jul 2022 02:48:29 -0700
Subject: [PATCH 02/10] Accept bracketed IPv6 hosts in ERC

* lisp/erc/erc-backend.el (erc--server-connect-dumb-ipv6-regexp): Add
liberal pattern for matching bracketed IPv6 addresses.
(erc-server-connect): Remove brackets from IPv6 hosts before
connecting.
* lisp/erc/erc.el (erc-select-read-args): Keep bracketed IPv6 hosts
intact.
---
 lisp/erc/erc-backend.el | 8 ++++++++
 lisp/erc/erc.el         | 7 +++++--
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index bc7a7d14dc..2ead0c9ba5 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -532,12 +532,20 @@ erc-open-network-stream
   (let ((p (plist-put parameters :nowait t)))
     (apply #'open-network-stream name buffer host service p)))
 
+(defvar erc--server-connect-dumb-ipv6-regexp
+  ;; Likely gives false positives and false negatives
+  (rx bot "["
+      (group (+ (or (any xdigit digit ":.") (: "%" (+ alnum)))))
+      "]" eot))
+
 (defun erc-server-connect (server port buffer &optional client-certificate)
   "Perform the connection and login using the specified SERVER and PORT.
 We will store server variables in the buffer given by BUFFER.
 CLIENT-CERTIFICATE may optionally be used to specify a TLS client
 certificate to use for authentication when connecting over
 TLS (see `erc-session-client-certificate' for more details)."
+  (when (string-match erc--server-connect-dumb-ipv6-regexp server)
+    (setq server (match-string 1 server)))
   (let ((msg (erc-format-message 'connect ?S server ?p port)) process
         (args `(,(format "erc-%s-%s" server port) nil ,server ,port)))
     (when client-certificate
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 0a16831fba..a7114a4bcf 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -2348,8 +2348,11 @@ erc-select-read-args
     (setq user-input (read-string
                       "IRC server: "
                       (erc-compute-server) 'erc-server-history-list))
-
-    (if (string-match "\\(.*\\):\\(.*\\)\\'" user-input)
+    (if (and (string-match (rx (or (: (* (not "[")) ":" (* any))
+                                   (group (+ any)))
+                               ":" (group (+ (not (any ":]")))) eot)
+                           user-input)
+             (match-string 1 user-input))
         (setq port (erc-string-to-port (match-string 2 user-input))
               user-input (match-string 1 user-input))
       (setq port
-- 
2.36.1

>From 74d61e3d1e8c34cb31efe764e79ac64417b06b47 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 03/10] Default to TLS port when calling erc-tls in lisp code

* lisp/erc/erc.el (erc-normalize-port): Add standard IANA port-name
mappings for 6667 and 6697.
(erc-open): Add note to doc string explaining that params `connect'
and `channel' are mutually exclusive.
(erc-tls): Call `erc-compute-port' with override.
(erc-compute-port): When `erc-port' hasn't been set and the port param
is a string, ask `erc-normalize-port' to look it up before falling
back to `erc-default-port'.

* test/lisp/erc/erc-tests.el (erc-tls): Add simplistic test focusing
on default parameters.
---
 lisp/erc/erc.el            | 18 ++++++++++++++---
 test/lisp/erc/erc-tests.el | 41 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index a7114a4bcf..f09720fc79 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1743,6 +1743,11 @@ erc-normalize-port
 * ircs        -> 994
 * ircd        -> 6667
 * ircd-dalnet -> 7000"
+  ;; These were updated in 2022 to reflect modern standards and
+  ;; practices.  See also:
+  ;;
+  ;; https://datatracker.ietf.org/doc/html/rfc7194#section-1
+  ;; https://www.iana.org/assignments/service-names-port-numbers
   (cond
    ((symbolp port)
     (erc-normalize-port (symbol-name port)))
@@ -1751,6 +1756,8 @@ erc-normalize-port
       (cond
        ((> port-nr 0)
         port-nr)
+       ((string-equal port "ircu") 6667)
+       ((string-equal port "ircs-u") 6697)
        ((string-equal port "irc")
         194)
        ((string-equal port "ircs")
@@ -2171,7 +2178,9 @@ erc-open
 
 If CONNECT is non-nil, connect to the server.  Otherwise assume
 already connected and just create a separate buffer for the new
-target CHANNEL.
+target given by CHANNEL, meaning these parameters are mutually
+exclusive.  Note that CHANNEL may also be a query; its name has
+been retained for historical reasons.
 
 Use PASSWD as user password on the server.  If TGT-LIST is
 non-nil, use it to initialize `erc-default-recipients'.
@@ -2434,7 +2443,7 @@ 'erc-ssl
 
 ;;;###autoload
 (cl-defun erc-tls (&key (server (erc-compute-server))
-                        (port   (erc-compute-port))
+                        (port   (erc-compute-port 'ircs-u))
                         (nick   (erc-compute-nick))
                         (user   (erc-compute-user))
                         password
@@ -6656,7 +6665,10 @@ erc-compute-port
 - PORT (the argument passed to this function)
 - The `erc-port' option
 - The `erc-default-port' variable"
-  (or port erc-port erc-default-port))
+  (cond ((numberp port) port)
+	(erc-port (erc-normalize-port erc-port))
+	(port (erc-normalize-port port))
+	(t erc-default-port)))
 
 ;; time routines
 
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 4971d0e194..be95a2f8e0 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -893,4 +893,45 @@ erc-process-input-line
 
           (should-not calls))))))
 
+(ert-deftest erc-tls ()
+  (let (calls)
+    (cl-letf (((symbol-function 'user-login-name)
+               (lambda (&optional _) "tester"))
+              ((symbol-function 'erc-open)
+               (lambda (&rest r) (push r calls))))
+
+      (ert-info ("Defaults")
+        (erc-tls)
+        (should (equal (pop calls)
+                       '("irc.libera.chat" 6697 "tester" "unknown" t
+                         nil nil nil nil nil "user" nil))))
+
+      (ert-info ("Full")
+        (erc-tls :server "irc.gnu.org"
+                 :port 7000
+                 :user "bobo"
+                 :nick "bob"
+                 :full-name "Bob's Name"
+                 :password "bob:changeme"
+                 :client-certificate t
+                 :id 'GNU.org)
+        (should (equal (pop calls)
+                       '("irc.gnu.org" 7000 "bob" "Bob's Name" t
+                         "bob:changeme" nil nil nil t "bobo" GNU.org))))
+
+      ;; Values are often nil when called by lisp code, which leads to
+      ;; null params.  This is why `erc-open' recomputes everything.
+      (ert-info ("Fallback")
+        (let ((erc-nick "bob")
+              (erc-server "irc.gnu.org")
+              (erc-email-userid "bobo")
+              (erc-user-full-name "Bob's Name"))
+          (erc-tls :server nil
+                   :port 7000
+                   :nick nil
+                   :password "bob:changeme"))
+        (should (equal (pop calls)
+                       '(nil 7000 nil "Bob's Name" t
+                             "bob:changeme" nil nil nil nil "bobo" nil)))))))
+
 ;;; erc-tests.el ends here
-- 
2.36.1

>From 1bd05c2ced90a2bdc339bdd8cfc76dd67918afc5 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 04/10] Add optional server param to erc-networks--determine

* lisp/erc/erc-networks.el (erc-networks--determine): Accept optional
`server' argument.

* test/lisp/erc/erc-networks-tests.el (erc-networks--determine): Add
test.
---
 lisp/erc/erc-networks.el            |  9 +++++----
 test/lisp/erc/erc-networks-tests.el | 17 +++++++++++++++++
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el
index 091b8aa92d..95338e5f1e 100644
--- a/lisp/erc/erc-networks.el
+++ b/lisp/erc/erc-networks.el
@@ -1232,14 +1232,15 @@ erc-set-network-name
 (defconst erc-networks--name-missing-sentinel (gensym "Unknown ")
   "Value to cover rare case of a literal NETWORK=nil.")
 
-(defun erc-networks--determine ()
+(defun erc-networks--determine (&optional server)
   "Return the name of the network as a symbol.
-Search `erc-networks-alist' for a known entity matching
+Search `erc-networks-alist' for a known entity matching SERVER or
 `erc-server-announced-name'.  If that fails, use the display name
 given by the `RPL_ISUPPORT' NETWORK parameter."
   (or (cl-loop for (name matcher) in erc-networks-alist
-               when (and matcher (string-match (concat matcher "\\'")
-                                               erc-server-announced-name))
+               when (and matcher
+                         (string-match (concat matcher "\\'")
+                                       (or server erc-server-announced-name)))
                return name)
       (and-let* ((vanity (erc--get-isupport-entry 'NETWORK 'single))
                  ((intern vanity))))
diff --git a/test/lisp/erc/erc-networks-tests.el b/test/lisp/erc/erc-networks-tests.el
index 66a334b709..88b9c3ca04 100644
--- a/test/lisp/erc/erc-networks-tests.el
+++ b/test/lisp/erc/erc-networks-tests.el
@@ -1704,4 +1704,21 @@ erc-networks--update-server-identity--triple-new
 
   (erc-networks-tests--clean-bufs))
 
+(ert-deftest erc-networks--determine ()
+  (should (eq (erc-networks--determine "irc.libera.chat") 'Libera.Chat))
+  (should (eq (erc-networks--determine "irc.oftc.net") 'OFTC))
+  (should (eq (erc-networks--determine "irc.dal.net") 'DALnet))
+
+  (let ((erc-server-announced-name "zirconium.libera.chat"))
+    (should (eq (erc-networks--determine) 'Libera.Chat)))
+  (let ((erc-server-announced-name "weber.oftc.net"))
+    (should (eq (erc-networks--determine) 'OFTC)))
+  (let ((erc-server-announced-name "redemption.ix.us.dal.net"))
+    (should (eq (erc-networks--determine) 'DALnet)))
+
+  ;; Failure
+  (let ((erc-server-announced-name "irc-us2.alphachat.net"))
+    (should (eq (erc-networks--determine)
+                erc-networks--name-missing-sentinel))))
+
 ;;; erc-networks-tests.el ends here
-- 
2.36.1

>From 45afa0d04d2306b78cedfdf6eb2e04b13bdda2ba Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 05/10] Improve new connections in erc-handle-irc-url

* lisp/erc/erc.el (erc-handle-irc-url): Fix `erc-open' invocation so
that the server buffer is named correctly.  Arrange for JOINing a
channel in a manner similar to ERC's autojoin module.
(erc--handle-irc-url-connect-function,
erc--handle-ircs-url-connect-function): Add placeholders for possible
future options allowing a user to connect when clicking an IRC link
without being prompted.
(erc--handle-url-connect-function): New function to serve as an
interactive-only fallback when a user hasn't specified a connect
function.
(erc-url-ircs): Add function conforming to browse-url, and possibly
other library interfaces that offer URI integration.
---
 lisp/erc/erc.el            | 145 ++++++++++++++++++++++++++++++++-----
 test/lisp/erc/erc-tests.el |  95 ++++++++++++++++++++++++
 2 files changed, 223 insertions(+), 17 deletions(-)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index f09720fc79..f06bbc6ab0 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -7450,25 +7450,136 @@ erc-get-parsed-vector-type
 ;; Teach url.el how to open irc:// URLs with ERC.
 ;; To activate, customize `url-irc-function' to `url-irc-erc'.
 
-;; FIXME change user to nick, and use API to find server buffer
+;; FIXME update comment above once the URL business is fully settled.
+;; Also: the function `url-retrieve-internal' finds a "loader" by
+;; looking for a library providing a feature named "url-<scheme>", but
+;; no such file currently exists for "ircs".
+
 ;;;###autoload
-(defun erc-handle-irc-url (host port channel user password)
-  "Use ERC to IRC on HOST:PORT in CHANNEL as USER with PASSWORD.
+(defun erc-handle-irc-url (host port channel nick password
+                                &optional connect-fn)
+  "Use ERC to IRC on HOST:PORT in CHANNEL.
 If ERC is already connected to HOST:PORT, simply /join CHANNEL.
-Otherwise, connect to HOST:PORT as USER and /join CHANNEL."
-  (let ((server-buffer
-         (car (erc-buffer-filter
-               (lambda ()
-                 (and (string-equal erc-session-server host)
-                      (= erc-session-port port)
-                      (erc-open-server-buffer-p)))))))
-    (with-current-buffer (or server-buffer (current-buffer))
-      (if (and server-buffer channel)
-          (erc-cmd-JOIN channel)
-        (erc-open host port (or user (erc-compute-nick)) (erc-compute-full-name)
-                  (not server-buffer) password nil channel
-                  (when server-buffer
-                    (get-buffer-process server-buffer)))))))
+Otherwise, connect to HOST:PORT as NICK and /join CHANNEL.
+
+Note that ERC no longer attempts to establish new connections
+without human intervention, although opting in may eventually be
+allowed."
+  (when (eql port 0) (setq port nil))
+  (let* ((net (erc-networks--determine host))
+         (server-buffer
+          ;; Viable matches may slip through the cracks for unknown
+          ;; networks.  Additional passes could likely improve things.
+          (car (erc-buffer-filter
+                (lambda ()
+                  (and (not erc--target)
+                       (erc-server-process-alive)
+                       ;; Always trust a matched network.
+                       (or (and net (eq net (erc-network)))
+                           (and (string-equal erc-session-server host)
+                                ;; Ports only matter when dialed hosts
+                                ;; match and we have sufficient info.
+                                (or (not port)
+                                    (= (erc-normalize-port erc-session-port)
+                                       port)))))))))
+         key deferred)
+    (unless server-buffer
+      (unless connect-fn
+        (user-error "Existing session for %s not found." host))
+      (setq deferred t
+            server-buffer (apply connect-fn :server host
+                                 `(,@(and port (list :port port))
+                                   ,@(and nick (list :nick nick))
+                                   ,@(and password `(:password ,password))))))
+    (when channel
+      ;; These aren't percent-decoded by default
+      (when (string-prefix-p "%" channel)
+        (setq channel (url-unhex-string channel)))
+      (cl-multiple-value-setq (channel key) (split-string channel "[?]"))
+      (if deferred
+          ;; Alternatively, we could make this a defmethod, so when
+          ;; autojoin is loaded, it can do its own thing.  Also, as
+          ;; with `erc-once-with-server-event', it's fine to set local
+          ;; hooks here because they're killed when reconnecting.
+          (with-current-buffer server-buffer
+            (letrec ((f (lambda (&rest _)
+                          (remove-hook 'erc-after-connect f t)
+                          (erc-cmd-JOIN channel key))))
+              (add-hook 'erc-after-connect f nil t)))
+        (with-current-buffer server-buffer
+          (erc-cmd-JOIN channel key))))))
+
+;; XXX ERASE ME (possibly use as basis for new section in info doc)
+;;
+;; For now, as a demo, users must require erc and do something like:
+;;
+;;   (add-to-list 'browse-url-default-handlers
+;;                '("\\`irc6?s?://" . erc--handle-ircs-url))
+;;
+;; Libraries that optionally depend on browse-url, like eww, etc. need
+;; an extra hand as well:
+;;
+;;   (setq eww-use-browse-url
+;;         (concat eww-use-browse-url "\\|\\`irc6?s?:"))
+;;
+;; Those that don't use browse-url get the same handler:
+;;
+;;   (add-to-list 'gnus-button-alist
+;;                '("\\birc6?s?://[][a-z0-9.,@_:+%?&/#-]+"
+;;                  0 t erc--handle-ircs-url 0))
+;;
+;; Finally, insert something like "ircs://testnet.ergo.chat/#test"
+;; where appropriate and perform a suitable action.
+
+;; The two variables below are contenders for exporting as user
+;; options.  The rationale for separate functions here instead of,
+;; say, a single option granting ERC permission to connect
+;; automatically is that, since ERC doesn't lacks any concept of
+;; configured server profiles, it has no idea what values to give for
+;; connection parameters, like nick, user, etc.
+;;
+;; Also, the current spec was simplified from the 2003 Butcher draft
+;; and doesn't explicitly allow for an auth[:password]@ component (or
+;; trailing ,flags or &options, for that matter).  Regardless, even
+;; when provided, we shouldn't just connect and risk exposing
+;; whatever's returned by `user-login-name', right?
+;;
+;; https://www.iana.org/assignments/uri-schemes
+;; https://datatracker.ietf.org/doc/html/draft-butcher-irc-url#section-6
+
+(defvar erc--url-irc-connect-function nil)
+(defvar erc--url-ircs-connect-function nil)
+
+(defun erc--url-default-connect-function (ircs &rest plist)
+  (let ((erc-server (plist-get plist :server))
+        (erc-port (or (plist-get plist :port)
+                      (and ircs (erc-normalize-port 'ircs-u))
+                      erc-port))
+        (erc-nick (or (plist-get plist :nick) erc-nick)))
+    (call-interactively (if ircs #'erc-tls #'erc))))
+
+(defvar url-irc-function)
+(declare-function url-type "url-parse.el" (url) t)
+(declare-function url-p "url-parse.el" (url) t)
+
+;; FIXME rename this and autoload it
+(defun erc--handle-ircs-url (&optional url &rest _)
+  (unless url
+    (setq url (pop command-line-args-left))
+    (cl-assert url))
+  (require 'url-parse)
+  (unless (url-p url)
+    (setq url (url-generic-parse-url url)))
+  (let* ((ircsp (string-suffix-p "s" (url-type url)))
+         (fn (or (if ircsp
+                     erc--url-ircs-connect-function
+                   erc--url-irc-connect-function)
+                 (apply-partially #'erc--url-default-connect-function ircsp)))
+         (url-irc-function (lambda (&rest r)
+                             (apply #'erc-handle-irc-url `(,@r ,fn)))))
+    ;; Amazingly, this does TRT for /&chan, /#chan, /##chan, /#&chan
+    (url-irc url)))
+
 
 (provide 'erc)
 
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index be95a2f8e0..f68a7debed 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -934,4 +934,99 @@ erc-tls
                        '(nil 7000 nil "Bob's Name" t
                              "bob:changeme" nil nil nil nil "bobo" nil)))))))
 
+(defun erc-tests--make-server-buf (name)
+  (with-current-buffer (get-buffer-create name)
+    (erc-mode)
+    (setq erc-server-process (start-process "sleep" (current-buffer)
+                                            "sleep" "1")
+          erc-session-server (concat "irc." name ".org")
+          erc-session-port 6667
+          erc-network (intern name))
+    (set-process-query-on-exit-flag erc-server-process nil)
+    (current-buffer)))
+
+(defun erc-tests--make-client-buf (server name)
+  (unless (bufferp server)
+    (setq server (get-buffer server)))
+  (with-current-buffer (get-buffer-create name)
+    (erc-mode)
+    (setq erc--target (erc--target-from-string name))
+    (dolist (v '(erc-server-process
+                 erc-session-server
+                 erc-session-port
+                 erc-network))
+      (set v (buffer-local-value v server)))
+    (current-buffer)))
+
+(ert-deftest erc-handle-irc-url ()
+  (should-error (erc-handle-irc-url "irc.gnu.org" 6667 nil nil nil))
+  (let* (calls
+         rvbuf
+         erc-networks-alist
+         erc-kill-channel-hook erc-kill-server-hook erc-kill-buffer-hook
+         (connect (lambda (&rest r)
+                    (push r calls)
+                    (if (functionp rvbuf) (funcall rvbuf) rvbuf))))
+
+    (cl-letf (((symbol-function 'erc-cmd-JOIN)
+               (lambda (&rest r) (push r calls))))
+
+      (with-current-buffer (erc-tests--make-server-buf "foonet")
+        (setq rvbuf (current-buffer)))
+      (erc-tests--make-server-buf "barnet")
+      (erc-tests--make-server-buf "baznet")
+
+      (ert-info ("Unknown network")
+        (erc-handle-irc-url "irc.foonet.org" 6667 "#chan" nil nil connect)
+        (should (equal '("#chan" nil) (pop calls)))
+        (should-not calls))
+
+      (ert-info ("Unknown network, no port")
+        (erc-handle-irc-url "irc.foonet.org" nil "#chan" nil nil connect)
+        (should (equal '("#chan" nil) (pop calls)))
+        (should-not calls))
+
+      (ert-info ("Known network, no port")
+        (setq erc-networks-alist '((foonet "irc.foonet.org")))
+        (erc-handle-irc-url "irc.foonet.org" nil "#chan" nil nil connect)
+        (should (equal '("#chan" nil) (pop calls)))
+        (should-not calls))
+
+      (ert-info ("Known network, different port")
+        (erc-handle-irc-url "irc.foonet.org" 6697 "#chan" nil nil connect)
+        (should (equal '("#chan" nil) (pop calls)))
+        (should-not calls))
+
+      (ert-info ("Known network, existing chan with key")
+        (erc-tests--make-client-buf "foonet" "#chan")
+        (erc-handle-irc-url "irc.foonet.org" nil "#chan?sec" nil nil connect)
+        (should (equal '("#chan" "sec") (pop calls)))
+        (should-not calls))
+
+      (ert-info ("Unknown network, connect, no chan")
+        (erc-handle-irc-url "irc.gnu.org" nil nil nil nil connect)
+        (should (equal '(:server "irc.gnu.org") (pop calls)))
+        (should-not calls))
+
+      (ert-info ("Unknown network, connect, chan")
+        (with-current-buffer "foonet"
+          (should-not (local-variable-p 'erc-after-connect)))
+        (setq rvbuf (lambda () (erc-tests--make-server-buf "gnu")))
+        (erc-handle-irc-url "irc.gnu.org" nil "#spam" nil nil connect)
+        (should (equal '(:server "irc.gnu.org") (pop calls)))
+        (should-not calls)
+        (with-current-buffer "gnu"
+          (should (local-variable-p 'erc-after-connect))
+          (funcall (car erc-after-connect))
+          (should (equal '("#spam" nil) (pop calls)))
+          (should-not erc-after-connect)
+          (should-not (local-variable-p 'erc-after-connect)))
+        (should-not calls))))
+
+  (when noninteractive
+    (kill-buffer "foonet")
+    (kill-buffer "barnet")
+    (kill-buffer "baznet")
+    (kill-buffer "#chan")))
+
 ;;; erc-tests.el ends here
-- 
2.36.1

>From ef467e6e378b1098bfe60ac2ab25270b035a63c0 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 06/10] [POC] Make erc-once-with-server-event more nimble

* lisp/erc/erc.el (erc-once-with-server-event, erc-once-more): Allow
ephemeral callbacks to indicate a need to postpone cleanup and go
another round by signaling the new custom error called
`erc-once-again'.  Also add new optional `depth' argument to let
caller specify a hook depth.
---
 lisp/erc/erc.el | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index f06bbc6ab0..39ec11c94b 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1484,7 +1484,9 @@ erc--default-target
   (when erc--target
     (erc--target-string erc--target)))
 
-(defun erc-once-with-server-event (event f)
+(define-error 'erc-once-again "Untracked server event" 'error)
+
+(defun erc-once-with-server-event (event f &optional depth)
   "Run function F the next time EVENT occurs in the `current-buffer'.
 
 You should make sure that `current-buffer' is a server buffer.
@@ -1507,11 +1509,16 @@ erc-once-with-server-event
         (hook (erc-get-hook event)))
     (put fun 'erc-original-buffer (current-buffer))
     (fset fun (lambda (proc parsed)
-                (with-current-buffer (get fun 'erc-original-buffer)
-                  (remove-hook hook fun t))
-                (fmakunbound fun)
-                (funcall f proc parsed)))
-    (add-hook hook fun nil t)
+                (let (rv again)
+                  (condition-case _err
+                      (setq rv (funcall f proc parsed))
+                    (erc-once-again (setq again t)))
+                  (unless again
+                    (with-current-buffer (get fun 'erc-original-buffer)
+                      (remove-hook hook fun t))
+                    (fmakunbound fun))
+                  rv)))
+    (add-hook hook fun depth t)
     fun))
 
 (define-inline erc-log (string)
-- 
2.36.1

>From a46f0bc6f452aec9be43b1534f5dc385dc39e72e Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 07/10] [POC] Support one-off JOIN handlers in ERC

* lisp/erc/erc.el (erc--join-with-callback, erc-cmd-JOIN): Factor out
joining logic for use in things like URL handlers for external
integrations.  Accept a callback to run when channel is joined.
---
 lisp/erc/erc.el | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 39ec11c94b..371612b085 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -3542,6 +3542,26 @@ erc--valid-local-channel-p
                   (string-search "&" chan-types)
                 (string-match-p "&" chan-types))))))
 
+(defun erc--join-with-callback (chnl key on-join)
+  (if-let* ((existing (erc-get-buffer chnl erc-server-process))
+            ((with-current-buffer existing
+               (erc-get-channel-user (erc-current-nick)))))
+      (progn (switch-to-buffer existing)
+             (when on-join (funcall on-join)))
+    (let ((callback
+           (and on-join
+                (lambda (_ parsed)
+                  (unless (equal chnl
+                                 (car (erc-response.command-args parsed)))
+                    (signal 'erc-once-again nil))
+                  (with-current-buffer (erc-get-buffer chnl erc-server-process)
+                    (funcall on-join))
+                  nil))))
+      (setq erc--server-last-reconnect-count 0)
+      (when callback
+        (erc-once-with-server-event 'JOIN callback 90))
+      (erc-server-join-channel nil chnl key))))
+
 (defun erc-cmd-JOIN (channel &optional key)
   "Join the channel given in CHANNEL, optionally with KEY.
 If CHANNEL is specified as \"-invite\", join the channel to which you
@@ -3554,12 +3574,7 @@ erc-cmd-JOIN
       (setq chnl (erc-ensure-channel-name channel)))
     (when chnl
       ;; Prevent double joining of same channel on same server.
-      (if-let* ((existing (erc-get-buffer chnl erc-server-process))
-                ((with-current-buffer existing
-                   (erc-get-channel-user (erc-current-nick)))))
-          (switch-to-buffer existing)
-        (setq erc--server-last-reconnect-count 0)
-        (erc-server-join-channel nil chnl key))))
+      (erc--join-with-callback chnl key nil)))
   t)
 
 (defalias 'erc-cmd-CHANNEL #'erc-cmd-JOIN)
-- 
2.36.1

>From 596835a935222f4625820340bf8d86a487646ace Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 08/10] [POC] Use erc--join-with-callback in URL handler

* lisp/erc/erc.el (erc-handle-irc-url): Accept new `on-join' one-off
JOIN handler and pass it to `erc--join-with-callback'.

* test/lisp/erc/erc-tests.el (erc-handle-irc-url): Use
`erc--join-with-callback' instead of `erc-cmd-JOIN'.
---
 lisp/erc/erc.el            | 6 +++---
 test/lisp/erc/erc-tests.el | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 371612b085..fd91441828 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -7479,7 +7479,7 @@ erc-get-parsed-vector-type
 
 ;;;###autoload
 (defun erc-handle-irc-url (host port channel nick password
-                                &optional connect-fn)
+                                &optional connect-fn on-join)
   "Use ERC to IRC on HOST:PORT in CHANNEL.
 If ERC is already connected to HOST:PORT, simply /join CHANNEL.
 Otherwise, connect to HOST:PORT as NICK and /join CHANNEL.
@@ -7526,10 +7526,10 @@ erc-handle-irc-url
           (with-current-buffer server-buffer
             (letrec ((f (lambda (&rest _)
                           (remove-hook 'erc-after-connect f t)
-                          (erc-cmd-JOIN channel key))))
+                          (erc--join-with-callback channel key on-join))))
               (add-hook 'erc-after-connect f nil t)))
         (with-current-buffer server-buffer
-          (erc-cmd-JOIN channel key))))))
+          (erc--join-with-callback channel key on-join))))))
 
 ;; XXX ERASE ME (possibly use as basis for new section in info doc)
 ;;
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index f68a7debed..947b45e1dc 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -968,8 +968,8 @@ erc-handle-irc-url
                     (push r calls)
                     (if (functionp rvbuf) (funcall rvbuf) rvbuf))))
 
-    (cl-letf (((symbol-function 'erc-cmd-JOIN)
-               (lambda (&rest r) (push r calls))))
+    (cl-letf (((symbol-function 'erc--join-with-callback)
+               (lambda (&rest r) (push (butlast r) calls))))
 
       (with-current-buffer (erc-tests--make-server-buf "foonet")
         (setq rvbuf (current-buffer)))
-- 
2.36.1

>From 35c018604c93d7f7f4a52393c7e55dab185c0f90 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 05:14:57 -0700
Subject: [PATCH 09/10] [POC] Demo improved ol-irc integration

* lisp/erc/erc.el (erc--org-init, erc--handle-url-org-visit,
erc--handle-url-org-visit-irc, erc--handle-url-org-visit-ircs): Add
various functions to demo org link integration.
---
 lisp/erc/erc.el | 52 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index fd91441828..7137a7b401 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -7602,6 +7602,58 @@ erc--handle-ircs-url
     ;; Amazingly, this does TRT for /&chan, /#chan, /##chan, /#&chan
     (url-irc url)))
 
+;; ERASE ME
+;;
+;; Org's ol-irc.el is pretty elaborate.  But a lot of things have to
+;; go perfectly for joining and prompting to work as intended.
+
+(defun erc--handle-url-org-visit (ircsp link)
+  ;; The dispatcher that calls `org-irc-visit' strips the scheme and
+  ;; colon, leaving only "//irc.gnu.org/#chan", which becomes
+  ;; (("irc.gnu.org") "#chan") when parsed by `org-irc-parse-link'.
+  (pcase-let*
+      ((`((,server ,port) ,channel ,nick) link)
+       (oj (and nick
+                (lambda ()
+                  (cl-assert nick)
+                  ;; Channel may not be populated yet
+                  (unless (erc-get-server-user nick)
+                    (erc-error "%s not found in %s" nick (erc-default-target)))
+                  (goto-char erc-input-marker)
+                  (insert (concat nick ": ")))))
+       (fn (or (if ircsp
+                   erc--url-ircs-connect-function
+                 erc--url-irc-connect-function)
+               (apply-partially #'erc--url-default-connect-function
+                                (and ircsp t)))))
+    (erc-handle-irc-url server port channel nil nil fn oj)))
+
+(declare-function org-irc-parse-link "ol-irc" (link))
+(declare-function org-link-get-parameter "ol" (type key))
+(declare-function org-link-set-parameters "ol" (type &rest parameters))
+
+(defun erc--handle-url-org-follow-irc (link _)
+  (erc--handle-url-org-visit nil (org-irc-parse-link link)))
+
+(defun erc--handle-url-org-follow-ircs (link _)
+  (erc--handle-url-org-visit t (org-irc-parse-link link)))
+
+;; Eventually, we should petition for `org-irc-visit-erc' to call our
+;; stuff to do the heavy lifting, assuming a new enough Emacs is
+;; present.  The following is only for demo purposes.
+
+(defun erc--org-init ()
+  ;; TODO also add irc6 and irc6s (possibly nonstandard)
+  (require 'ol-irc)
+  (org-link-set-parameters
+   "irc"
+   :follow #'erc--handle-url-org-follow-irc)
+  (org-link-set-parameters
+   "ircs"
+   :follow #'erc--handle-url-org-follow-ircs
+   :store (org-link-get-parameter "irc" :store)
+   :export (org-link-get-parameter "irc" :export)))
+
 
 (provide 'erc)
 
-- 
2.36.1

>From 1ef37c2aeff57b5d81a6ebd64a5a0d505203d923 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 11 Jul 2022 22:07:08 -0700
Subject: [PATCH 10/10] [POC] * etc/emacs-irc.desktop: New file.

---
 etc/emacs-irc.desktop | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 etc/emacs-irc.desktop

diff --git a/etc/emacs-irc.desktop b/etc/emacs-irc.desktop
new file mode 100644
index 0000000000..ebdcda3a07
--- /dev/null
+++ b/etc/emacs-irc.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Name=Emacs (IRC)
+GenericName=Chat client
+Keywords=ERC;extensible;chat;IRC;client;
+Categories=Network;Chat;IRCClient;
+Comment=GNU Emacs is an extensible, customizable text editor - ERC is a powerful, modular, and extensible IRC client for Emacs
+# FIXME update command line and name once autoloaded
+Exec=emacs -l erc -f erc--handle-ircs-url %u
+Icon=emacs
+MimeType=x-scheme-handler/irc;x-scheme-handler/ircs;
+NoDisplay=true
+Terminal=false
+Type=Application
-- 
2.36.1