From 41184136303032e8207bc37ad40ae2cffd019706 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Sat, 6 May 2023 16:19:24 +0100 Subject: [PATCH 1/9] Message tag support --- src/irc.ts | 11 ++++-- src/parse_message.ts | 85 ++++++++++++++++++++++++++++++----------- test/data/fixtures.json | 36 ++++++++++++++--- test/test-parse-line.js | 14 ++++--- 4 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/irc.ts b/src/irc.ts index e45da403..21acea7f 100644 --- a/src/irc.ts +++ b/src/irc.ts @@ -197,7 +197,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter TypedEmitter TypedEmitter; prefix?: string; server?: string; nick?: string; @@ -13,6 +14,15 @@ export interface Message { commandType: CommandType; } +interface ParserOptions { + supportsMessageTags: boolean; + stripColors: boolean; +} + +const IRC_LINE_MATCH = /^:(?[^ ]+) +(?.+)/; + +const IRC_LINE_MATCH_WITH_TAGS = /^(?@[^ ]+ )?:(?[^ ]+) +(?.+)/; + /** * parseMessage(line, stripColors) * @@ -22,36 +32,66 @@ export interface Message { * @param stripColors If true, strip IRC colors. * @return A parsed message object. */ -export function parseMessage(line: string, stripColors: boolean): Message { +export function parseMessage(line: string, opts: Partial|boolean = false): Message { + if (typeof opts === "boolean") { + opts = { + stripColors: opts, + } + } + const message: Message = { args: [], commandType: 'normal', }; - if (stripColors) { + + if (opts.stripColors) { line = stripColorsAndStyle(line); } // Parse prefix - let match = line.match(/^:([^ ]+) +/); - if (match) { - message.prefix = match[1]; - line = line.replace(/^:[^ ]+ +/, ''); - match = message.prefix.match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/); - if (match) { - message.nick = match[1]; - message.user = match[3]; - message.host = match[4]; - } - else { - message.server = message.prefix; - } + let match = line.match(opts.supportsMessageTags ? IRC_LINE_MATCH_WITH_TAGS : IRC_LINE_MATCH); + if (!match) { + // Unparseable format. + throw Error(`Invalid format, could not parse message '${line}''`); + } + + const { prefix, tags, content } = match.groups || {}; + if (!prefix) { + throw Error('No prefix on message'); + } + message.prefix = prefix; + const prefixMatch = message.prefix.match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/); + + if (prefixMatch) { + message.nick = prefixMatch[1]; + message.user = prefixMatch[3]; + message.host = prefixMatch[4]; + } + else { + message.server = message.prefix; + } + + // Parse the message tags + if (tags) { + message.tags = new Map( + // Strip @ + tags.substring(1).trim().split(';').map( + (tag) => tag.split('=', 2) + ) as Array<[string, string|undefined]> + ); } // Parse command - match = line.match(/^([^ ]+) */); - message.command = match?.[1]; - message.rawCommand = match?.[1]; - line = line.replace(/^[^ ]+ +/, ''); + match = content.match(/^([^ ]+) */); + + if (!match?.[1]) { + throw Error('Could not parse command'); + } + + message.command = match[1]; + message.rawCommand = match[1]; + + const parameters = content.substring(message.rawCommand.length).trim(); if (message.rawCommand && replyCodes[message.rawCommand]) { message.command = replyCodes[message.rawCommand].name; message.commandType = replyCodes[message.rawCommand].type; @@ -60,16 +100,17 @@ export function parseMessage(line: string, stripColors: boolean): Message { let middle, trailing; // Parse parameters - if (line.search(/^:| +:/) !== -1) { - match = line.match(/(.*?)(?:^:| +:)(.*)/); + if (parameters.search(/^:| +:/) !== -1) { + match = parameters.match(/(.*?)(?:^:| +:)(.*)/); if (!match) { + console.log('Egg!'); throw Error('Invalid format, could not parse parameters'); } middle = match[1].trimEnd(); trailing = match[2]; } else { - middle = line; + middle = parameters; } if (middle.length) {message.args = middle.split(/ +/);} diff --git a/test/data/fixtures.json b/test/data/fixtures.json index b6fc871a..a80e70c7 100644 --- a/test/data/fixtures.json +++ b/test/data/fixtures.json @@ -101,6 +101,9 @@ "args": ["#channel", "so : colons: :are :: not a problem ::::"] }, ":nick!user@host PRIVMSG #channel :\u000314,01\u001fneither are colors or styles\u001f\u0003": { + "opts": { + "stripColors": true + }, "prefix": "nick!user@host", "nick": "nick", "user": "user", @@ -108,10 +111,12 @@ "command": "PRIVMSG", "rawCommand": "PRIVMSG", "commandType": "normal", - "args": ["#channel", "neither are colors or styles"], - "stripColors": true + "args": ["#channel", "neither are colors or styles"] }, ":nick!user@host PRIVMSG #channel :\u000314,01\u001fwe can leave styles and colors alone if desired\u001f\u0003": { + "opts": { + "stripColors": false + }, "prefix": "nick!user@host", "nick": "nick", "user": "user", @@ -119,8 +124,22 @@ "command": "PRIVMSG", "rawCommand": "PRIVMSG", "commandType": "normal", - "args": ["#channel", "\u000314,01\u001fwe can leave styles and colors alone if desired\u001f\u0003"], - "stripColors": false + "args": ["#channel", "\u000314,01\u001fwe can leave styles and colors alone if desired\u001f\u0003"] + }, + "@msgid=63E1033A051D4B41B1AB1FA3CF4B243E :nick!user@host PRIVMSG #channel :Hello!": { + "opts": { + "supportsMessageTags": true, + "stripColors": false + }, + "prefix": "nick!user@host", + "nick": "nick", + "user": "user", + "host": "host", + "command": "PRIVMSG", + "rawCommand": "PRIVMSG", + "commandType": "normal", + "tags": [ [ "msgid", "63E1033A051D4B41B1AB1FA3CF4B243E" ] ], + "args": ["#channel", "Hello!"] }, ":pratchett.freenode.net 324 nodebot #ubuntu +CLcntjf 5:10 #ubuntu-unregged": { "prefix": "pratchett.freenode.net", @@ -129,8 +148,15 @@ "rawCommand": "324", "commandType": "reply", "args": ["nodebot", "#ubuntu", "+CLcntjf", "5:10", "#ubuntu-unregged"] + }, + "PING mynick": { + "prefix": "pratchett.freenode.net", + "server": "pratchett.freenode.net", + "command": "rpl_channelmodeis", + "rawCommand": "324", + "commandType": "reply", + "args": ["nodebot", "#ubuntu", "+CLcntjf", "5:10", "#ubuntu-unregged"] } - }, "433-before-001": { "sent": [ diff --git a/test/test-parse-line.js b/test/test-parse-line.js index 12d0db36..26bbed1a 100644 --- a/test/test-parse-line.js +++ b/test/test-parse-line.js @@ -7,14 +7,18 @@ test('irc.parseMessage', function(t) { const checks = testHelpers.getFixtures('parse-line'); Object.keys(checks).forEach(function(line) { - let stripColors = false; - if (checks[line].hasOwnProperty('stripColors')) { - stripColors = checks[line].stripColors; - delete checks[line].stripColors; + let opts = {}; + if (checks[line].opts) { + opts = checks[line].opts; + delete checks[line].opts; } + const message = parseMessage(line, opts); t.deepEqual( + { + ...message, + ...(message.tags && {tags: [...message.tags.entries()]}), + }, checks[line], - parseMessage(line, stripColors), line + ' parses correctly' ); }); From 8822d9f8ddc20431d72864adb428aa443535c2f6 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Sat, 6 May 2023 16:33:24 +0100 Subject: [PATCH 2/9] Use capability negotiations --- src/capabilities.ts | 4 +++ src/irc.ts | 2 +- src/parse_message.ts | 68 +++++++++++++++++++++++++------------------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/capabilities.ts b/src/capabilities.ts index bc297227..7e07d67d 100644 --- a/src/capabilities.ts +++ b/src/capabilities.ts @@ -67,6 +67,10 @@ export class IrcCapabilities extends (EventEmitter as new () => IrcCapabilitiesE return this.userCapabilites.ready; } + public isSupported(capability: string) { + return this.userCapabilites.caps.has(capability); + } + public get supportsSasl() { if (!this.serverCapabilites.ready) { throw Error('Server response has not arrived yet'); diff --git a/src/irc.ts b/src/irc.ts index 21acea7f..5663928a 100644 --- a/src/irc.ts +++ b/src/irc.ts @@ -1364,7 +1364,7 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter[^ ]+) +(?.+)/; +const IRC_LINE_MATCH_REGEX = /^:(?[^ ]+) +(?.+)/; +const IRC_LINE_MATCH_WITH_TAGS_REGEX = /^(?@[^ ]+ )?:(?[^ ]+) +(?.+)/; + +const IRC_COMMAND_REGEX = /^([^ ]+) */; -const IRC_LINE_MATCH_WITH_TAGS = /^(?@[^ ]+ )?:(?[^ ]+) +(?.+)/; /** * parseMessage(line, stripColors) @@ -29,7 +34,8 @@ const IRC_LINE_MATCH_WITH_TAGS = /^(?@[^ ]+ )?:(?[^ ]+) +(?|boolean = false): Message { @@ -49,40 +55,42 @@ export function parseMessage(line: string, opts: Partial|boolean } // Parse prefix - let match = line.match(opts.supportsMessageTags ? IRC_LINE_MATCH_WITH_TAGS : IRC_LINE_MATCH); - if (!match) { - // Unparseable format. - throw Error(`Invalid format, could not parse message '${line}''`); - } + let match = line.match(opts.supportsMessageTags ? IRC_LINE_MATCH_WITH_TAGS_REGEX : IRC_LINE_MATCH_REGEX); + let content = line; + if (match) { + const { prefix, tags, content: ctnt } = match.groups || {}; + content = ctnt; + if (!prefix) { + throw Error('No prefix on message'); + } + message.prefix = prefix; + const prefixMatch = message.prefix.match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/); - const { prefix, tags, content } = match.groups || {}; - if (!prefix) { - throw Error('No prefix on message'); - } - message.prefix = prefix; - const prefixMatch = message.prefix.match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/); + if (prefixMatch) { + message.nick = prefixMatch[1]; + message.user = prefixMatch[3]; + message.host = prefixMatch[4]; + } + else { + message.server = message.prefix; + } - if (prefixMatch) { - message.nick = prefixMatch[1]; - message.user = prefixMatch[3]; - message.host = prefixMatch[4]; + // Parse the message tags + if (tags) { + message.tags = new Map( + // Strip @ + tags.substring(1).trim().split(';').map( + (tag) => tag.split('=', 2) + ) as Array<[string, string|undefined]> + ); + } } else { - message.server = message.prefix; - } - - // Parse the message tags - if (tags) { - message.tags = new Map( - // Strip @ - tags.substring(1).trim().split(';').map( - (tag) => tag.split('=', 2) - ) as Array<[string, string|undefined]> - ); + // Still allowed, it might just be a command } // Parse command - match = content.match(/^([^ ]+) */); + match = content.match(IRC_COMMAND_REGEX); if (!match?.[1]) { throw Error('Could not parse command'); From 79cb54e1880280de5df47ae7579808a11d8de13b Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Sat, 6 May 2023 16:33:31 +0100 Subject: [PATCH 3/9] Ensure we don't break simple commands --- test/data/fixtures.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/data/fixtures.json b/test/data/fixtures.json index a80e70c7..17b4a515 100644 --- a/test/data/fixtures.json +++ b/test/data/fixtures.json @@ -150,12 +150,10 @@ "args": ["nodebot", "#ubuntu", "+CLcntjf", "5:10", "#ubuntu-unregged"] }, "PING mynick": { - "prefix": "pratchett.freenode.net", - "server": "pratchett.freenode.net", - "command": "rpl_channelmodeis", - "rawCommand": "324", - "commandType": "reply", - "args": ["nodebot", "#ubuntu", "+CLcntjf", "5:10", "#ubuntu-unregged"] + "args": [ "mynick" ], + "commandType": "normal", + "command": "PING", + "rawCommand": "PING" } }, "433-before-001": { From d5243934bbf5174cd4d6004834e2036618c5e724 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 May 2023 13:27:08 +0100 Subject: [PATCH 4/9] changelog --- changelog.d/105.feature | 1 + src/parse_message.ts | 24 +++++++++++++++++++++++- test/data/fixtures.json | 37 ++++++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 changelog.d/105.feature diff --git a/changelog.d/105.feature b/changelog.d/105.feature new file mode 100644 index 00000000..746d17f2 --- /dev/null +++ b/changelog.d/105.feature @@ -0,0 +1 @@ +Add support for [message tags](https://ircv3.net/specs/extensions/message-tags). \ No newline at end of file diff --git a/src/parse_message.ts b/src/parse_message.ts index 06640522..7b7e9929 100644 --- a/src/parse_message.ts +++ b/src/parse_message.ts @@ -80,7 +80,29 @@ export function parseMessage(line: string, opts: Partial|boolean message.tags = new Map( // Strip @ tags.substring(1).trim().split(';').map( - (tag) => tag.split('=', 2) + (tag) => { + const parts = tag.split('='); + return [ + parts.splice(0, 1)[0], + parts.join('=').replace(/\\(s|\\|r|n|:)/g, (char) => { + // https://ircv3.net/specs/extensions/message-tags#escaping-values + switch (char) { + case "\\s": + return " "; + case "\\r": + return "\r"; + case "\\n": + return "\n"; + case "\\\\": + return '\\'; + case "\\:": + return ';'; + default: + return char; + } + }), + ] + } ) as Array<[string, string|undefined]> ); } diff --git a/test/data/fixtures.json b/test/data/fixtures.json index 17b4a515..e21eb84b 100644 --- a/test/data/fixtures.json +++ b/test/data/fixtures.json @@ -128,8 +128,7 @@ }, "@msgid=63E1033A051D4B41B1AB1FA3CF4B243E :nick!user@host PRIVMSG #channel :Hello!": { "opts": { - "supportsMessageTags": true, - "stripColors": false + "supportsMessageTags": true }, "prefix": "nick!user@host", "nick": "nick", @@ -141,9 +140,37 @@ "tags": [ [ "msgid", "63E1033A051D4B41B1AB1FA3CF4B243E" ] ], "args": ["#channel", "Hello!"] }, - ":pratchett.freenode.net 324 nodebot #ubuntu +CLcntjf 5:10 #ubuntu-unregged": { - "prefix": "pratchett.freenode.net", - "server": "pratchett.freenode.net", + "@+example=raw+:=,escaped\\:\\s\\\\ :nick!user@example.com PRIVMSG #channel :Message": { + "opts": { + "supportsMessageTags": true + }, + "user": "user", + "nick": "nick", + "prefix": "nick!user@example.com", + "host": "example.com", + "command": "PRIVMSG", + "rawCommand": "PRIVMSG", + "commandType": "normal", + "tags": [ [ "+example", "raw+:=,escaped; \\" ] ], + "args": ["#channel", "Message"] + }, + "@label=123;msgid=abc;+example-client-tag=example-value :nick!user@example.com TAGMSG #channel": { + "opts": { + "supportsMessageTags": true + }, + "prefix": "nick!user@example.com", + "nick": "nick", + "user": "user", + "host": "example.com", + "command": "TAGMSG", + "rawCommand": "TAGMSG", + "commandType": "normal", + "tags":[ [ "label", "123" ], [ "msgid", "abc" ], [ "+example-client-tag", "example-value" ] ], + "args": ["#channel"] + }, + ":host.foo.bar 324 nodebot #ubuntu +CLcntjf 5:10 #ubuntu-unregged": { + "prefix": "host.foo.bar", + "server": "host.foo.bar", "command": "rpl_channelmodeis", "rawCommand": "324", "commandType": "reply", From 18b142025bffbfdbe2baed2875345f40d908eeba Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 May 2023 16:45:32 +0100 Subject: [PATCH 5/9] Test escape normal chars --- test/data/fixtures.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/data/fixtures.json b/test/data/fixtures.json index e21eb84b..337d445f 100644 --- a/test/data/fixtures.json +++ b/test/data/fixtures.json @@ -140,7 +140,7 @@ "tags": [ [ "msgid", "63E1033A051D4B41B1AB1FA3CF4B243E" ] ], "args": ["#channel", "Hello!"] }, - "@+example=raw+:=,escaped\\:\\s\\\\ :nick!user@example.com PRIVMSG #channel :Message": { + "@+example=raw+:=,escaped\\:\\s\\b\\\\ :nick!user@example.com PRIVMSG #channel :Message": { "opts": { "supportsMessageTags": true }, @@ -151,7 +151,7 @@ "command": "PRIVMSG", "rawCommand": "PRIVMSG", "commandType": "normal", - "tags": [ [ "+example", "raw+:=,escaped; \\" ] ], + "tags": [ [ "+example", "raw+:=,escaped; b\\" ] ], "args": ["#channel", "Message"] }, "@label=123;msgid=abc;+example-client-tag=example-value :nick!user@example.com TAGMSG #channel": { From 8d9ee822323303014110e756f7dbcbd2afafaa65 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 May 2023 16:48:09 +0100 Subject: [PATCH 6/9] also escape \. --- src/parse_message.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parse_message.ts b/src/parse_message.ts index 7b7e9929..0095fe11 100644 --- a/src/parse_message.ts +++ b/src/parse_message.ts @@ -84,7 +84,7 @@ export function parseMessage(line: string, opts: Partial|boolean const parts = tag.split('='); return [ parts.splice(0, 1)[0], - parts.join('=').replace(/\\(s|\\|r|n|:)/g, (char) => { + parts.join('=').replace(/\\./g, (char) => { // https://ircv3.net/specs/extensions/message-tags#escaping-values switch (char) { case "\\s": @@ -98,7 +98,7 @@ export function parseMessage(line: string, opts: Partial|boolean case "\\:": return ';'; default: - return char; + return char[1]; } }), ] From 3cb1e2934b87a55994c3f2591a97c78ea2474594 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 May 2023 18:59:22 +0100 Subject: [PATCH 7/9] Additional capabilities --- src/capabilities.ts | 32 ++++++++++++++++++++++++++++++-- src/irc.ts | 14 +++++++++++--- src/parse_message.ts | 4 ++-- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/capabilities.ts b/src/capabilities.ts index 7e07d67d..db5bcca9 100644 --- a/src/capabilities.ts +++ b/src/capabilities.ts @@ -2,6 +2,29 @@ import EventEmitter from "events"; import { Message } from "./parse_message"; import TypedEmitter from "typed-emitter"; +export enum IrcCapability { + /** + * https://ircv3.net/specs/extensions/account-tag + */ + AccountTag = "account-tag", + /** + * https://ircv3.net/specs/extensions/server-time + */ + ServerTime = "server-time", + /** + * https://ircv3.net/specs/extensions/batch + */ + Batch = "batch", + /** + * https://ircv3.net/specs/extensions/message-tags + */ + MessageTags = "message-tags", + /** + * https://ircv3.net/specs/extensions/sasl-3.2 + */ + Sasl = "sasl" +} + class Capabilities { constructor( public readonly caps = new Set(), @@ -67,8 +90,13 @@ export class IrcCapabilities extends (EventEmitter as new () => IrcCapabilitiesE return this.userCapabilites.ready; } - public isSupported(capability: string) { - return this.userCapabilites.caps.has(capability); + /** + * Is at least one of the given capabilities supported. + * @param capability A named capability string. + * @returns True if any of the capabilities are supported, false if none of them are. + */ + public isSupported(...capabilities: IrcCapability[]) { + return capabilities.some(capability => this.userCapabilites.caps.has(capability.toString())); } public get supportsSasl() { diff --git a/src/irc.ts b/src/irc.ts index 53670ded..7fda235a 100644 --- a/src/irc.ts +++ b/src/irc.ts @@ -30,6 +30,7 @@ import splitLongLines from './splitLines'; import TypedEmitter from "typed-emitter"; import { ClientEvents, CtcpEventIndex, JoinEventIndex, MessageEventIndex, PartEventIndex } from './events'; import { DefaultIrcSupported, IrcClientState, IrcInMemoryState, WhoisResponse } from './state'; +import { IrcCapability } from './capabilities'; const lineDelimiter = new RegExp('\r\n|\r|\n'); const MIN_DELAY_MS = 33; @@ -293,10 +294,12 @@ export class Client extends (EventEmitter as unknown as new () => TypedEmitter TypedEmitter|boolean message.tags = new Map( // Strip @ tags.substring(1).trim().split(';').map( - (tag) => { + tag => { const parts = tag.split('='); return [ parts.splice(0, 1)[0], - parts.join('=').replace(/\\./g, (char) => { + parts.join('=').replace(/\\./g, char => { // https://ircv3.net/specs/extensions/message-tags#escaping-values switch (char) { case "\\s": From a515f96aa24024a77310dada30e4400aafc42252 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 18 May 2023 18:18:57 +0100 Subject: [PATCH 8/9] Add tests without prefix --- test/data/fixtures.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/data/fixtures.json b/test/data/fixtures.json index 337d445f..8825fac2 100644 --- a/test/data/fixtures.json +++ b/test/data/fixtures.json @@ -181,6 +181,22 @@ "commandType": "normal", "command": "PING", "rawCommand": "PING" + }, + "PRIVMSG #test :Hello nodebot!": { + "command": "PRIVMSG", + "rawCommand": "PRIVMSG", + "commandType": "normal", + "args": ["#test", "Hello nodebot!"] + }, + "@label=123;msgid=abc;+example-client-tag=example-value TAGMSG #channel": { + "opts": { + "supportsMessageTags": true + }, + "command": "TAGMSG", + "rawCommand": "TAGMSG", + "commandType": "normal", + "tags":[ [ "label", "123" ], [ "msgid", "abc" ], [ "+example-client-tag", "example-value" ] ], + "args": ["#channel"] } }, "433-before-001": { From 46a6e1be822cc58537f5ca915212dce07c262dcb Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 18 May 2023 18:19:09 +0100 Subject: [PATCH 9/9] Start to support without prefixes --- src/parse_message.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/parse_message.ts b/src/parse_message.ts index 2c1ae273..4518fb55 100644 --- a/src/parse_message.ts +++ b/src/parse_message.ts @@ -22,8 +22,9 @@ interface ParserOptions { stripColors: boolean; } -const IRC_LINE_MATCH_REGEX = /^:(?[^ ]+) +(?.+)/; -const IRC_LINE_MATCH_WITH_TAGS_REGEX = /^(?@[^ ]+ )?:(?[^ ]+) +(?.+)/; +const IRC_LINE_MATCH_REGEX = /^(?:[^ ]+) +(?.+)/; +const IRC_LINE_MATCH_WITH_TAGS_REGEX = /^(?@[^ ]+ )?(?:[^ ]+)? +(?.+)/; +const IRC_PREFIX_REGEX = /^(?[_a-zA-Z0-9\[\]\\`^{}|-]*)?(!(?[^@]+))?@?(?.*)$/ const IRC_COMMAND_REGEX = /^([^ ]+) */; @@ -54,22 +55,24 @@ export function parseMessage(line: string, opts: Partial|boolean line = stripColorsAndStyle(line); } - // Parse prefix + // Parse prefix and tags let match = line.match(opts.supportsMessageTags ? IRC_LINE_MATCH_WITH_TAGS_REGEX : IRC_LINE_MATCH_REGEX); + + // Assume content is the full line unless we pull a prefix and/or tags out. let content = line; + if (match) { const { prefix, tags, content: ctnt } = match.groups || {}; content = ctnt; - if (!prefix) { - throw Error('No prefix on message'); - } - message.prefix = prefix; - const prefixMatch = message.prefix.match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/); + message.prefix = prefix?.substring(1); + const prefixMatch = message.prefix?.match(IRC_PREFIX_REGEX); + + console.log("PREFIX:", prefixMatch, message.prefix); - if (prefixMatch) { - message.nick = prefixMatch[1]; - message.user = prefixMatch[3]; - message.host = prefixMatch[4]; + if (prefixMatch?.groups) { + message.nick = prefixMatch.groups.nick; + message.user = prefixMatch.groups.user; + message.host = prefixMatch.groups.host; } else { message.server = message.prefix; @@ -133,7 +136,6 @@ export function parseMessage(line: string, opts: Partial|boolean if (parameters.search(/^:| +:/) !== -1) { match = parameters.match(/(.*?)(?:^:| +:)(.*)/); if (!match) { - console.log('Egg!'); throw Error('Invalid format, could not parse parameters'); } middle = match[1].trimEnd();