IRC Commands
Release rolling / release (push) Canceled after 8m43s

This commit is contained in:
Raven Scott
2026-08-13 00:33:57 -04:00
parent 15afc148d7
commit 8bb8254c42
22 changed files with 3799 additions and 1228 deletions
+219 -88
View File
@@ -17,6 +17,10 @@ function bareIrcCreateTuiApp(ctx, client) {
height: size0.height || 24,
help: false,
quitConfirm: false,
roomsOpen: false,
roomsCursor: 0,
roomsFilterEdit: false,
openedRoomsOnce: false,
filterOpen: false,
status: 'connecting…',
input: tui.textinput.create({ prompt: '', focused: true, charLimit: 450 }),
@@ -38,10 +42,6 @@ function bareIrcCreateTuiApp(ctx, client) {
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
client.on('join', function () {
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
client.on('sasl', function (p) {
self.status = p && p.ok ? 'sasl ok' : 'sasl failed'
if (tui && typeof tui.send === 'function')
@@ -59,9 +59,67 @@ function bareIrcCreateTuiApp(ctx, client) {
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
client.on('join', function () {
self.roomsOpen = false
self.roomsFilterEdit = false
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
client.on('list', function () {
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
client.on('listend', function (p) {
self.status = 'rooms ' + ((p && p.count) || 0)
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
client.on('motd_end', function () {
var joins = (client.opts && client.opts.autojoin) || []
if (!self.openedRoomsOnce && (!joins || !joins.length)) {
self.openedRoomsOnce = true
self.roomsOpen = true
self.roomsCursor = 0
self.status = 'pick a room (Enter join / filter Esc close)'
}
var srv = bareIrcEnsureBuffer(client.state, 'server', '*server')
bareIrcPushLine(client.state, srv, {
atMs: Date.now(),
kind: 'info',
text:
'You are ' +
(client.state.nick || 'guest') +
'. /rooms browse /join #libera help /query nick ? keys'
})
if (tui && typeof tui.send === 'function')
tui.send({ type: 'irc.paint' })
})
this._sync()
return null
},
_roomRows: function () {
var listed = this.client.state.channelList || []
if (listed.length) return listed
return BARE_IRC_FEATURED_ROOMS
},
_openRooms: function (query) {
this.roomsOpen = true
this.roomsCursor = 0
this.roomsFilterEdit = false
if (query) this.client.list(query)
this.status = query
? 'listing ' + query
: 'featured rooms (type /rooms linux to search)'
},
_joinRoomAtCursor: function () {
var rows = this._roomRows()
var hit = rows[this.roomsCursor]
if (!hit || !hit.name) return
this.roomsOpen = false
this.roomsFilterEdit = false
this.client.join(hit.name)
this.status = 'joining ' + hit.name
},
_current: function () {
var st = this.client.state
return (
@@ -73,9 +131,42 @@ function bareIrcCreateTuiApp(ctx, client) {
buf.unread = 0
buf.highlight = false
var cols = Math.max(40, this.width || 80)
var nickW = cols >= 100 ? 16 : 0
var nickW = !this.roomsOpen && cols >= 100 ? 16 : 0
this.viewport.width = Math.max(20, cols - nickW - 2)
this.viewport.height = Math.max(4, (this.height || 24) - 6)
if (this.roomsOpen) {
var rows = this._roomRows()
if (this.roomsCursor >= rows.length)
this.roomsCursor = Math.max(0, rows.length - 1)
var rlines = []
var ri
for (ri = 0; ri < rows.length; ri++) {
var rr = rows[ri]
var mark = ri === this.roomsCursor ? '>' : ' '
var users =
rr.users === '' || rr.users == null
? ' '
: String(rr.users).padStart
? String(rr.users).padStart(4, ' ')
: String(rr.users)
rlines.push(
mark +
' ' +
String(rr.name || '')
.slice(0, 18)
.padEnd(18, ' ') +
' ' +
users +
' ' +
String(rr.topic || '')
)
}
if (!rlines.length) rlines.push('(no rooms — /rooms linux to search)')
this.viewport.setContent(rlines.join('\n'))
var mid = Math.floor((this.viewport.height || 8) / 2)
this.viewport.setYOffset(Math.max(0, this.roomsCursor - mid))
return
}
var lines = []
for (var i = 0; i < buf.lines.length; i++) {
var rec = buf.lines[i]
@@ -128,86 +219,55 @@ function bareIrcCreateTuiApp(ctx, client) {
if (!hit) return
this.input.setValue(val.slice(0, val.length - prefix.length) + hit)
},
_runSlash: function (line) {
var parts = line.slice(1).split(/\s+/)
var cmd = (parts[0] || '').toLowerCase()
var rest = line.slice(1 + (parts[0] || '').length).trim()
_applySlash: function (res) {
var buf = this._current()
if (cmd === 'join' && parts[1]) this.client.join(parts[1], parts[2])
else if (cmd === 'part')
this.client.part(parts[1] || buf.name, rest.replace(/^\S+\s*/, ''))
else if (cmd === 'query' && parts[1]) {
var q = bareIrcEnsureBuffer(this.client.state, 'query', parts[1])
this.client.state.currentId = q.id
if (parts.slice(2).join(' ')) {
this.client.sendPrivmsg(parts[1], parts.slice(2).join(' '))
}
} else if ((cmd === 'msg' || cmd === 'privmsg') && parts[1]) {
this.client.sendPrivmsg(parts[1], parts.slice(2).join(' '))
} else if (cmd === 'notice' && parts[1]) {
this.client.sendRaw('NOTICE', [parts[1], parts.slice(2).join(' ')])
} else if (cmd === 'nick' && parts[1])
this.client.sendRaw('NICK', [parts[1]])
else if (cmd === 'whois' && parts[1])
this.client.sendRaw('WHOIS', [parts[1]])
else if (cmd === 'who' && parts[1]) this.client.sendRaw('WHO', [parts[1]])
else if (cmd === 'topic') {
if (rest) this.client.sendRaw('TOPIC', [buf.name, rest])
else this.client.sendRaw('TOPIC', [buf.name])
} else if (cmd === 'me' && rest) {
this.client.sendPrivmsg(buf.name, '\x01ACTION ' + rest + '\x01')
} else if (cmd === 'away') this.client.sendRaw('AWAY', rest ? [rest] : [])
else if (cmd === 'quit') {
this.client.quit(rest || 'Leaving')
return tui.quit
} else if (cmd === 'quote' && rest) {
var qparts = rest.split(/\s+/)
this.client.sendRaw(qparts[0], qparts.slice(1))
} else if (cmd === 'disconnect') this.client.quit('Disconnect')
else if (cmd === 'reconnect') {
try {
if (this.client.opts) {
this.client.attach(bareIrcDial(ctx, this.client.opts))
this.status = 'reconnecting\u2026'
}
} catch (err) {
this.status = 'reconnect failed'
}
} else if (cmd === 'win' || cmd === 'buffer') {
var n = parseInt(parts[1], 10)
var list = bareIrcListBuffers(this.client.state)
if (n >= 1 && n <= list.length) {
this.client.state.currentId = list[n - 1].id
}
} else if (cmd === 'p2p' && parts[1] === 'open' && parts[2]) {
var name = String(parts[2])
if (name.indexOf('[p2p]') < 0) name = name + ' [p2p]'
var pb = bareIrcEnsureBuffer(this.client.state, 'p2p', name)
this.client.state.currentId = pb.id
bareIrcPushLine(this.client.state, pb, {
atMs: Date.now(),
kind: 'info',
text: 'Shadow room — not Libera. Messages stay on the swarm sideband.'
})
if (typeof ctx.bareOsIrcShadowOpen === 'function') {
try {
ctx.bareOsIrcShadowOpen(parts[2])
} catch (e) {
/* ignore */
}
}
} else if (cmd === 'help') {
this.help = true
} else {
if (res.help) this.help = true
if (res.rooms) this._openRooms(res.roomsQuery || '')
if (res.note) {
bareIrcPushLine(this.client.state, buf, {
atMs: Date.now(),
kind: 'info',
text: 'unknown command /' + cmd
text: res.note
})
}
if (res.usage) {
bareIrcPushLine(this.client.state, buf, {
atMs: Date.now(),
kind: 'info',
text: res.usage
})
}
if (res.clear && buf) buf.lines = []
if (res.close) {
if (buf.kind === 'channel') this.client.part(buf.name, '')
var id = buf.id
if (id !== 'server') {
delete this.client.state.buffers[id]
this.client.state.currentId = 'server'
}
}
if (res.quit) return tui.quit
this._sync()
return null
},
_runSlash: function (line) {
var self = this
var res = bareIrcRunSlash(this.client, line, {
buf: this._current(),
ctx: ctx,
reconnect: function () {
try {
if (self.client.opts) {
self.client.attach(bareIrcDial(ctx, self.client.opts))
self.status = 'reconnecting\u2026'
}
} catch (err) {
self.status = 'reconnect failed'
}
}
})
return this._applySlash(res)
},
update: function (msg) {
if (msg && msg.type === 'resize') {
this.width = msg.width || this.width
@@ -228,6 +288,60 @@ function bareIrcCreateTuiApp(ctx, client) {
if (msg && msg.type === 'key') this.help = false
return [this, null]
}
if (this.roomsOpen) {
if (this.roomsFilterEdit) {
if (tui.key.matches(msg, 'escape')) {
this.roomsFilterEdit = false
this.input.reset()
return [this, null]
}
if (tui.key.matches(msg, 'enter')) {
var q = String(this.input.value || '').trim()
this.input.reset()
this.roomsFilterEdit = false
if (q) this.client.list(q)
this.status = q ? 'listing ' + q : 'rooms'
this._sync()
return [this, null]
}
var rpair = this.input.update(msg)
this.input = rpair[0]
return [this, rpair[1]]
}
if (tui.key.matches(msg, 'escape', 'q')) {
this.roomsOpen = false
this._sync()
return [this, null]
}
if (tui.key.matches(msg, '/', 'f')) {
this.roomsFilterEdit = true
this.input.reset()
return [this, null]
}
if (tui.key.matches(msg, 'enter')) {
this._joinRoomAtCursor()
this._sync()
return [this, null]
}
if (tui.key.matches(msg, 'j', 'down')) {
this.roomsCursor++
this._sync()
return [this, null]
}
if (tui.key.matches(msg, 'k', 'up')) {
this.roomsCursor = Math.max(0, this.roomsCursor - 1)
this._sync()
return [this, null]
}
if (tui.key.matches(msg, 'pageup', 'pagedown')) {
var step = this.viewport.height || 10
if (tui.key.matches(msg, 'pageup'))
this.roomsCursor = Math.max(0, this.roomsCursor - step)
else this.roomsCursor += step
this._sync()
return [this, null]
}
}
if (this.quitConfirm) {
if (tui.key.matches(msg, 'y', 'Y')) {
this.client.quit('Quit')
@@ -248,6 +362,11 @@ function bareIrcCreateTuiApp(ctx, client) {
this.help = true
return [this, null]
}
if (tui.key.matches(msg, 'ctrl+l')) {
this._openRooms('')
this._sync()
return [this, null]
}
if (tui.key.matches(msg, 'ctrl+n', 'alt+right')) {
this._nextBuffer(1)
return [this, null]
@@ -375,14 +494,24 @@ function bareIrcCreateTuiApp(ctx, client) {
}
body = joined.join('\n')
}
var prompt =
(buf.kind === 'p2p' ? '#p2p ' : '') +
'[' +
buf.name +
'] ' +
(this.input.view ? this.input.view() : this.input.value || '')
var footRaw =
'Enter send /join /query /quit Ctrl+n/p buffers Tab nick ? help'
var prompt
if (this.roomsOpen && this.roomsFilterEdit) {
prompt =
'Filter: ' +
(this.input.view ? this.input.view() : this.input.value || '')
} else {
prompt =
(buf.kind === 'p2p' ? '#p2p ' : '') +
'[' +
(this.roomsOpen ? 'rooms' : buf.name) +
'] ' +
(this.input.view ? this.input.view() : this.input.value || '')
}
var footRaw = this.roomsOpen
? this.roomsFilterEdit
? 'Filter + Enter search LIST Esc back'
: 'Enter join j/k move / filter Esc close featured or /rooms linux'
: 'Enter send /rooms /join /query Ctrl+L rooms Ctrl+n/p Tab nick ?'
var foot = st ? st().dim().width(cols).render(footRaw) : footRaw
var rule = st
? st()
@@ -412,11 +541,13 @@ function bareIrcCreateTuiApp(ctx, client) {
var body = this.quitConfirm
? 'Disconnect and quit?\n\nY yes any other key cancel'
: 'Bare OS irc\n\n' +
'/join #chan /part /query nick /msg nick text\n' +
'/nick /whois /me /away /quote /quit\n' +
'/rooms [pat] browse/list /join #chan /part /query nick\n' +
'/msg /notice /me /nick /whois /whowas /who /names /topic\n' +
'/kick /invite /op /deop /voice /mode /ban /away /back\n' +
'/list /motd /quote /ctcp /ping /ignore /clear /close /quit\n' +
'/p2p open #chan shadow room (not Libera)\n' +
'Ctrl+n / Ctrl+p next buffer Tab complete\n\n' +
'Default network: irc.libera.chat:6697 TLS\n' +
'Ctrl+L rooms Ctrl+n/p buffers Tab nick\n\n' +
'Default: irc.libera.chat:6697 TLS random nick if unset\n' +
'Press any key to close.'
var boxed = st
? st()