/** IRC message codec + casemap (modern.ircdocs.horse). No import. */ var BARE_IRC_MAX_LINE = 512 var BARE_IRC_MAX_TAGS = 8191 function bareIrcStripCrlf(s) { var t = String(s == null ? '' : s) if (t.charAt(t.length - 1) === '\n') t = t.slice(0, -1) if (t.charAt(t.length - 1) === '\r') t = t.slice(0, -1) return t } function bareIrcUnescapeTagValue(v) { var s = String(v == null ? '' : v) var out = '' for (var i = 0; i < s.length; i++) { var c = s.charAt(i) if (c !== '\\') { out += c continue } var n = s.charAt(i + 1) if (n === ':') { out += ';' i++ } else if (n === 's') { out += ' ' i++ } else if (n === '\\') { out += '\\' i++ } else if (n === 'r') { out += '\r' i++ } else if (n === 'n') { out += '\n' i++ } else if (n) { out += n i++ } } return out } function bareIrcEscapeTagValue(v) { var s = String(v == null ? '' : v) var out = '' for (var i = 0; i < s.length; i++) { var c = s.charAt(i) if (c === ';') out += '\\:' else if (c === ' ') out += '\\s' else if (c === '\\') out += '\\\\' else if (c === '\r') out += '\\r' else if (c === '\n') out += '\\n' else out += c } return out } /** * @param {string} line * @returns {{ tags: Record, source: string, nick: string, user: string, host: string, command: string, params: string[] } | null} */ function bareIrcParseMessage(line) { var raw = bareIrcStripCrlf(line) if (!raw) return null var tags = {} var i = 0 if (raw.charAt(0) === '@') { var sp = raw.indexOf(' ') if (sp < 0) return null var tagStr = raw.slice(1, sp) i = sp + 1 while (i < raw.length && raw.charAt(i) === ' ') i++ var parts = tagStr.split(';') for (var t = 0; t < parts.length; t++) { var item = parts[t] if (!item) continue var eq = item.indexOf('=') if (eq < 0) tags[item] = '' else tags[item.slice(0, eq)] = bareIrcUnescapeTagValue(item.slice(eq + 1)) } raw = raw.slice(i) i = 0 } var source = '' var nick = '' var user = '' var host = '' if (raw.charAt(0) === ':') { var sp2 = raw.indexOf(' ') if (sp2 < 0) return null source = raw.slice(1, sp2) raw = raw.slice(sp2 + 1) while (raw.charAt(0) === ' ') raw = raw.slice(1) var bang = source.indexOf('!') var at = source.indexOf('@') if (bang >= 0) { nick = source.slice(0, bang) if (at > bang) { user = source.slice(bang + 1, at) host = source.slice(at + 1) } else user = source.slice(bang + 1) } else if (at >= 0) { nick = source.slice(0, at) host = source.slice(at + 1) } else nick = source } while (raw.charAt(0) === ' ') raw = raw.slice(1) var cmdEnd = raw.indexOf(' ') var command var rest if (cmdEnd < 0) { command = raw rest = '' } else { command = raw.slice(0, cmdEnd) rest = raw.slice(cmdEnd + 1) } if (!command) return null var params = [] while (rest.length) { while (rest.charAt(0) === ' ') rest = rest.slice(1) if (!rest) break if (rest.charAt(0) === ':') { params.push(rest.slice(1)) break } var nsp = rest.indexOf(' ') if (nsp < 0) { params.push(rest) break } params.push(rest.slice(0, nsp)) rest = rest.slice(nsp + 1) } return { tags: tags, source: source, nick: nick, user: user, host: host, command: command, params: params } } /** * @param {{ tags?: Record, source?: string, command: string, params?: string[] }} msg */ function bareIrcSerializeMessage(msg) { var m = msg || {} var out = '' var tags = m.tags if (tags && typeof tags === 'object') { var keys = [] var k for (k in tags) { if (Object.prototype.hasOwnProperty.call(tags, k)) keys.push(k) } if (keys.length) { out += '@' for (var i = 0; i < keys.length; i++) { if (i) out += ';' var val = tags[keys[i]] if (val == null || val === '') out += keys[i] else out += keys[i] + '=' + bareIrcEscapeTagValue(val) } out += ' ' } } if (m.source) out += ':' + m.source + ' ' out += String(m.command || '') var params = Array.isArray(m.params) ? m.params : [] for (var p = 0; p < params.length; p++) { var part = String(params[p] == null ? '' : params[p]) var last = p === params.length - 1 if (last) { out += ' :' + part } else { out += ' ' + part } } return out + '\r\n' } /** * Byte/string accumulator. Returns complete lines without the delimiter. */ function bareIrcCreateLineBuffer() { var acc = '' return { push: function (chunk) { if (chunk == null) return [] var s if (typeof chunk === 'string') s = chunk else if (typeof TextDecoder !== 'undefined') { s = new TextDecoder('utf-8', { fatal: false }).decode( chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk) ) } else { var u = chunk instanceof Uint8Array ? chunk : [] s = '' for (var i = 0; i < u.length; i++) s += String.fromCharCode(u[i]) } acc += s.replace(/\r\n/g, '\n').replace(/\r/g, '\n') var out = [] for (;;) { var n = acc.indexOf('\n') if (n < 0) break var line = acc.slice(0, n) acc = acc.slice(n + 1) if (line) out.push(line) } return out }, pending: function () { return acc } } } function bareIrcRfc1459LowerChar(c) { var code = c.charCodeAt(0) if (code >= 65 && code <= 90) return String.fromCharCode(code + 32) if (c === '[') return '{' if (c === ']') return '}' if (c === '\\') return '|' if (c === '~') return '^' return c } function bareIrcCasemap(s, mapping) { var t = String(s == null ? '' : s) var kind = String(mapping || 'rfc1459').toLowerCase() if (kind === 'ascii') return t.toLowerCase() var out = '' for (var i = 0; i < t.length; i++) out += bareIrcRfc1459LowerChar(t.charAt(i)) return out } function bareIrcEqual(a, b, mapping) { return bareIrcCasemap(a, mapping) === bareIrcCasemap(b, mapping) } function bareIrcIsChannel(name, chantypes) { var n = String(name || '') if (!n) return false var types = chantypes || '#' return types.indexOf(n.charAt(0)) >= 0 }