251 lines
7.4 KiB
JavaScript
251 lines
7.4 KiB
JavaScript
/**
|
|
* Load vendored `bare-discord-js` onto `ctx.bare.discordJS`.
|
|
*
|
|
* discord.js is CJS and remaps Node builtins through `bare-node-runtime` on Bare.
|
|
* Do not ESM-import the package from the booter (its ESM entry uses `node:module`).
|
|
*/
|
|
|
|
import { readFileSync } from '#host-fs'
|
|
import { tryRequireFromBooter } from './bare-os-ctx-bare.js'
|
|
import { bareOsHostBooterWarn } from './bare-os-host-booter-log.js'
|
|
import { takePackedDiscordJs } from './bare-os-ctx-discord-registry.js'
|
|
|
|
/** @type {Record<string, unknown> | undefined} */
|
|
let discordJsCache
|
|
let discordJsTried = false
|
|
/** @type {string} */
|
|
let discordJsLastError = ''
|
|
|
|
/** Last load failure (empty when unused or successful). */
|
|
export function getBareDiscordJsLoadError() {
|
|
return discordJsLastError
|
|
}
|
|
|
|
function noteDiscordLoadError(err) {
|
|
const msg =
|
|
err && typeof err === 'object' && 'message' in err
|
|
? String(/** @type {{ message?: unknown }} */ (err).message || err)
|
|
: String(err || 'unknown error')
|
|
discordJsLastError = msg.slice(0, 800)
|
|
return discordJsLastError
|
|
}
|
|
|
|
function bareRuntime() {
|
|
if (typeof globalThis.Bare !== 'undefined') return true
|
|
const v = globalThis.process?.versions
|
|
return Boolean(v && typeof v.bare === 'string')
|
|
}
|
|
|
|
/**
|
|
* Discord rejects tokens with trailing newlines, BOM, or copy-paste invisibles.
|
|
* @param {unknown} raw
|
|
* @returns {string}
|
|
*/
|
|
export function normalizeDiscordToken(raw) {
|
|
if (typeof raw !== 'string') return ''
|
|
let t = raw.trim()
|
|
if (t.charCodeAt(0) === 0xfeff) t = t.slice(1)
|
|
t = t.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
|
return t.trim()
|
|
}
|
|
|
|
/**
|
|
* @param {string} line
|
|
* @returns {{ key: string, value: string } | null}
|
|
*/
|
|
export function parseDotEnvLine(line) {
|
|
let s = String(line)
|
|
.replace(/^\uFEFF/, '')
|
|
.trim()
|
|
if (!s || s.startsWith('#')) return null
|
|
if (s.startsWith('export ')) s = s.slice(7).trim()
|
|
const eq = s.indexOf('=')
|
|
if (eq === -1) return null
|
|
const key = s.slice(0, eq).trim()
|
|
if (!key) return null
|
|
let val = s.slice(eq + 1).trim()
|
|
if (
|
|
(val.startsWith('"') && val.endsWith('"')) ||
|
|
(val.startsWith("'") && val.endsWith("'"))
|
|
) {
|
|
val = val.slice(1, -1)
|
|
}
|
|
return { key, value: val }
|
|
}
|
|
|
|
/**
|
|
* @param {string} text
|
|
* @returns {Record<string, string>}
|
|
*/
|
|
export function parseDotEnvText(text) {
|
|
/** @type {Record<string, string>} */
|
|
const out = {}
|
|
if (typeof text !== 'string' || !text) return out
|
|
let body = text
|
|
if (body.charCodeAt(0) === 0xfeff) body = body.slice(1)
|
|
for (const line of body.split(/\r?\n/)) {
|
|
const parsed = parseDotEnvLine(line)
|
|
if (!parsed) continue
|
|
out[parsed.key] = parsed.value
|
|
}
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* Copy host Discord token / env-file settings into the guest session.
|
|
* When `DISCORD_ENV_FILE` (or `BARE_OS_DISCORD_ENV_FILE`) is a **host** path,
|
|
* read it here so the guest does not need host filesystem access.
|
|
*
|
|
* @param {Record<string, string | undefined> | NodeJS.ProcessEnv | null | undefined} hostEnv
|
|
* @param {Record<string, string>} shellEnv
|
|
*/
|
|
export function applyDiscordHostEnvToShellEnv(hostEnv, shellEnv) {
|
|
if (!hostEnv || !shellEnv) return
|
|
for (const k of [
|
|
'BARE_OS_DISCORD',
|
|
'DISCORD_TOKEN',
|
|
'DISCORD_GUILD_ID',
|
|
'DISCORD_ID_WHITELIST',
|
|
'DISCORD_USER_INSTALL',
|
|
'DISCORD_ENV_FILE',
|
|
'BARE_OS_DISCORD_ENV_FILE'
|
|
]) {
|
|
const v = hostEnv[k]
|
|
if (v != null && String(v).trim()) shellEnv[k] = String(v)
|
|
}
|
|
|
|
const envFile = String(
|
|
hostEnv.DISCORD_ENV_FILE || hostEnv.BARE_OS_DISCORD_ENV_FILE || ''
|
|
).trim()
|
|
if (!envFile) return
|
|
try {
|
|
const parsed = parseDotEnvText(readFileSync(envFile, 'utf8'))
|
|
const token = normalizeDiscordToken(parsed.DISCORD_TOKEN || '')
|
|
if (token && !String(shellEnv.DISCORD_TOKEN || '').trim()) {
|
|
shellEnv.DISCORD_TOKEN = token
|
|
}
|
|
const guild = String(parsed.DISCORD_GUILD_ID || '').trim()
|
|
if (guild && !String(shellEnv.DISCORD_GUILD_ID || '').trim()) {
|
|
shellEnv.DISCORD_GUILD_ID = guild
|
|
}
|
|
const whitelist = String(parsed.DISCORD_ID_WHITELIST || '').trim()
|
|
if (whitelist && !String(shellEnv.DISCORD_ID_WHITELIST || '').trim()) {
|
|
shellEnv.DISCORD_ID_WHITELIST = whitelist
|
|
}
|
|
const userInstall = String(
|
|
parsed.DISCORD_USER_INSTALL || parsed.BARE_OS_DISCORD_USER_INSTALL || ''
|
|
).trim()
|
|
if (userInstall && !String(shellEnv.DISCORD_USER_INSTALL || '').trim()) {
|
|
shellEnv.DISCORD_USER_INSTALL = userInstall
|
|
}
|
|
} catch {
|
|
// Guest may still read the same path from the VFS (`--env`).
|
|
}
|
|
}
|
|
|
|
function unwrapDiscordModule(mod) {
|
|
if (!mod || typeof mod !== 'object') return undefined
|
|
const direct = /** @type {Record<string, unknown>} */ (mod)
|
|
if (typeof direct.Client === 'function') return direct
|
|
const def = direct.default
|
|
if (def && typeof def === 'object' && typeof def.Client === 'function') {
|
|
return /** @type {Record<string, unknown>} */ (def)
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
function applyBareProcessEmitWarning() {
|
|
const proc = globalThis.process
|
|
if (!proc || typeof proc.emitWarning === 'function') return
|
|
proc.emitWarning = function emitWarning(warning, type, code) {
|
|
const msg = warning instanceof Error ? warning.message : String(warning)
|
|
const name = typeof type === 'string' ? type : 'Warning'
|
|
const id = typeof code === 'string' ? code : ''
|
|
const line = id ? name + ' [' + id + ']: ' + msg : name + ': ' + msg
|
|
try {
|
|
if (typeof proc.emit === 'function') proc.emit('warning', warning)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
try {
|
|
console.error(line)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
}
|
|
|
|
function applyBareTlsCompat() {
|
|
const proc = globalThis.process
|
|
if (!proc || !proc.env) return
|
|
if (!proc.env.NODE_TLS_REJECT_UNAUTHORIZED) {
|
|
proc.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
}
|
|
try {
|
|
const req = globalThis.require
|
|
if (typeof req !== 'function') return
|
|
const bareHttps = req('bare-https')
|
|
if (!bareHttps || typeof bareHttps.Agent !== 'function') return
|
|
const insecureAgent = new bareHttps.Agent({ rejectUnauthorized: false })
|
|
bareHttps.globalAgent = insecureAgent
|
|
if (bareHttps.Agent) bareHttps.Agent.global = insecureAgent
|
|
} catch {
|
|
/* optional */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown> | null} [_ctx]
|
|
* @returns {Promise<Record<string, unknown> | undefined>}
|
|
*/
|
|
export async function loadBareDiscordJs(_ctx) {
|
|
void _ctx
|
|
if (discordJsTried) return discordJsCache
|
|
discordJsTried = true
|
|
discordJsLastError = ''
|
|
if (bareRuntime()) applyBareTlsCompat()
|
|
applyBareProcessEmitWarning()
|
|
|
|
/** @type {unknown} */
|
|
let lastErr = null
|
|
|
|
try {
|
|
const packed = unwrapDiscordModule(takePackedDiscordJs())
|
|
if (packed) {
|
|
discordJsCache = packed
|
|
return discordJsCache
|
|
}
|
|
} catch (err) {
|
|
lastErr = err
|
|
}
|
|
|
|
try {
|
|
const mod = await tryRequireFromBooter('bare-discord-js')
|
|
const discord = unwrapDiscordModule(mod)
|
|
if (discord) {
|
|
discordJsCache = discord
|
|
return discordJsCache
|
|
}
|
|
if (!lastErr) {
|
|
lastErr = new Error(
|
|
String(import.meta.url || '').startsWith('bare:')
|
|
? 'standalone pack did not register discord.js (bare-os-ctx-discord-packed.js)'
|
|
: 'require("bare-discord-js") returned no Client'
|
|
)
|
|
}
|
|
} catch (err) {
|
|
lastErr = err
|
|
}
|
|
|
|
const detail = noteDiscordLoadError(
|
|
lastErr || 'vendored bare-discord-js unresolved'
|
|
)
|
|
bareOsHostBooterWarn(
|
|
'ctx_bare_discord_js_load_failed',
|
|
'ctx.bare.discordJS: failed to load vendored bare-discord-js',
|
|
detail
|
|
)
|
|
discordJsCache = undefined
|
|
return undefined
|
|
}
|