This commit is contained in:
Raven Scott
2026-08-13 12:07:14 -04:00
parent 2d0563984e
commit 4d879da756
@@ -0,0 +1,703 @@
/**
* 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
}