Updates
Release rolling / release (push) Successful in 10m12s

This commit is contained in:
Raven Scott
2026-08-13 08:54:43 -04:00
parent babf086e2e
commit ba45ab47e4
12 changed files with 779 additions and 62 deletions
@@ -3,9 +3,9 @@
"section": 1,
"title": "discord-bot",
"synopsis": [
"discord-bot [--env PATH] [--token TOKEN] [--guild ID] [--check]"
"discord-bot [--env PATH] [--token TOKEN] [--guild ID] [--check] [--debug] [--message-content] [--login-timeout MS]"
],
"description": "Foreground Discord ping-pong bot using ctx.bare.discordJS (vendored bare-discord-js / official discord.js). Replies pong to the /ping slash command and to the message ping. Token comes from --token, guest DISCORD_TOKEN, a VFS .env file (--env, DISCORD_ENV_FILE, ~/.discord.env), or a host DISCORD_ENV_FILE / DISCORD_TOKEN copied into the session by the booter. Enable MESSAGE CONTENT INTENT in the Discord Developer Portal for channel message ping/pong; /ping works without that privileged intent. Custom bots should use ctx.bare.discordJS.Client the same way (see examples/discord-ping-pong).",
"description": "Foreground Discord ping-pong bot using ctx.bare.discordJS (vendored bare-discord-js / official discord.js). Replies pong to the /ping slash command. Channel message ping/pong is opt-in: pass --message-content (or DISCORD_MESSAGE_CONTENT=1) and enable MESSAGE CONTENT INTENT in the Discord Developer Portal. Requesting that privileged intent without enabling it closes the gateway (4014) and used to hang after 'logging in'. Token comes from --token, guest DISCORD_TOKEN, a VFS .env file (--env, DISCORD_ENV_FILE, ~/.discord.env), or a host DISCORD_ENV_FILE / DISCORD_TOKEN copied into the session by the booter. Custom bots should use ctx.bare.discordJS.Client the same way (see examples/discord-ping-pong).",
"options": [
{
"flag": "--env PATH",
@@ -22,6 +22,18 @@
{
"flag": "--check",
"meaning": "Verify ctx.bare.discordJS and that a token can be resolved; do not login."
},
{
"flag": "--debug",
"meaning": "Print discord.js debug lines (also DISCORD_DEBUG=1)."
},
{
"flag": "--message-content",
"meaning": "Request the privileged Message Content Intent so channel 'ping' replies pong. Enable the same intent in the Developer Portal first."
},
{
"flag": "--login-timeout MS",
"meaning": "Fail login if the gateway is not ready after MS milliseconds (default 45000)."
}
],
"keywords": ["discord", "bot", "ping", "pong", "ctx.bare"],
@@ -29,6 +41,9 @@
"DISCORD_TOKEN — bot token in the guest session (also copied from the host when set).",
"DISCORD_ENV_FILE / BARE_OS_DISCORD_ENV_FILE — path to a .env file. On the host this may be a host filesystem path (booter reads it). In the guest it is a VFS path.",
"DISCORD_GUILD_ID — optional guild for slash-command registration.",
"DISCORD_MESSAGE_CONTENT / BARE_OS_DISCORD_MESSAGE_CONTENT — set 1 to request Message Content Intent (same as --message-content).",
"DISCORD_DEBUG / BARE_OS_DISCORD_DEBUG — set 1 to print discord.js debug lines.",
"DISCORD_LOGIN_TIMEOUT_MS — login deadline in milliseconds (default 45000).",
"BARE_OS_DISCORD — set 0 / false to skip loading ctx.bare.discordJS.",
"BARE_OS_BARE_MODULES — set 0 to omit ctx.bare entirely."
],
+148 -13
View File
@@ -67,15 +67,60 @@ function discordHasFlag(argv, names) {
return false
}
function discordEnvFlag(ctx, keys) {
if (!ctx.env) return false
for (let i = 0; i < keys.length; i++) {
const v = String(ctx.env[keys[i]] || '')
.trim()
.toLowerCase()
if (v === '1' || v === 'true' || v === 'yes') return true
}
return false
}
function discordWantsMessageContent(ctx, argv) {
return (
discordHasFlag(argv, ['--message-content', '--privileged-intents']) ||
discordEnvFlag(ctx, [
'DISCORD_MESSAGE_CONTENT',
'BARE_OS_DISCORD_MESSAGE_CONTENT'
])
)
}
function discordDebugOn(ctx, argv) {
return (
discordHasFlag(argv, ['--debug', '-v', '--verbose']) ||
discordEnvFlag(ctx, ['DISCORD_DEBUG', 'BARE_OS_DISCORD_DEBUG'])
)
}
function discordLoginTimeoutMs(ctx, argv) {
const raw =
discordArgValue(argv, ['--login-timeout']) ||
(ctx.env &&
(ctx.env.DISCORD_LOGIN_TIMEOUT_MS ||
ctx.env.BARE_OS_DISCORD_LOGIN_TIMEOUT_MS)) ||
'45000'
const n = Number(raw)
return Number.isFinite(n) && n > 0 ? n : 45000
}
function discordErrText(err) {
return (err && err.message) || String(err)
}
function discordUsage(argv0) {
return (
'usage: ' +
(argv0 || 'discord-bot') +
' [--env PATH] [--token TOKEN] [--guild ID] [--check]\n' +
' [--debug] [--message-content] [--login-timeout MS]\n' +
'Ping-pong bot via ctx.bare.discordJS.\n' +
'Token from --token, DISCORD_TOKEN, or a .env file (--env, DISCORD_ENV_FILE,\n' +
'host DISCORD_ENV_FILE, ~/.discord.env).\n' +
'Replies pong to /ping and to the message "ping".'
'Replies pong to /ping. Message "ping" needs --message-content and the\n' +
'Message Content Intent in the Developer Portal (privileged).'
)
}
@@ -202,13 +247,18 @@ async function run(ctx, argv) {
const Routes = dj.Routes
const SlashCommandBuilder = dj.SlashCommandBuilder
const wantsMessageContent = discordWantsMessageContent(ctx, argv)
const debugOn = discordDebugOn(ctx, argv)
const loginMs = discordLoginTimeoutMs(ctx, argv)
const intents = [GatewayIntentBits.Guilds]
if (GatewayIntentBits.GuildMessages)
intents.push(GatewayIntentBits.GuildMessages)
if (GatewayIntentBits.DirectMessages)
intents.push(GatewayIntentBits.DirectMessages)
if (GatewayIntentBits.MessageContent)
if (wantsMessageContent && GatewayIntentBits.MessageContent) {
intents.push(GatewayIntentBits.MessageContent)
}
const client = new Client({ intents: intents })
const pingCommand = new SlashCommandBuilder()
@@ -268,37 +318,122 @@ async function run(ctx, argv) {
await message.reply('pong')
} catch (err) {
ctx.console.error(
'discord-bot: message reply failed: ' +
((err && err.message) || String(err))
'discord-bot: message reply failed: ' + discordErrText(err)
)
}
})
}
client.on(Events.Error, function (error) {
ctx.console.error(
'discord-bot: client error: ' +
((error && error.message) || String(error))
)
ctx.console.error('discord-bot: client error: ' + discordErrText(error))
})
if (Events.ShardError) {
client.on(Events.ShardError, function (error) {
ctx.console.error('discord-bot: shard error: ' + discordErrText(error))
})
}
if (Events.Warn) {
client.on(Events.Warn, function (message) {
ctx.console.error('discord-bot: ' + String(message || ''))
})
}
if (debugOn && Events.Debug) {
client.on(Events.Debug, function (message) {
ctx.console.log('discord-bot: ' + String(message || ''))
})
}
ctx.console.log(
'discord-bot: logging in (token from ' + loaded.source + ')...'
)
try {
await client.login(loaded.token)
} catch (err) {
ctx.console.error(
'discord-bot: login failed: ' + ((err && err.message) || String(err))
if (!wantsMessageContent) {
ctx.console.log(
'discord-bot: /ping only (pass --message-content after enabling Message Content Intent for channel ping/pong)'
)
}
let loggingIn = true
let loginTimer = null
function discordGatewayFatal(event) {
const code = event && typeof event === 'object' ? event.code : event
if (code === 4014) {
const err = new Error(
'gateway closed 4014 (disallowed intents). Enable Message Content Intent in the Developer Portal, or omit --message-content (default).'
)
err.code = 4014
return err
}
if (code === 4013) {
const err = new Error('gateway closed 4013 (invalid intents)')
err.code = 4013
return err
}
if (code === 4004) {
const err = new Error(
'gateway closed 4004 (authentication failed). Check the bot token.'
)
err.code = 4004
return err
}
return null
}
try {
await Promise.race([
client.login(loaded.token),
new Promise(function (_, reject) {
loginTimer = setTimeout(function () {
reject(
new Error(
'login timed out after ' +
loginMs +
'ms (REST/gateway). Retry with --debug or DISCORD_DEBUG=1.'
)
)
}, loginMs)
if (loginTimer && typeof loginTimer.unref === 'function') {
loginTimer.unref()
}
if (Events.ShardError) {
client.on(Events.ShardError, function (error) {
if (!loggingIn) return
reject(
error instanceof Error ? error : new Error(discordErrText(error))
)
})
}
if (Events.ShardDisconnect) {
client.on(Events.ShardDisconnect, function (event) {
if (!loggingIn) return
const fatal = discordGatewayFatal(event)
if (fatal) reject(fatal)
})
}
})
])
} catch (err) {
ctx.console.error('discord-bot: login failed: ' + discordErrText(err))
if (err && err.code === 'TokenInvalid') {
ctx.console.error(
'Invalid token: Developer Portal → Bot → Token (not the OAuth2 client secret).'
)
}
if (err && (err.code === 4014 || /disallowed intents/i.test(String(err)))) {
ctx.console.error(
'Privileged intents: Developer Portal → Bot → Privileged Gateway Intents.'
)
}
ctx.exitCode = 1
loggingIn = false
if (loginTimer) clearTimeout(loginTimer)
await Promise.resolve(client.destroy()).catch(function () {})
return
}
loggingIn = false
if (loginTimer) clearTimeout(loginTimer)
await new Promise(function (resolve) {
function shutdown() {