export class IRCMessage { constructor( public tags: Map, public prefix: string, public command: string, public params: string[], ) {} toString() { let messageString = ''; if (this.tags.size !== 0) { let tagArray = []; for (const [key, value] of this.tags) { if (value === '') { tagArray.push(key); } else { tagArray.push(`${key}=${encodeTag(value)}`); } } messageString = `@${tagArray.join(";")} `; } if (this.prefix) { messageString = `${messageString}:${this.prefix} `; } messageString = `${messageString}${this.command}`; const params = this.params.slice(); let lastParam = params.pop(); if (lastParam) { if (lastParam.indexOf(' ') !== -1) { lastParam = `:${lastParam}`; } params.push(lastParam); } messageString = `${messageString} ${params.join(' ')}`; return messageString; } } function decodeTag(value: string): string { const tagDecodeMap = new Map([ ['\\:', ';'], ['\\s', ' '], ['\\\\', '\\'], ['\\r', '\r'], ['\\n', '\n'], ['\\', ''], ]); return value.replace(/\\:|\\s|\\\\|\\r|\\n|\\/gi, t => tagDecodeMap.get(t) || '') } function encodeTag(value: string): string { const tagEncodeMap = new Map([ [';', '\\:'], [' ', '\\s'], ['\\', '\\\\'], ['\r', '\\r'], ['\n', '\\n'], ]); return value.replace(/;| |\\|\r|\n/gi, t => tagEncodeMap.get(t) || ''); } function addToTags(key: string): boolean { const tagsToPass = [ 'batch', 'reflectionircd.chat/delete-message', 'reflectionircd.chat/edit-message', 'label', ] return (tagsToPass.includes(key) || key.startsWith('+')); } export function parseIRCMessage(rawLine: string) { //console.log(`RAW: ${rawLine}`); let restOfMessage = rawLine; let parsedTags: Map = new Map(); let prefix = ''; let command = ''; let params: string[] = []; if (rawLine.startsWith('@')) { const tags = restOfMessage.substring(1, restOfMessage.indexOf(' ')); restOfMessage = restOfMessage.substring(restOfMessage.indexOf(' ')+1); for (const tag of tags.split(';')) { const valueSplit = tag.indexOf('='); if (valueSplit === -1 && addToTags(tag)) { parsedTags.set(tag, ''); continue; } const key = tag.substring(0, valueSplit); const value = tag.substring(valueSplit+1); if (addToTags(key)) parsedTags.set(key, decodeTag(value)); } } if (restOfMessage.startsWith(':')) { prefix = restOfMessage.substring(0, restOfMessage.indexOf(' ')); restOfMessage = restOfMessage.substring(restOfMessage.indexOf(' ')+1); } if (restOfMessage.indexOf(' ') === -1) { command = restOfMessage; return new IRCMessage(parsedTags, prefix, command, params); } command = restOfMessage.substring(0, restOfMessage.indexOf(' ')); restOfMessage = restOfMessage.substring(restOfMessage.indexOf(' ') + 1); let lastParam = ''; if (restOfMessage.indexOf(' :') !== -1) { lastParam = restOfMessage.substring(restOfMessage.indexOf(' :') + 2); restOfMessage = restOfMessage.substring(0, restOfMessage.indexOf(' :')); } params = restOfMessage.split(' '); if (lastParam !== '') { params.push(lastParam); } //console.log(parsedTags, prefix, command, params); return new IRCMessage(parsedTags, prefix, command, params); }