@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user