From 8496ff0a974312281da8ab98594c887b21493b05 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Sun, 24 May 2026 06:45:01 -0400 Subject: [PATCH] feat(platform): Phase 490 bot span metadata (v0.8.453) Add bot.token span, bot.verify span, and messageSent on bot.message. Co-authored-by: Cursor --- README.md | 2 ++ index.js | 105 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 4506606..340a299 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ Expose a single `EventEmitter` API the UI drives over IPC (`apps/pearcord/index. **v0.8.430 (Phase 467):** `voice.leave` span `end` includes `leavingIsStage`; `screen.start` span `end` includes `label`. Bundle: `npm run test:phase467-voice-stage`. See [VOICE.md](../../docs/VOICE.md), [STAGE_CHANNELS.md](../../docs/STAGE_CHANNELS.md). +**v0.8.453 (Phase 490):** `bot.token`/`bot.verify`/`bot.install`/`bot.message` span metadata extend. UI bot compositor + hints. Bundle: `npm run test:phase490-bots`. See [BOTS.md](../../docs/BOTS.md). + **v0.8.452 (Phase 489):** `slash.register` `customCount`; `slash.delete` `deleted`; `slash.invoke` `replyLen`/`commandFound`. UI slash compositor + hints. Bundle: `npm run test:phase489-slash`. See [SLASH_COMMANDS.md](../../docs/SLASH_COMMANDS.md). **v0.8.451 (Phase 488):** `webhook.create` `channelType`/`hasAvatar`; `webhook.execute` `messageSent`. UI webhook compositor split + hints. Bundle: `npm run test:phase488-webhooks`. See [WEBHOOKS.md](../../docs/WEBHOOKS.md). diff --git a/index.js b/index.js index 6b5c636..65066c5 100644 --- a/index.js +++ b/index.js @@ -9968,37 +9968,55 @@ class PearcordPlatform extends EventEmitter { botId = null, ttlMs = 7 * 86400000 } = {}) { - if (!this.guild?.guild) throw new Error('no guild') - const user = this.identity.user - if (!user) throw new Error('register first') - await this._assertStepUp() - const roles = await this._memberRoles() - if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) { - throw new Error('no permission to create bot install tokens') + const guildId = this.guild?.guild?.id || null + const span = this.log.time('bot.token', { guildId }) + try { + if (!this.guild?.guild) throw new Error('no guild') + const user = this.identity.user + if (!user) throw new Error('register first') + await this._assertStepUp() + const roles = await this._memberRoles() + if (!roleHasPermission(roles, PERMISSION.MANAGE_GUILD)) { + throw new Error('no permission to create bot install tokens') + } + if (!this.botLimits) { + this.botLimits = new BotRateLimits({ storagePath: this.storagePath }) + await this.botLimits.ready() + } + await this.botLimits.assertBotRegister(this.guild.guild.id) + const { token, payload } = createInstallToken({ + keyPair: this.identity.keyPair, + guildId: this.guild.guild.id, + guildName: this.guild.guild.name, + guildTopic: this.guild.guild.topic, + botId: botId || id(), + name, + intents, + permissions, + botPublicKey, + issuedBy: user.id, + ttlMs + }) + const bot = payloadToBotRow(payload, this.identity.snapshot().publicKey) + await this._ingestGuildBot(bot) + this.guild.gossipBotInstall(bot) + this.emit('bot', bot) + span.end({ + guildId, + botId: bot.id, + botName: String(bot.name || '').slice(0, 32), + permissionMask: Number(bot.permissions) || 0, + tokenIssued: !!token + }) + return { token, bot, expiresAt: payload.expiresAt } + } catch (err) { + this.log.error('bot.token error', { + guildId, + error: err?.message || String(err) + }) + span.fail(err) + throw err } - if (!this.botLimits) { - this.botLimits = new BotRateLimits({ storagePath: this.storagePath }) - await this.botLimits.ready() - } - await this.botLimits.assertBotRegister(this.guild.guild.id) - const { token, payload } = createInstallToken({ - keyPair: this.identity.keyPair, - guildId: this.guild.guild.id, - guildName: this.guild.guild.name, - guildTopic: this.guild.guild.topic, - botId: botId || id(), - name, - intents, - permissions, - botPublicKey, - issuedBy: user.id, - ttlMs - }) - const bot = payloadToBotRow(payload, this.identity.snapshot().publicKey) - await this._ingestGuildBot(bot) - this.guild.gossipBotInstall(bot) - this.emit('bot', bot) - return { token, bot, expiresAt: payload.expiresAt } } async installBotFromToken (tokenInput) { @@ -10020,7 +10038,8 @@ class PearcordPlatform extends EventEmitter { botId: bot.id, guildId: bot.guildId, botName: String(bot.name || '').slice(0, 32), - permissionMask: Number(bot.permissions) || 0 + permissionMask: Number(bot.permissions) || 0, + installed: true }) return { bot, token: typeof tokenInput === 'string' ? tokenInput : null } } catch (err) { @@ -10034,7 +10053,26 @@ class PearcordPlatform extends EventEmitter { } async verifyBotToken (tokenInput) { - return verifyInstallToken(tokenInput) + const guildId = this.guild?.guild?.id || null + const span = this.log.time('bot.verify', { guildId }) + try { + const result = verifyInstallToken(tokenInput, { + expectedGuildId: guildId || undefined + }) + span.end({ + guildId, + ok: !!result.ok, + botId: result.payload?.botId || null + }) + return result + } catch (err) { + this.log.error('bot.verify error', { + guildId, + error: err?.message || String(err) + }) + span.fail(err) + throw err + } } async sendBotMessage ({ botId, content, channelId } = {}) { @@ -10060,7 +10098,8 @@ class PearcordPlatform extends EventEmitter { guildId, channelId: chId, contentLength: String(content ?? '').length, - botId + botId, + messageSent: !!msg?.id }) return msg } catch (err) {