summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2026-04-29 17:10:23 -0300
committerEuAndreh <eu@euandre.org>2026-04-29 17:10:23 -0300
commit5f101f4166dc26cb8a4b823c94a4d175cff2514c (patch)
tree3d58b3381c31a77244d3853410b935b029d5ce0b /src
parentFix mute extban exemption, list query, ISUPPORT split, line limit (diff)
downloadpapod-5f101f4166dc26cb8a4b823c94a4d175cff2514c.tar.gz
papod-5f101f4166dc26cb8a4b823c94a4d175cff2514c.tar.xz
Implement draft/metadata-2 before-connect optional behavior
Clients with draft/metadata-2 + batch can now METADATA * SET/GET/LIST/ CLEAR before completing registration. Pre-registration values are buffered on the client atom and replayed in a metadata BATCH at the end of the welcome burst, then merged into the global metadata atom under the registered nick. The value-suffix form ("draft/metadata-2=before-connect") is only sent in CAP LS 302 replies; CAP LS without a 302 version still emits the bare name to avoid IRCv3.1 clients seeing name=value capabilities.
Diffstat (limited to 'src')
-rw-r--r--src/papod.clj116
1 files changed, 112 insertions, 4 deletions
diff --git a/src/papod.clj b/src/papod.clj
index d96bb87..e20bec8 100644
--- a/src/papod.clj
+++ b/src/papod.clj
@@ -1225,9 +1225,28 @@
" :You have " (count memos)
" unread memo(s)."
" Use /msg MemoServ LIST")])
- rejoins (auto-rejoin-replies client components)]
+ rejoins (auto-rejoin-replies client components)
+ pending (:pending-metadata @client)
+ meta-burst
+ (when (seq pending)
+ (let [bid (str (java.util.UUID/randomUUID))
+ nlow (string/lower-case nick)]
+ (when-let [meta-atom (:metadata components)]
+ (swap! meta-atom update nlow
+ (fnil into {}) pending))
+ (swap! client dissoc :pending-metadata)
+ (concat
+ [(str "BATCH +" bid " metadata " nick)]
+ (map
+ (fn [[k v]]
+ (str "@batch=" bid
+ " :" +server-name+
+ " METADATA " nick " " k
+ " * :" v))
+ (sort-by first pending))
+ [(str "BATCH -" bid)])))]
(notify-monitors! components nick "730")
- (vec (concat welcome memo-line rejoins)))))))
+ (vec (concat welcome memo-line meta-burst rejoins)))))))
(defn- valid-nick?
[s]
@@ -1439,7 +1458,7 @@
reg-cap
" draft/channel-rename"
" draft/extended-isupport"
- " draft/metadata-2"
+ " draft/metadata-2=before-connect"
" draft/typing")
(str "sasl message-tags server-time"
" echo-message batch"
@@ -3800,8 +3819,96 @@
(client-target client)
requested))
+(defn- pending-metadata-handler
+ "Handles METADATA SET/GET/LIST/CLEAR before registration completes
+ (draft/metadata-2 before-connect). Values are buffered in the
+ client atom until the welcome burst replays them."
+ [params client]
+ (let [target (first params)
+ subcmd (when (> (count params) 1)
+ (string/upper-case (second params)))
+ rest-args (drop 2 params)]
+ (cond
+ (or (nil? target) (string/blank? target))
+ [(numeric-reply client "461"
+ "METADATA :Not enough parameters")]
+
+ (nil? subcmd)
+ [(numeric-reply client "461"
+ "METADATA :Not enough parameters")]
+
+ ;; Only "*" target makes sense pre-registration.
+ (not= target "*")
+ [(str "FAIL METADATA INVALID_TARGET "
+ target " :Invalid target")]
+
+ :else
+ (case subcmd
+ "GET"
+ (let [keys-asked (vec rest-args)
+ cur (or (:pending-metadata @client) {})]
+ (mapv
+ (fn [k]
+ (if-let [v (get cur k)]
+ (numeric-reply client "761"
+ (str "* " k " * :" v))
+ (numeric-reply client "766"
+ (str "* " k
+ " :no matching key"))))
+ keys-asked))
+
+ "LIST"
+ (let [cur (or (:pending-metadata @client) {})]
+ (mapv
+ (fn [[k v]]
+ (numeric-reply client "761"
+ (str "* " k " * :" v)))
+ (sort-by first cur)))
+
+ "SET"
+ (let [k (first rest-args)
+ v-parts (rest rest-args)
+ raw-v (when (seq v-parts)
+ (string/join " " v-parts))
+ v (when raw-v
+ (cond-> raw-v
+ (string/starts-with? raw-v ":") (subs 1)))
+ v-bytes (when v
+ (alength (.getBytes ^String v "UTF-8")))]
+ (cond
+ (nil? k)
+ [(numeric-reply client "461"
+ "METADATA :Not enough parameters")]
+
+ (and v-bytes (> v-bytes 360))
+ [(str "FAIL METADATA VALUE_INVALID :Value of "
+ k " is too long")]
+
+ (or (nil? v) (string/blank? v))
+ (do (swap! client update :pending-metadata
+ (fnil dissoc {}) k)
+ [(numeric-reply client "766"
+ (str "* " k " :no matching key"))])
+
+ :else
+ (do (swap! client update :pending-metadata
+ (fnil assoc {}) k v)
+ [(numeric-reply client "761"
+ (str "* " k " * :" v))])))
+
+ "CLEAR"
+ (do (swap! client assoc :pending-metadata {})
+ [(numeric-reply client "762"
+ "* :end of metadata")])
+
+ ;; SUB/UNSUB/SUBS pre-reg: not useful.
+ [(numeric-reply client "461"
+ "METADATA :Not enough parameters")]))))
+
(defn- handle-metadata
[params client components]
+ (if-not (:registered? @client)
+ (pending-metadata-handler params client)
(let [nick (client-target client)
target (first params)
subcmd (when (> (count params) 1)
@@ -3954,7 +4061,7 @@
":none"))])
[(str "FAIL METADATA INVALID_SUBCOMMAND "
- subcmd " :Unknown subcommand")]))))
+ subcmd " :Unknown subcommand")])))))
(defn- handle-markread
[params client components]
@@ -4936,6 +5043,7 @@
"CAP" (handle-cap params client components)
"AUTHENTICATE" (handle-authenticate params client components)
"REGISTER" (handle-register params client components)
+ "METADATA" (handle-metadata params client components)
"BATCH" (let [ref (first params)]
(cond
(and ref (string/starts-with? ref "+"))