Files
bare-operating-system/packages/bare-os-coreutils/lib/irc-parse.js
T
Raven Scott 1187e30cf6
Release rolling / release (push) Successful in 9m31s
Add irc
2026-08-12 23:50:49 -04:00

253 lines
6.3 KiB
JavaScript

/** 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<string, string>, 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<string, string>, 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
}