/** * 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.' ) } 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 () { if (spec.generatedNick && live.state.nick) { bareIrcRememberNick(ctx, live.state.nick) } 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 } }