Files
bare-operating-system/packages/bare-os-booter/lib/bare-os-discord-commands-guest.cjs
T
2026-08-13 12:07:14 -04:00

704 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Bare OS Discord slash-command catalog (guest-safe: no import/export).
* Used by /bin/discord-bot (prepended) and the bare-os-discord initd unit.
*/
var BARE_OS_DISCORD_REPLY_MAX = 1900
var BARE_OS_DISCORD_FS_MAX = 12 * 1024
var BARE_OS_DISCORD_WHITELIST_DENY =
'Access denied. Your Discord user id is not on DISCORD_ID_WHITELIST.'
var BARE_OS_DISCORD_RUN_ALLOW = {
uname: 1,
whoami: 1,
hostname: 1,
date: 1,
uptime: 1,
id: 1,
pwd: 1,
arch: 1,
nproc: 1,
help: 1,
motd: 1,
true: 1,
false: 1,
uname: 1,
df: 1,
ps: 1,
procstat: 1,
'uname -a': 1
}
function discordCmdClip(text, max) {
const s = String(text == null ? '' : text)
const n = max || BARE_OS_DISCORD_REPLY_MAX
if (s.length <= n) return s
return s.slice(0, n - 20) + '\n…(truncated)'
}
function discordCmdRedact(text) {
return String(text == null ? '' : text)
.replace(/[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{5,}\.[A-Za-z0-9_\-]{20,}/g, '[token]')
.replace(/(DISCORD_TOKEN|BOT_TOKEN|TOKEN|SECRET|PASSWORD|PASSWD|API_KEY)\s*[=:]\s*\S+/gi, '$1=[redacted]')
}
function discordCmdFence(text, lang) {
const body = discordCmdClip(discordCmdRedact(text))
return '```' + (lang || '') + '\n' + body.replace(/```/g, '`ˋ`') + '\n```'
}
async function discordCmdReadText(ctx, logicalPath) {
if (!logicalPath || typeof ctx.vfs?.readFile !== 'function') return ''
try {
const buf = await ctx.vfs.readFile(logicalPath)
if (!buf) return ''
if (typeof ctx.b4a?.toString === 'function') return ctx.b4a.toString(buf)
return String(buf)
} catch {
return ''
}
}
async function discordCmdReadJson(ctx, logicalPath) {
const t = await discordCmdReadText(ctx, logicalPath)
if (!t) return null
try {
return JSON.parse(t)
} catch {
return null
}
}
function discordCmdKv(obj) {
const keys = Object.keys(obj || {})
const lines = []
for (let i = 0; i < keys.length; i++) {
const k = keys[i]
if (obj[k] == null || obj[k] === '') continue
lines.push(k + ': ' + String(obj[k]))
}
return lines.join('\n') || '(empty)'
}
function discordCmdPathOk(raw) {
const p = String(raw || '').trim() || '.'
if (!p || p.indexOf('\0') >= 0) return null
if (p.indexOf('..') >= 0) return null
if (p === '~/.discord/.env' || p === '~/.discord.env') return null
const allow =
p === '.' ||
p === '~' ||
p.charAt(0) === '~' ||
p.indexOf('/proc') === 0 ||
p.indexOf('/etc') === 0 ||
p.indexOf('/var/log') === 0 ||
p.indexOf('/run') === 0 ||
p.indexOf('/home') === 0 ||
p.indexOf('/usr/share') === 0 ||
p.indexOf('/share') === 0 ||
p.indexOf('/tmp') === 0
return allow ? p : null
}
async function discordCmdCapture(ctx, fn) {
const lines = []
const cons = ctx.console || {}
const ol = cons.log
const oe = cons.error
cons.log = function (s) {
lines.push(String(s))
}
cons.error = function (s) {
lines.push(String(s))
}
try {
await fn()
} finally {
cons.log = ol
cons.error = oe
}
return lines.join('\n')
}
async function discordCmdOsRelease(ctx) {
const t = await discordCmdReadText(ctx, '/etc/os-release')
const out = {}
const lines = String(t).split(/\r?\n/)
for (let i = 0; i < lines.length; i++) {
const m = /^([A-Z0-9_]+)=(.*)$/.exec(lines[i].trim())
if (!m) continue
out[m[1]] = m[2].replace(/^"|"$/g, '')
}
return out
}
function discordCmdEnv(ctx) {
return (ctx && ctx.env) || (ctx && ctx.vfs && ctx.vfs.env) || {}
}
/** Comma-separated Discord snowflake ids → lookup map. Empty / unset → {}. */
function discordParseIdWhitelist(raw) {
const ids = Object.create(null)
const s = String(raw == null ? '' : raw).trim()
if (!s) return ids
const parts = s.split(',')
for (let i = 0; i < parts.length; i++) {
let id = String(parts[i] || '').trim()
if (!id) continue
if (id.charAt(0) === '<' && id.charAt(id.length - 1) === '>') {
id = id.slice(1, -1)
if (id.charAt(0) === '@') id = id.slice(1)
if (id.charAt(0) === '!') id = id.slice(1)
id = id.trim()
}
if (id) ids[id] = 1
}
return ids
}
function discordWhitelistRaw(ctx) {
const e = discordCmdEnv(ctx)
return e.DISCORD_ID_WHITELIST || e.BARE_OS_DISCORD_ID_WHITELIST || ''
}
/**
* Unset / empty whitelist: allow everyone.
* Non-empty: only listed Discord user ids may use the bot.
*/
function discordUserAllowed(ctx, userId) {
const ids = discordParseIdWhitelist(discordWhitelistRaw(ctx))
let n = 0
for (const k in ids) {
if (Object.prototype.hasOwnProperty.call(ids, k)) n++
}
if (n === 0) return true
const id = String(userId == null ? '' : userId).trim()
return Boolean(id && ids[id])
}
function discordInteractionUserId(interaction) {
if (!interaction) return ''
if (interaction.user && interaction.user.id) return String(interaction.user.id)
const member = interaction.member
if (member && member.user && member.user.id) return String(member.user.id)
if (member && member.id) return String(member.id)
return ''
}
async function discordReplyWhitelistDenied(ctx, interaction) {
const payload = {
content: BARE_OS_DISCORD_WHITELIST_DENY,
ephemeral: true
}
try {
if (interaction.deferred && typeof interaction.editReply === 'function') {
await interaction.editReply(payload)
} else if (interaction.replied && typeof interaction.followUp === 'function') {
await interaction.followUp(payload)
} else if (typeof interaction.reply === 'function') {
await interaction.reply(payload)
}
} catch (err) {
if (ctx && ctx.console && typeof ctx.console.error === 'function') {
ctx.console.error(
'discord-bot: deny reply failed: ' + ((err && err.message) || err)
)
}
}
}
async function discordCmdStatus(ctx) {
const e = discordCmdEnv(ctx)
const os = await discordCmdOsRelease(ctx)
return discordCmdKv({
os: os.PRETTY_NAME || os.NAME || 'Bare OS',
version: os.VERSION_ID || e.BARE_OS_KERNEL_VERSION || e.BARE_OS_RELEASE || '',
hostname: e.HOSTNAME || e.NAME || 'bare-os',
user: e.USER || e.LOGNAME || e.USERNAME || '',
home: e.HOME || '',
shell: e.SHELL || '/bin/sh',
arch: e.BARE_OS_ARCH || e.MACHINE || '',
booter: e.BARE_OS_BOOTER_PACKAGE_VERSION || '',
now: new Date().toISOString()
})
}
async function discordCmdUname(ctx) {
const e = discordCmdEnv(ctx)
const os = await discordCmdOsRelease(ctx)
return [
os.NAME || 'BareOS',
e.HOSTNAME || 'bare-os',
os.VERSION_ID || e.BARE_OS_KERNEL_VERSION || '0.1',
e.BARE_OS_ARCH || e.MACHINE || 'unknown',
os.PRETTY_NAME || e.BARE_OS_BUILD || 'bare-userland'
].join(' ')
}
async function discordCmdHandleBare(ctx, sub) {
const e = discordCmdEnv(ctx)
if (sub === 'ping') return { text: 'pong · Bare OS is online' }
if (sub === 'about') {
return {
text: discordCmdKv({
name: 'Bare OS Discord bot',
role: 'slash control surface for this guest session',
commands: '/bare /sys /svc /fs /net /man /say /run /journal /ping',
service: 'bare-os-discord (systemctl; requires ~/.discord/.env)',
stop: 'Ctrl+C in the foreground, or systemctl stop bare-os-discord'
})
}
}
if (sub === 'help') {
return {
text:
'**Bare OS bot**\n' +
'`/bare` ping about help status whoami hostname date uptime motd uname\n' +
'`/sys` df mem ps env doctor features rlimits\n' +
'`/svc` list status start stop restart logs\n' +
'`/fs` ls cat stat head\n' +
'`/net` peers swarm summary\n' +
'`/man <page>` `/say <text>` `/run <cmd>` `/journal [unit]` `/ping`'
}
}
if (sub === 'status') return { text: discordCmdFence(await discordCmdStatus(ctx)) }
if (sub === 'uname') return { text: discordCmdFence(await discordCmdUname(ctx)) }
if (sub === 'whoami') return { text: String(e.USER || e.LOGNAME || e.USERNAME || 'guest') }
if (sub === 'hostname') return { text: String(e.HOSTNAME || e.NAME || 'bare-os') }
if (sub === 'date') return { text: new Date().toISOString() + ' · ' + String(Date()) }
if (sub === 'uptime') {
const t = await discordCmdReadText(ctx, '/proc/uptime')
return { text: t ? t.trim() : 'uptime unavailable' }
}
if (sub === 'motd') {
const t = await discordCmdReadText(ctx, '/etc/motd')
return { text: t ? discordCmdFence(t) : '(no /etc/motd)' }
}
return { text: 'unknown /bare subcommand', ephemeral: true }
}
async function discordCmdHandleSys(ctx, sub) {
if (sub === 'df') {
const t =
(await discordCmdReadText(ctx, '/proc/bare_os/host_os.json')) ||
(await discordCmdReadText(ctx, '/proc/bare_os_resources'))
return { text: t ? discordCmdFence(discordCmdClip(t, 1600), 'json') : 'disk info unavailable' }
}
if (sub === 'mem') {
const t =
(await discordCmdReadText(ctx, '/proc/meminfo')) ||
(await discordCmdReadText(ctx, '/proc/bare_os/host_os.json'))
return { text: t ? discordCmdFence(discordCmdClip(t, 1600)) : 'meminfo unavailable' }
}
if (sub === 'ps') {
const table = await discordCmdReadJson(ctx, '/proc/bare_os/process_table.json')
const rows = table && Array.isArray(table.processes) ? table.processes : []
const lines = ['pid\tname\tstate']
for (let i = 0; i < Math.min(rows.length, 30); i++) {
const r = rows[i] || {}
lines.push(
String(r.pid || r.id || '') +
'\t' +
String(r.name || r.comm || r.cmd || '') +
'\t' +
String(r.state || r.status || '')
)
}
if (rows.length > 30) lines.push('…' + (rows.length - 30) + ' more')
return { text: discordCmdFence(lines.join('\n')) }
}
if (sub === 'env') {
const e = discordCmdEnv(ctx)
const keys = Object.keys(e).sort()
const lines = []
const skip = /TOKEN|SECRET|PASSWORD|PASSWD|KEY|PRIVATE|CREDENTIAL/i
for (let i = 0; i < keys.length && lines.length < 40; i++) {
if (skip.test(keys[i])) {
lines.push(keys[i] + '=[redacted]')
continue
}
lines.push(keys[i] + '=' + String(e[keys[i]]).slice(0, 80))
}
return { text: discordCmdFence(lines.join('\n')) }
}
if (sub === 'doctor') {
const t =
(await discordCmdReadText(ctx, '/proc/bare_os/security_posture.json')) ||
(await discordCmdReadText(ctx, '/proc/bare_os/debug.json'))
return { text: t ? discordCmdFence(discordCmdClip(t, 1600), 'json') : 'doctor snapshot unavailable' }
}
if (sub === 'features') {
const t = await discordCmdReadText(ctx, '/proc/bare_os_features')
return { text: t ? discordCmdFence(discordCmdClip(t, 1600), 'json') : 'features unavailable' }
}
if (sub === 'rlimits') {
const t = await discordCmdReadText(ctx, '/proc/bare_os/rlimits.json')
return { text: t ? discordCmdFence(discordCmdClip(t, 1600), 'json') : 'rlimits unavailable' }
}
return { text: 'unknown /sys subcommand', ephemeral: true }
}
async function discordCmdHandleSvc(ctx, sub, unit) {
const name = String(unit || '').replace(/\.service$/, '')
if (typeof ctx.bareOsRunSystemctlCli !== 'function') {
return { text: 'systemctl is not available on this ctx', ephemeral: true }
}
const argv =
sub === 'list'
? ['systemctl', 'list']
: name
? ['systemctl', sub, name]
: null
if (!argv) return { text: 'unit name required', ephemeral: true }
const out = await discordCmdCapture(ctx, function () {
return ctx.bareOsRunSystemctlCli(argv)
})
return { text: out ? discordCmdFence(out) : '(no output)' }
}
async function discordCmdHandleFs(ctx, sub, rawPath, nlines) {
const p = discordCmdPathOk(rawPath || '.')
if (!p) return { text: 'path not allowed (no `..`; stay under /proc /etc /var/log /run /home ~ /share)', ephemeral: true }
if (sub === 'ls') {
if (typeof ctx.vfs?.readdir !== 'function') return { text: 'readdir unavailable', ephemeral: true }
try {
const names = await ctx.vfs.readdir(p)
const list = Array.isArray(names) ? names : []
return { text: discordCmdFence(list.slice(0, 80).join('\n') || '(empty)') }
} catch (err) {
return { text: 'ls failed: ' + ((err && err.message) || err), ephemeral: true }
}
}
if (sub === 'stat') {
const stfn = ctx.vfs && (ctx.vfs.lstat || ctx.vfs.stat)
if (typeof stfn !== 'function') return { text: 'stat unavailable', ephemeral: true }
try {
const st = await stfn.call(ctx.vfs, p)
return { text: discordCmdFence(JSON.stringify(st, null, 2), 'json') }
} catch (err) {
return { text: 'stat failed: ' + ((err && err.message) || err), ephemeral: true }
}
}
const text = await discordCmdReadText(ctx, p)
if (!text) return { text: '(empty or unreadable)' }
if (sub === 'head') {
const n = Math.max(1, Math.min(40, Number(nlines) || 12))
return { text: discordCmdFence(text.split(/\r?\n/).slice(0, n).join('\n')) }
}
return { text: discordCmdFence(text.slice(0, BARE_OS_DISCORD_FS_MAX)) }
}
async function discordCmdHandleNet(ctx, sub) {
if (sub === 'peers' || sub === 'swarm') {
const t =
(await discordCmdReadText(ctx, '/proc/bare_os/swarm')) ||
(await discordCmdReadText(ctx, '/proc/bare_os_swarm'))
return { text: t ? discordCmdFence(discordCmdClip(t, 1600), 'json') : 'swarm snapshot unavailable' }
}
const t =
(await discordCmdReadText(ctx, '/proc/bare_os/net_summary.json')) ||
(await discordCmdReadText(ctx, '/proc/net/dev'))
return { text: t ? discordCmdFence(discordCmdClip(t, 1600)) : 'net summary unavailable' }
}
async function discordCmdHandleMan(ctx, page) {
const name = String(page || '').replace(/[^a-zA-Z0-9._+-]/g, '')
if (!name) return { text: 'man page name required', ephemeral: true }
if (typeof ctx.execLine === 'function') {
const out = await discordCmdCapture(ctx, function () {
return ctx.execLine('man ' + name)
})
if (out) return { text: discordCmdFence(out) }
}
const t = await discordCmdReadText(ctx, '/share/man/man.json')
if (!t) return { text: 'man database unavailable' }
try {
const db = JSON.parse(t)
const pages = (db && db.pages) || []
for (let i = 0; i < pages.length; i++) {
if (pages[i] && pages[i].name === name) {
const p = pages[i]
return {
text: discordCmdFence(
(p.title || name) +
'\n' +
(p.synopsis && p.synopsis[0] ? p.synopsis[0] : '') +
'\n\n' +
String(p.description || '').slice(0, 1400)
)
}
}
}
} catch {
/* fall through */
}
return { text: 'no man page for ' + name, ephemeral: true }
}
function discordCmdSayBox(text) {
const s = String(text || '').slice(0, 200)
const lines = s.split(/\r?\n/).slice(0, 6)
let w = 8
for (let i = 0; i < lines.length; i++) {
if (lines[i].length > w) w = lines[i].length
}
if (w > 48) w = 48
const bar = '+' + Array(w + 3).join('-') + '+'
const body = lines.map(function (ln) {
const t = ln.slice(0, w)
return '| ' + t + Array(w - t.length + 1).join(' ') + ' |'
})
return [bar, body.join('\n'), bar, ' \\', ' cow-ish · bare-os'].join('\n')
}
async function discordCmdHandleRun(ctx, raw) {
const cmd = String(raw || '').trim()
if (!cmd) return { text: 'command required', ephemeral: true }
if (/[;&|`$<>(){}]/.test(cmd)) {
return { text: 'metacharacters are not allowed', ephemeral: true }
}
const key = cmd.replace(/\s+/g, ' ')
const bin = key.split(' ')[0]
if (!BARE_OS_DISCORD_RUN_ALLOW[key] && !BARE_OS_DISCORD_RUN_ALLOW[bin]) {
return {
text:
'not in allowlist. Try: uname, whoami, hostname, date, uptime, id, pwd, arch, nproc, help, motd, df, ps, procstat, uname -a',
ephemeral: true
}
}
if (typeof ctx.execLine !== 'function') {
return { text: 'execLine unavailable', ephemeral: true }
}
const out = await discordCmdCapture(ctx, function () {
return ctx.execLine(key)
})
return { text: out ? discordCmdFence(out) : '(no output, exit ' + String(ctx.exitCode || 0) + ')' }
}
async function discordCmdHandleJournal(ctx, unit) {
if (typeof ctx.bareOsRunSystemctlCli === 'function' && unit) {
const out = await discordCmdCapture(ctx, function () {
return ctx.bareOsRunSystemctlCli([
'journalctl',
'-u',
String(unit),
'--lines',
'30'
])
})
return { text: out ? discordCmdFence(out) : '(empty journal)' }
}
const t =
(await discordCmdReadText(ctx, '/var/log/bare-os/discord.log')) ||
(await discordCmdReadText(ctx, '/var/log/bare-os/kernel-console.log')) ||
(await discordCmdReadText(ctx, '/var/log/messages'))
return { text: t ? discordCmdFence(t.split(/\r?\n/).slice(-30).join('\n')) : '(no journal)' }
}
function discordCmdOpt(s, name, desc, required) {
if (typeof s.addStringOption !== 'function') return s
return s.addStringOption(function (o) {
o.setName(name).setDescription(desc)
if (required && typeof o.setRequired === 'function') o.setRequired(true)
return o
})
}
function discordCmdAddSubs(builder, items) {
if (!builder || typeof builder.addSubcommand !== 'function') return false
for (let i = 0; i < items.length; i++) {
const it = items[i]
builder.addSubcommand(function (s) {
s.setName(it[0]).setDescription(it[1])
if (it[2]) it[2](s)
return s
})
}
return true
}
function discordBuildSlashCommands(dj) {
const B = dj && dj.SlashCommandBuilder
if (typeof B !== 'function') return []
const bare = new B().setName('bare').setDescription('Bare OS session')
const sys = new B().setName('sys').setDescription('Bare OS system snapshots')
const svc = new B().setName('svc').setDescription('systemctl units')
const fsCmd = new B().setName('fs').setDescription('Read-only VFS')
const net = new B().setName('net').setDescription('Swarm / network')
const man = new B().setName('man').setDescription('Look up a man page')
const say = new B().setName('say').setDescription('Speak as Bare OS')
const run = new B().setName('run').setDescription('Run an allowlisted utility')
const journal = new B()
.setName('journal')
.setDescription('Tail a unit or system log')
const ping = new B().setName('ping').setDescription('Reply pong')
const ok =
discordCmdAddSubs(bare, [
['ping', 'Latency / liveness'],
['about', 'What this bot is'],
['help', 'Command map'],
['status', 'Session snapshot'],
['whoami', 'Guest user'],
['hostname', 'Guest hostname'],
['date', 'Clock'],
['uptime', '/proc/uptime'],
['motd', '/etc/motd'],
['uname', 'uname -a style']
]) &&
discordCmdAddSubs(sys, [
['df', 'Disk / host resources'],
['mem', 'Memory info'],
['ps', 'Process table'],
['env', 'Redacted environment'],
['doctor', 'Security / debug posture'],
['features', '/proc/bare_os_features'],
['rlimits', 'Resource limits']
]) &&
discordCmdAddSubs(svc, [
['list', 'systemctl list'],
['status', 'Unit status', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', true)
}],
['start', 'Start a unit', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', true)
}],
['stop', 'Stop a unit', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', true)
}],
['restart', 'Restart a unit', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', true)
}],
['logs', 'Unit logs', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', true)
}]
]) &&
discordCmdAddSubs(fsCmd, [
['ls', 'List a directory', function (s) {
discordCmdOpt(s, 'path', 'VFS path', false)
}],
['cat', 'Read a file', function (s) {
discordCmdOpt(s, 'path', 'VFS path', true)
}],
['stat', 'Stat a path', function (s) {
discordCmdOpt(s, 'path', 'VFS path', true)
}],
['head', 'First lines of a file', function (s) {
discordCmdOpt(s, 'path', 'VFS path', true)
}]
]) &&
discordCmdAddSubs(net, [
['peers', 'Swarm peers'],
['swarm', 'Swarm snapshot'],
['summary', 'net_summary']
])
if (!ok) {
return [ping.toJSON()]
}
discordCmdOpt(man, 'page', 'Command name (e.g. uname)', true)
discordCmdOpt(say, 'text', 'Text to box', true)
discordCmdOpt(run, 'cmd', 'Allowlisted utility', true)
discordCmdOpt(journal, 'unit', 'Optional unit name', false)
return [
bare,
sys,
svc,
fsCmd,
net,
man,
say,
run,
journal,
ping
].map(function (c) {
return c.toJSON()
})
}
async function discordDispatchInteraction(ctx, interaction) {
if (!interaction || typeof interaction.isChatInputCommand !== 'function') {
return false
}
if (!interaction.isChatInputCommand()) return false
if (!discordUserAllowed(ctx, discordInteractionUserId(interaction))) {
if (ctx && ctx.console && typeof ctx.console.log === 'function') {
ctx.console.log(
'discord-bot: denied user ' +
(discordInteractionUserId(interaction) || '?')
)
}
await discordReplyWhitelistDenied(ctx, interaction)
return true
}
const name = String(interaction.commandName || '')
let sub = ''
let opt = function () {
return ''
}
if (interaction.options) {
if (typeof interaction.options.getSubcommand === 'function') {
try {
sub = String(interaction.options.getSubcommand(false) || '')
} catch {
sub = ''
}
}
opt = function (key) {
if (typeof interaction.options.getString === 'function') {
const v = interaction.options.getString(key)
return v == null ? '' : String(v)
}
return ''
}
}
let result
try {
if (name === 'ping' || (name === 'bare' && (sub === 'ping' || !sub))) {
result = { text: 'pong · Bare OS is online' }
} else if (name === 'bare') result = await discordCmdHandleBare(ctx, sub)
else if (name === 'sys') result = await discordCmdHandleSys(ctx, sub)
else if (name === 'svc') result = await discordCmdHandleSvc(ctx, sub, opt('unit'))
else if (name === 'fs') {
result = await discordCmdHandleFs(ctx, sub, opt('path'), opt('lines'))
} else if (name === 'net') result = await discordCmdHandleNet(ctx, sub)
else if (name === 'man') result = await discordCmdHandleMan(ctx, opt('page'))
else if (name === 'say') result = { text: discordCmdFence(discordCmdSayBox(opt('text'))) }
else if (name === 'run') result = await discordCmdHandleRun(ctx, opt('cmd'))
else if (name === 'journal') result = await discordCmdHandleJournal(ctx, opt('unit'))
else result = { text: 'unknown command: /' + name, ephemeral: true }
} catch (err) {
result = {
text: 'error: ' + ((err && err.message) || String(err)),
ephemeral: true
}
}
const payload = {
content: discordCmdClip(result && result.text ? result.text : '(no output)'),
ephemeral: Boolean(result && result.ephemeral)
}
try {
if (interaction.deferred && typeof interaction.editReply === 'function') {
await interaction.editReply(payload)
} else if (interaction.replied && typeof interaction.followUp === 'function') {
await interaction.followUp(payload)
} else {
await interaction.reply(payload)
}
} catch (err) {
if (ctx && ctx.console && typeof ctx.console.error === 'function') {
ctx.console.error(
'discord-bot: reply failed: ' + ((err && err.message) || err)
)
}
}
return true
}
var bareOsDiscordCommands = {
buildSlashCommands: discordBuildSlashCommands,
dispatchInteraction: discordDispatchInteraction,
parseIdWhitelist: discordParseIdWhitelist,
userAllowed: discordUserAllowed
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = bareOsDiscordCommands
}