From 329f9063d0c82393dbc6ffaa842b87929467c187 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 21 Sep 2021 16:58:00 +0200 Subject: [PATCH] Add support for WHOX This allows querying the account of the user. --- components/app.js | 15 ++++++--- lib/client.js | 86 +++++++++++++++++++++++++++++++++++++++++++++-- lib/irc.js | 1 + state.js | 20 +++++------ 4 files changed, 105 insertions(+), 17 deletions(-) diff --git a/components/app.js b/components/app.js index 6b73607..b0d6b4d 100644 --- a/components/app.js +++ b/components/app.js @@ -585,8 +585,7 @@ export default class App extends Component { continue; } this.createBuffer(serverID, buf.name); - client.who(buf.name); - client.monitor(buf.name); + this.whoUserBuffer(buf.name, serverID); } let lastReceipt = this.latestReceipt(ReceiptType.DELIVERED); @@ -853,6 +852,15 @@ export default class App extends Component { }); } + whoUserBuffer(target, serverID) { + let client = this.clients.get(serverID); + + client.who(target, { + fields: ["flags", "hostname", "nick", "realname", "username", "account"], + }); + client.monitor(target); + } + open(target, serverID) { if (!serverID) { serverID = State.getActiveServerID(this.state); @@ -865,8 +873,7 @@ export default class App extends Component { this.switchToChannel = target; client.send({ command: "JOIN", params: [target] }); } else { - client.who(target); - client.monitor(target); + this.whoUserBuffer(target, serverID); this.createBuffer(serverID, target); this.switchBuffer({ server: serverID, name: target }); } diff --git a/lib/client.js b/lib/client.js index 464db7d..3553e8d 100644 --- a/lib/client.js +++ b/lib/client.js @@ -30,7 +30,21 @@ const NORMAL_CLOSURE = 1000; const GOING_AWAY = 1001; const UNSUPPORTED_DATA = 1003; +// See https://github.com/quakenet/snircd/blob/master/doc/readme.who +// Sorted by order of appearance in RPL_WHOSPCRPL +const WHOX_FIELDS = { + "channel": "c", + "username": "u", + "hostname": "h", + "server": "s", + "nick": "n", + "flags": "f", + "account": "a", + "realname": "r", +}; + let lastLabel = 0; +let lastWhoxToken = 0; export default class Client extends EventTarget { static Status = { @@ -65,6 +79,7 @@ export default class Client extends EventTarget { cm = irc.CaseMapping.RFC1459; monitored = new irc.CaseMapMap(null, irc.CaseMapping.RFC1459); whoisDB = new irc.CaseMapMap(null, irc.CaseMapping.RFC1459); + whoxQueries = new Map(); constructor(params) { super(); @@ -332,14 +347,43 @@ export default class Client extends EventTarget { } } - who(mask) { - let msg = { command: "WHO", params: [mask] }; + who(mask, options) { + let params = [mask]; + + let fields = "", token = ""; + if (options && this.isupport.has("WHOX")) { + let match = ""; // Matches exact channel or nick + + fields = "t"; // Always include token in reply + if (options.fields) { + options.fields.forEach((k) => { + if (!WHOX_FIELDS[k]) { + throw new Error(`Unknown WHOX field ${k}`); + } + fields += WHOX_FIELDS[k]; + }); + } + + token = String(lastWhoxToken % 1000); + lastWhoxToken++; + + params.push(`${match}%${fields},${token}`); + this.whoxQueries.set(token, fields); + } + + let msg = { command: "WHO", params }; let l = []; return this.roundtrip(msg, (msg) => { switch (msg.command) { case irc.RPL_WHOREPLY: // TODO: match with mask - l.push(msg); + l.push(this.parseWhoReply(msg)); + break; + case irc.RPL_WHOSPCRPL: + if (msg.params.length !== fields.length || msg.params[1] !== token) { + break; + } + l.push(this.parseWhoReply(msg)); break; case irc.RPL_ENDOFWHO: if (msg.params[1] === mask) { @@ -347,9 +391,45 @@ export default class Client extends EventTarget { } break; } + }).finally(() => { + this.whoxQueries.delete(token); }); } + parseWhoReply(msg) { + switch (msg.command) { + case irc.RPL_WHOREPLY: + let last = msg.params[msg.params.length - 1]; + return { + username: msg.params[2], + hostname: msg.params[3], + server: msg.params[4], + nick: msg.params[5], + flags: msg.params[6], + realname: last.slice(last.indexOf(" ") + 1), + }; + case irc.RPL_WHOSPCRPL: + let token = msg.params[1]; + let fields = this.whoxQueries.get(token); + if (!fields) { + throw new Error("Unknown WHOX token: " + token); + } + let who = {}; + let i = 0; + Object.keys(WHOX_FIELDS).forEach((k) => { + if (fields.indexOf(WHOX_FIELDS[k]) < 0) { + return; + } + + who[k] = msg.params[2 + i]; + i++; + }); + return who; + default: + throw new Error("Not a WHO reply: " + msg.command); + } + } + whois(target) { let targetCM = this.cm(target); let msg = { command: "WHOIS", params: [target] }; diff --git a/lib/irc.js b/lib/irc.js index 38d6470..5a7b8a5 100644 --- a/lib/irc.js +++ b/lib/irc.js @@ -23,6 +23,7 @@ export const RPL_EXCEPTLIST = "348"; export const RPL_ENDOFEXCEPTLIST = "349"; export const RPL_WHOREPLY = "352"; export const RPL_NAMREPLY = "353"; +export const RPL_WHOSPCRPL = "354"; export const RPL_ENDOFNAMES = "366"; export const RPL_BANLIST = "367"; export const RPL_ENDOFBANLIST = "368"; diff --git a/state.js b/state.js index 9865f9d..e095007 100644 --- a/state.js +++ b/state.js @@ -363,16 +363,16 @@ export const State = { case irc.RPL_ENDOFNAMES: break; case irc.RPL_WHOREPLY: - let last = msg.params[msg.params.length - 1]; - who = { - username: msg.params[2], - hostname: msg.params[3], - server: msg.params[4], - nick: msg.params[5], - away: msg.params[6] == 'G', // H for here, G for gone - realname: last.slice(last.indexOf(" ") + 1), - offline: false, - }; + case irc.RPL_WHOSPCRPL: + let who = client.parseWhoReply(msg); + + if (who.flags !== undefined) { + who.away = who.flags.indexOf("G") >= 0; // H for here, G for gone + delete who.flags; + } + + who.offline = false; + return updateUser(who.nick, who); case irc.RPL_ENDOFWHO: target = msg.params[1];