diff options
| -rw-r--r-- | src/papod.clj | 153 |
1 files changed, 151 insertions, 2 deletions
diff --git a/src/papod.clj b/src/papod.clj index d155b27..9f148c3 100644 --- a/src/papod.clj +++ b/src/papod.clj @@ -1058,6 +1058,7 @@ true)) (declare join-one!) +(declare notify-monitors!) (defn- persistent-channels "Channel handles where `nick` has stored membership." @@ -1190,7 +1191,8 @@ " MAXTARGETS=4" " :are supported by this server")) (numeric-reply client "005" - (str "MODES=4 NETWORK=papod" + (str "MODES=4 MONITOR=100" + " NETWORK=papod" " NICKLEN=30 PREFIX=(ov)@+" " STATUSMSG=@+" " TARGMAX=KICK:1,LIST:,WHOIS:1,PRIVMSG:4,NAMES:" @@ -1206,6 +1208,7 @@ " unread memo(s)." " Use /msg MemoServ LIST")]) rejoins (auto-rejoin-replies client components)] + (notify-monitors! components nick "730") (vec (concat welcome memo-line rejoins))))))) (defn- valid-nick? @@ -1323,6 +1326,13 @@ @notified mn)))] (deliver-to-client! (:w m) line) (swap! notified conj mn)))) + ;; MONITOR notifications: only on real (non-case) + ;; nick changes — old goes offline, new comes + ;; online. + (when (not= (string/lower-case old-nick) + (string/lower-case new-nick)) + (notify-monitors! components old-nick "731") + (notify-monitors! components new-nick "730")) [line]) ;; Pre-registration: just set the nick (do (swap! client assoc :nick new-nick) @@ -3778,6 +3788,130 @@ [(numeric-reply client "306" ":You have been marked as being away")])))) +(def- +monitor-limit+ 100) + +(defn- nick-online? + [clients nick] + (let [low (string/lower-case nick)] + (boolean + (some (fn [[k _]] (= (string/lower-case k) low)) + @clients)))) + +(defn- mononline-line + [client target] + (numeric-reply client "730" (str ":" target))) + +(defn- monoffline-line + [client target] + (numeric-reply client "731" (str ":" target))) + +(defn- handle-monitor + [params client components] + (let [{:keys [clients nick-monitors]} components + sub (some-> (first params) string/upper-case) + arg (second params) + targets (when arg (string/split arg #",")) + valid? (fn [t] (and (seq t) (valid-nick? t)))] + (case sub + "+" + (let [valid-ts (filter valid? targets) + invalid-ts (remove valid? targets) + current (or (:monitor-targets @client) #{}) + new-lows (->> valid-ts + (map string/lower-case) + (remove current)) + next-set (into current new-lows)] + (cond + (> (count next-set) +monitor-limit+) + [(numeric-reply client "734" + (str +monitor-limit+ " " + (string/join "," targets) + " :Monitor list is full."))] + :else + (do (swap! client assoc :monitor-targets next-set) + (when nick-monitors + (doseq [low new-lows] + (swap! nick-monitors update low + (fnil conj #{}) client))) + (let [online (filter + #(nick-online? clients %) + valid-ts) + offline (concat + invalid-ts + (remove + #(nick-online? clients %) + valid-ts))] + (cond-> [] + (seq online) + (conj (mononline-line client + (string/join "," online))) + (seq offline) + (conj (monoffline-line client + (string/join "," offline)))))))) + + "-" + (let [removed-lows (->> targets + (map string/lower-case) + set) + current (or (:monitor-targets @client) #{}) + next-set (into #{} + (remove removed-lows current))] + (swap! client assoc :monitor-targets next-set) + (when nick-monitors + (doseq [low removed-lows] + (swap! nick-monitors update low + (fnil disj #{}) client))) + []) + + "C" + (let [current (or (:monitor-targets @client) #{})] + (swap! client assoc :monitor-targets #{}) + (when nick-monitors + (doseq [low current] + (swap! nick-monitors update low + (fnil disj #{}) client))) + []) + + "L" + (let [current (or (:monitor-targets @client) #{}) + list-line (when (seq current) + [(numeric-reply client "732" + (str ":" + (string/join "," current)))])] + (vec + (concat list-line + [(numeric-reply client "733" + ":End of MONITOR list")]))) + + "S" + (let [current (or (:monitor-targets @client) #{}) + online (filter #(nick-online? clients %) + current) + offline (remove (set online) current)] + (cond-> [] + (seq online) + (conj (mononline-line client + (string/join "," online))) + (seq offline) + (conj (monoffline-line client + (string/join "," offline))))) + + [(numeric-reply client "461" + "MONITOR :Not enough parameters")]))) + +(defn- notify-monitors! + [components nick numeric] + (when-let [nm (:nick-monitors components)] + (let [low (string/lower-case nick) + watchers (get @nm low #{})] + (doseq [watcher watchers + :let [w (:w @watcher) + wn (or (:nick @watcher) "*")] + :when w] + (deliver-to-client! w + (str ":" +server-name+ " " numeric " " + wn " :" nick)))))) + (defn- handle-setname [params client components] (let [{:keys [clients channels]} components @@ -4174,6 +4308,9 @@ (= command "SETNAME") (handle-setname params client components) + (= command "MONITOR") + (handle-monitor params client components) + (= command "NOTICE") (handle-notice params client components) @@ -5334,6 +5471,7 @@ :process-id process-id :clients (atom {}) :account-clients (atom {}) + :nick-monitors (atom {}) :channels (atom {}) :ops (atom {}) :voiced (atom {}) @@ -5422,7 +5560,18 @@ (let [entry (get @(:clients components) nick)] (when (or (nil? entry) (= client (:client-atom entry))) - (swap! (:clients components) dissoc nick)))) + (swap! (:clients components) dissoc nick) + ;; Notify monitors only when the nick is + ;; actually going offline (no other session + ;; remains for it). + (when (:registered? @client) + (notify-monitors! components nick + "731"))))) + ;; Drop our own monitor subscriptions. + (when-let [nm (:nick-monitors components)] + (doseq [low (or (:monitor-targets @client) + #{})] + (swap! nm update low (fnil disj #{}) client))) (when (:channels components) (doseq [[ch _] @(:channels components)] (swap! (:channels components) update ch disj nick))) |
