'use strict' const slashMixin = { _slashCommandGossipKeys: null, _slashRegistryHealWatermark: null, _slashUsageHealWatermark: null, _initSlashMixinState () { if (!this._slashCommandGossipKeys) { this._slashCommandGossipKeys = new Set() } }, _shouldGossipSlashCommand (row) { this._initSlashMixinState() if (!row?.guildId || !row?.name) return true const hash = `${row.guildId}:${row.name}:${row.description || ''}:${row.optionsJson || ''}:${row.defaultMemberPermissions ?? ''}:${row.builtin ? 1 : 0}:${row.updatedAt || row.createdAt || 0}` if (this._slashCommandGossipKeys.has(hash)) return false this._slashCommandGossipKeys.add(hash) if (this._slashCommandGossipKeys.size > 8192) { const first = this._slashCommandGossipKeys.values().next().value if (first) this._slashCommandGossipKeys.delete(first) } return true }, async _healSlashRegistryOnPartition (guildId) { const gid = guildId || this.guild?.guild?.id || null const span = this.log.time('slash.heal', { spanKind: 'slash.heal', guildId: gid, slice: 'registry' }) try { if (!gid || !this.slashCommands?.list) { span.end({ relisted: 0, skipped: true }) return { relisted: 0, skipped: true } } const rows = await this.slashCommands.list().catch(() => []) let relisted = 0 for (const row of rows) { if (row.guildId && row.guildId !== gid) continue const gossipRow = { id: row.id, guildId: row.guildId, name: row.name, description: row.description, optionsJson: row.options?.length ? JSON.stringify(row.options) : row.optionsJson || null, applicationId: row.applicationId, defaultMemberPermissions: row.defaultMemberPermissions ?? null, builtin: !!row.builtin, createdAt: row.createdAt } if (this._shouldGossipSlashCommand(gossipRow) && this.guild?.gossipSlashCommand) { this.guild.gossipSlashCommand(gossipRow) relisted++ } } const watermark = Date.now() this._slashRegistryHealWatermark = watermark span.end({ relisted, watermark, commandCount: rows.length }) return { relisted, watermark, commandCount: rows.length } } catch (err) { this.log.error('slash.heal error', { guildId: gid, slice: 'registry', error: err?.message || String(err) }) span.fail(err) return { relisted: 0, error: err?.message || String(err) } } }, async _healSlashUsageCursorOnPartition (guildId) { const gid = guildId || this.guild?.guild?.id || null const span = this.log.time('slash.create', { spanKind: 'slash.create', guildId: gid, context: 'heal.usage' }) try { if (!gid) { span.end({ relisted: 0, skipped: true, slashCommandCount: 0 }) return { relisted: 0, skipped: true } } const list = this.slashCommands ? await this.slashCommands.list().catch(() => []) : [] const watermark = Date.now() this._slashUsageHealWatermark = watermark span.end({ relisted: 0, watermark, slashCommandCount: list.length, customCount: list.filter((r) => !r.builtin).length, guildCount: (this.guilds || []).length, activeChannelId: this.activeChannelId || null }) return { relisted: 0, watermark, slashCommandCount: list.length } } catch (err) { this.log.error('slash.create error', { guildId: gid, context: 'heal.usage', error: err?.message || String(err) }) span.fail(err) return { relisted: 0, error: err?.message || String(err) } } } } module.exports = { slashMixin }