Add irc
Release rolling / release (push) Successful in 9m31s

This commit is contained in:
Raven Scott
2026-08-12 23:50:49 -04:00
parent bbfe873414
commit 1187e30cf6
53 changed files with 6693 additions and 32 deletions
+226
View File
@@ -0,0 +1,226 @@
/**
* Modern IRC client. Default: Libera.Chat TLS irc.libera.chat:6697.
* TTY → ctx.tui; otherwise scriptable: status | send | join | query | whois.
*/
function ircUsage(argv0) {
return (
'usage: ' +
(argv0 || 'irc') +
' [libera|HOST] [--tls|--plain] [--insecure-plain] [--nick N] [--sasl-user U]\n' +
' ' +
(argv0 || 'irc') +
' send TARGET TEXT | join #chan | query NICK [TEXT] | whois NICK | status\n' +
'Default network: irc.libera.chat:6697 (TLS). Config: ~/.irc/config.json\n' +
'See man irc.'
)
}
function bareIrcDial(ctx, spec) {
if (typeof ctx.bareOsIrcHostAllowed === 'function') {
var gate = ctx.bareOsIrcHostAllowed(spec.host)
if (gate && gate.ok === false) {
throw new Error(
'irc: host blocked (' + (gate.reason || 'policy') + '): ' + spec.host
)
}
}
if (spec.tls === false) {
var plainOk =
typeof ctx.bareOsIrcPlaintextAllowed === 'function'
? ctx.bareOsIrcPlaintextAllowed(spec.host, {
tls: false,
insecurePlain: spec.insecurePlain
})
: { ok: !bareIrcHostLooksLibera(spec.host) || spec.insecurePlain }
if (plainOk && plainOk.ok === false) {
throw new Error('irc: ' + (plainOk.reason || 'plaintext blocked'))
}
if (typeof ctx.bareOsTelnetConnect !== 'function') {
throw new Error('irc: no plaintext connector (ctx.bareOsTelnetConnect)')
}
return ctx.bareOsTelnetConnect(spec.host, spec.port, {})
}
if (typeof ctx.bareOsTlsConnect !== 'function') {
throw new Error(
'irc: ctx.bareOsTlsConnect missing (need booter ctx API 1.57+)'
)
}
return ctx.bareOsTlsConnect(spec.host, spec.port, {
servername: spec.host,
rejectUnauthorized: true
})
}
async function bareIrcEnsureLiveClient(ctx, spec) {
if (
ctx._bareIrcClient &&
ctx._bareIrcClient.state.connection === 'registered'
) {
return ctx._bareIrcClient
}
var sock = bareIrcDial(ctx, spec)
var client = bareIrcCreateClient(spec)
client.attach(sock)
ctx._bareIrcClient = client
return client
}
function bareIrcWaitRegistered(client, ms) {
return new Promise(function (resolve, reject) {
if (client.state.registered) {
resolve(client)
return
}
var t = setTimeout(function () {
reject(new Error('irc: register timeout'))
}, ms || 20000)
client.on('registered', function () {
clearTimeout(t)
resolve(client)
})
client.on('error', function (p) {
clearTimeout(t)
reject(
p && p.error ? p.error : new Error(p && p.text ? p.text : 'irc error')
)
})
})
}
async function run(ctx, argv) {
var flags = bareIrcParseFlags(argv)
if (flags.help) {
ctx.console.log(ircUsage(argv[0]))
return
}
var loaded = await bareIrcLoadConfig(ctx)
var spec = bareIrcResolveConnect(ctx, flags, loaded)
var sub = flags.rest[0] || ''
if (sub === 'status') {
var c = ctx._bareIrcClient
var info = {
connection: c ? c.state.connection : 'idle',
nick: c ? c.state.nick : spec.nick,
host: spec.host,
port: spec.port,
tls: spec.tls,
network: spec.network
}
ctx.console.log(
flags.json ? JSON.stringify(info) : JSON.stringify(info, null, 2)
)
return
}
if (sub === 'send' || sub === 'join' || sub === 'query' || sub === 'whois') {
try {
var client = await bareIrcEnsureLiveClient(ctx, spec)
await bareIrcWaitRegistered(client, 25000)
if (sub === 'send') {
var target = flags.rest[1]
var text = flags.rest.slice(2).join(' ')
if (!target || !text) {
ctx.console.error('irc: send TARGET TEXT')
ctx.exitCode = 1
return
}
if (!client.sendPrivmsg(target, text)) {
ctx.console.error('irc: send failed (rate limit or no socket)')
ctx.exitCode = 1
}
return
}
if (sub === 'join') {
if (!flags.rest[1]) {
ctx.console.error('irc: join #channel')
ctx.exitCode = 1
return
}
client.join(flags.rest[1])
return
}
if (sub === 'query') {
if (!flags.rest[1]) {
ctx.console.error('irc: query NICK [TEXT]')
ctx.exitCode = 1
return
}
if (flags.rest[2])
client.sendPrivmsg(flags.rest[1], flags.rest.slice(2).join(' '))
return
}
if (sub === 'whois') {
if (!flags.rest[1]) {
ctx.console.error('irc: whois NICK')
ctx.exitCode = 1
return
}
client.sendRaw('WHOIS', [flags.rest[1]])
return
}
} catch (e) {
ctx.console.error('irc: ' + ((e && e.message) || String(e)))
ctx.exitCode = 1
}
return
}
var stdin = ctx.replStdin
var isTTY = Boolean(stdin && stdin.isTTY)
if (!isTTY || !ctx.tui || typeof ctx.tui.run !== 'function') {
ctx.console.error(
'irc: TTY + ctx.tui required for full-screen mode; use: irc send TARGET TEXT'
)
ctx.exitCode = 1
return
}
try {
if (typeof ctx.bareOsIrcHostAllowed === 'function') {
var allow = ctx.bareOsIrcHostAllowed(spec.host)
if (allow && allow.ok === false) {
ctx.console.error(
'irc: host blocked (' + (allow.reason || 'policy') + '): ' + spec.host
)
ctx.exitCode = 1
return
}
}
var socket = bareIrcDial(ctx, spec)
var live = bareIrcCreateClient(spec)
live.attach(socket)
live.on('registered', function () {
var joins = spec.autojoin || []
for (var i = 0; i < joins.length; i++) live.join(joins[i])
if (typeof ctx.bareOsIrcPresencePublish === 'function') {
try {
ctx.bareOsIrcPresencePublish({
schema: 1,
network: spec.network || 'libera',
nick: live.state.nick,
account: live.state.account || '',
channels: spec.shareChannels ? joins : [],
atMs: Date.now()
})
} catch (e) {
/* ignore */
}
}
})
var app = bareIrcCreateTuiApp(ctx, live)
try {
await ctx.tui.run(app, bareIrcTuiRunOpts())
} finally {
try {
live.quit('Client closed')
} catch (e2) {
/* ignore */
}
}
} catch (err) {
ctx.console.error('irc: ' + ((err && err.message) || String(err)))
ctx.exitCode = 1
}
}