// Rollen, die man sich selbst per Knopf gibt. // // Discords eigenes Onboarding kann das auch, verlangt dafuer aber sieben // Vorgabekanaele, von denen fuenf beschreibbar sind. Der Server hat fuenf und // drei - nachgemessen, nicht geraten (Fehlercode 350000). Zwei Kanaele nur // anzulegen, damit eine Schwelle passt, macht den Server schlechter. Also ein // Knopf unter dem Willkommenstext. import { logger } from '#core/logger.ts'; import type { RestFull } from './api.ts'; import type { Interaction } from './gateway.ts'; import { SELF_ROLES, type SelfRolePlan } from './plan.ts'; import { createRoleResolver } from './roles.ts'; const log = logger('discord:rollen'); /** Interaktionsart: Klick auf eine Komponente. */ const MESSAGE_COMPONENT = 3; /** Antwortart: sichtbare Antwort, hier zusaetzlich nur fuer den Klickenden. */ const CHANNEL_MESSAGE = 4; const EPHEMERAL = 64; /** * Die Knopfleiste unter dem Willkommenstext. * Style 2 ist der graue Knopf - er soll einladen, nicht schreien. */ export function selfRoleComponents(): Array> { if (SELF_ROLES.length === 0) return []; return [{ type: 1, // Reihe components: SELF_ROLES.map((r) => ({ type: 2, // Knopf style: 2, label: r.label, custom_id: r.customId, emoji: { name: r.emoji }, })), }]; } export type RoleButtons = { handle: (interaction: Interaction) => Promise; }; export function createRoleButtons(api: RestFull, guildId: string): RoleButtons { // Ohne Bremse: ein Klick kommt von einem Menschen, und wenn eine Rolle // gerade erst angelegt wurde, soll der naechste Klick sie finden. const roles = createRoleResolver(api, guildId); async function reply(interaction: Interaction, content: string): Promise { await api.post(`/interactions/${interaction.id}/${interaction.token}/callback`, { type: CHANNEL_MESSAGE, data: { content, flags: EPHEMERAL }, }); } return { async handle(interaction) { if (interaction.type !== MESSAGE_COMPONENT) return; const customId = interaction.data?.custom_id; const plan: SelfRolePlan | undefined = SELF_ROLES.find((r) => r.customId === customId); if (!plan) return; const member = interaction.member; if (!member) { // Ausserhalb eines Servers gibt es keine Rollen zu vergeben. await reply(interaction, 'Das geht nur im Server selbst.'); return; } try { const id = await roles.id(plan.role); if (!id) { await reply(interaction, `Die Rolle **${plan.role}** gibt es gerade nicht. Sag einem Mod Bescheid.`); log.error(`Rolle "${plan.role}" nicht gefunden - Bauplan und Server laufen auseinander`); return; } const user = member.user.id; const path = `/guilds/${guildId}/members/${user}/roles/${id}`; const hatSie = member.roles.includes(id); if (hatSie) { await api.del(path, 'Rolle per Knopf abgegeben'); await reply(interaction, plan.offText); log.info(`${member.user.username} hat ${plan.role} abgegeben`); } else { await api.put(path, undefined, 'Rolle per Knopf geholt'); await reply(interaction, plan.onText); log.info(`${member.user.username} hat ${plan.role} geholt`); } } catch (err) { log.error(`Rollenwechsel fehlgeschlagen: ${(err as Error).message}`); // Auf einen Klick gehoert eine Antwort, auch wenn es schiefging - // sonst steht bei der Person nur "Interaktion fehlgeschlagen". await reply(interaction, 'Hat nicht geklappt. Versuch es gleich noch einmal.') .catch(() => undefined); } }, }; }