Multi-guild
Mantenha configuração, recursos, filas e interações independentes quando o mesmo bot atende vários servidores.
O problema que o escopo resolve#
Sem isolamento, duas guilds podem disputar a mesma configuração, fila ou instância de componente.
Configuração
Cada guild possui canais, cargos e variáveis próprios.
Concorrência
Filas lógicas recebem a guild na chave interna quando há contexto.
Interações
Prompts e paginações validam autor, guild e Client responsável.
Contexto automático#
async function sendSupportPanel() { const config = Client.appConfig() const channel = Client.getAppChannels().support const staff = Client.getAppRoles().staff if (!channel) return await sendPanel(channel.Id, { title: config.variables?.tickets?.title ?? 'Suporte', staffRoleId: staff?.Id })}Client.on(Events.GuildMemberAdd, { execute: async member => { // O evento ativa o contexto de member.guild.id. await sendSupportPanel() }})A função sendSupportPanel não recebe guildId, porque foi chamada durante um evento contextualizado. Se a mesma função também rodar em um script administrativo sem contexto, passe o ID explicitamente em sua assinatura.
Contexto explícito#
Jobs externos, scripts e callbacks fora do runtime precisam indicar a guild que desejam ler.
async function synchronizeAllGuilds() { for (const guild of Client.guilds.cache.values()) { const config = Client.appConfig(guild.id) const channels = Client.getAppChannels(guild.id) const roles = Client.getAppRoles(guild.id) await syncGuild({ guildId: guild.id, auditChannelId: channels.audit?.Id, staffRoleId: roles.staff?.Id, options: config.variables?.sync ?? {} }) }}Filas e cooldowns#
O Core acrescenta o escopo quando a chamada ocorre dentro de uma execução contextualizada.
Client.slash({ data: new SlashCommandBuilder() .setName('sync') .setDescription('Sincroniza o painel.'), cooldown: '15s', execute: async interaction => { await interaction.deferReply({ flags: 64 }) await Client.queue('panel-sync', async () => { await syncPanel(interaction.guildId) }) await interaction.editReply('Painel sincronizado.') }})Duas execuções de panel-sync na mesma guild entram na mesma fila. A mesma chave em outra guild segue independentemente. O cooldown de comando também considera a guild para não bloquear o usuário em todos os servidores.
Prompts e paginações escopados#
Cada solicitação registra uma instância temporária vinculada ao autor e à guild.
Client.registerPaginator('members-pages', async context => { const members = context.guildId ? await memberRepository.list(context.guildId) : [] return Paginator.dynamic({ id: 'members-pages', ownerId: context.ownerId, guildId: context.guildId, data: members, pageSize: 10, render: renderMembersPage })})- Outro usuário não consegue navegar pela instância aberta pelo autor.
- Um componente enviado em uma guild não é resolvido pela instância de outra guild.
- Dois Clients no mesmo processo usam namespaces de runtime separados.
- Dados retornados por uma factory devem ser consultados usando
context.guildId.
Checklist multi-guild#
- Toda tabela ou coleção persistente inclui
guildIdquando os dados pertencem ao servidor. - Caches criados pela aplicação usam
Map<guildId, value>ou uma chave composta. - IDs de canais e cargos vêm de aliases resolvidos, não de constantes globais.
- Factories de prompts e paginações usam
context.guildId. - Tasks de escopo
guildnão gravam o resultado em uma variável global única. - Logs de operação incluem a guild quando isso ajuda a diagnosticar a origem.