// Der Bauplan des Community-Servers. Deklarativ, damit man ihn lesen und // im Pull Request diskutieren kann, statt ihn in einer Oberflaeche zu suchen. // // Leitgedanke: der Kanal hat 5 bis 30 gleichzeitige Zuschauer. Ein Server mit // zwanzig Kanaelen wirkt bei dieser Groesse leer, und ein leerer Kanal haelt // niemanden. Fuenf sichtbare Kanaele, in denen etwas passiert, sind besser. // Wann welcher Kanal dazukommt, steht in docs/projects/discord-community.md. // // Jeder Eintrag hat einen `key`, der sich nie aendert, und einen `name`, der // sich aendern darf. Gefunden wird ueber `name` und `aliases`, verwiesen wird // ueber `key` - so wird aus einer Umbenennung eine Umbenennung und kein // zweiter Kanal daneben. import { bits } from './permissions.ts'; import { CH } from './api.ts'; // Zur Schreibweise: Kommentare und Bezeichner bleiben bei ae/oe/ue wie im // restlichen Repo. Alles, was ein Mitglied auf Discord zu sehen bekommt - // Kanalnamen, Themen, Rollennamen, der Willkommenstext, AutoMod-Meldungen - // steht in richtigem Deutsch. "Sei anstaendig" liest sich wie ein Tippfehler, // und der erste Eindruck eines Servers ist der Text darin. /** Schluessel, unter denen eine Webhook-URL im Zugangsdaten-Speicher landet. */ export type WebhookSlot = 'goLiveWebhook' | 'recapWebhook' | 'clipsWebhook' | 'technikWebhook'; export type RolePlan = { name: string; color: number; /** Eigene Gruppe in der Mitgliederliste. */ hoist: boolean; mentionable: boolean; permissions: bigint; /** Wofuer es die Rolle gibt - landet in der Vorschau und in der Doku. */ purpose: string; }; export type OverwritePlan = { /** Rollenname, oder '@everyone' fuer die Grundrolle. */ role: string; allow?: bigint; deny?: bigint; }; export type CategoryPlan = { key: string; name: string; /** Frueher benutzte Namen. Wird einer gefunden, wird umbenannt. */ aliases?: string[]; overwrites?: OverwritePlan[]; }; export type ChannelPlan = { key: string; name: string; aliases?: string[]; type: number; topic?: string; /** `key` einer Kategorie aus CATEGORIES. */ category?: string; overwrites?: OverwritePlan[]; webhooks?: Array<{ name: string; slot: WebhookSlot }>; }; // ---------------------------------------------------------------- Rechte /** * Was ein normales Mitglied ueberall darf. Bewusst als vollstaendige Liste * gesetzt statt auf Discords Vorgabe zu vertrauen - die aendert sich, und * "jeder darf alle anpingen" ist der Unterschied zwischen einem Server, den * man anlaesst, und einem, den man stummschaltet. */ export const EVERYONE = bits( 'CREATE_INSTANT_INVITE', // Mitglieder duerfen einladen. Wachstum vor Kontrolle. 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'SEND_MESSAGES_IN_THREADS', 'CREATE_PUBLIC_THREADS', 'ADD_REACTIONS', 'EMBED_LINKS', 'ATTACH_FILES', 'USE_EXTERNAL_EMOJIS', 'USE_EXTERNAL_STICKERS', 'USE_APPLICATION_COMMANDS', 'SEND_POLLS', 'CHANGE_NICKNAME', 'CONNECT', 'SPEAK', 'STREAM', 'USE_VAD', 'USE_SOUNDBOARD', ); // Nicht enthalten und damit verboten: MENTION_EVERYONE, SEND_TTS_MESSAGES, // CREATE_PRIVATE_THREADS und saemtliche Verwaltungsrechte. const MOD = EVERYONE | bits( 'KICK_MEMBERS', 'BAN_MEMBERS', 'MODERATE_MEMBERS', 'MANAGE_MESSAGES', 'MANAGE_THREADS', 'MANAGE_NICKNAMES', 'MUTE_MEMBERS', 'DEAFEN_MEMBERS', 'MOVE_MEMBERS', 'VIEW_AUDIT_LOG', 'MENTION_EVERYONE', ); // Kein MANAGE_GUILD, kein MANAGE_ROLES, kein MANAGE_CHANNELS: Moderation // heisst aufraeumen, nicht umbauen. Wer umbauen soll, bekommt es einzeln. // Abdu bekommt ADMINISTRATOR. Das Recht schliesst alle anderen ein und setzt // jede Kanal-Ausnahme ausser Kraft - auch kuenftige. Wer diese Rolle traegt, // kann den Server komplett umbauen und loeschen, deshalb gehoert sie an genau // ein Konto und an kein zweites. const ABDU = bits('ADMINISTRATOR'); export const ROLES: RolePlan[] = [ { name: 'Abdu', color: 0x9146ff, hoist: true, mentionable: true, permissions: ABDU, purpose: 'Der Kanal selbst. Steht ganz oben in der Mitgliederliste und traegt ' + 'Administrator, darf also alles. Nur fuer Abdus eigenes Konto.', }, { name: 'Mod', color: 0x5865f2, hoist: true, mentionable: true, permissions: MOD, purpose: 'Raeumt auf: loeschen, stummschalten, im Zweifel rauswerfen.', }, { name: 'Stammgast', color: 0x4fd1c5, hoist: true, mentionable: false, permissions: EVERYONE | bits('PRIORITY_SPEAKER'), purpose: 'Auszeichnung fuer Leute, die regelmaessig da sind. Bei dieser Kanalgroesse ' + 'ist sichtbare Zugehoerigkeit der wirksamste Hebel - die Rolle faerbt den ' + 'Namen und sortiert ihn in der Mitgliederliste nach oben.', }, { name: 'Live-Ping', color: 0x99aab5, hoist: false, mentionable: true, permissions: EVERYONE, purpose: 'Wird bei Stream-Start angepingt - selbst vergeben, nicht automatisch. ' + 'Ein Rundruf an alle bei jedem Stream ist der schnellste Weg, dass der ' + 'Server stummgeschaltet wird.', }, ]; // ---------------------------------------------------------------- Kanaele // // Zu den Namen: Discord schreibt Textkanaele klein und macht aus Leerzeichen // Bindestriche. Emoji, das Trennzeichen U+2502 und Umlaute bleiben unveraendert // stehen - nachgemessen am echten Server, nicht angenommen. export const CATEGORIES: CategoryPlan[] = [ { key: 'info', name: '📌 Info', aliases: ['Info', 'Informationen'] }, { key: 'community', name: '🎮 Community', aliases: ['Community', 'Textkanäle'] }, { key: 'intern', name: '🔒 Intern', aliases: ['Intern'], // Fuer normale Mitglieder gibt es diese Kategorie nicht. overwrites: [ { role: '@everyone', deny: bits('VIEW_CHANNEL') }, { role: 'Mod', allow: bits('VIEW_CHANNEL') }, ], }, ]; /** Nur lesen: schreiben darf hier niemand ausser Mods und Webhooks. */ const READONLY: OverwritePlan[] = [ { role: '@everyone', deny: bits('SEND_MESSAGES', 'CREATE_PUBLIC_THREADS') }, ]; /** Lesen, aber diskutieren nur in Threads - haelt die Ankuendigungen sauber. */ const ANNOUNCE: OverwritePlan[] = [ { role: '@everyone', deny: bits('SEND_MESSAGES'), allow: bits('ADD_REACTIONS', 'CREATE_PUBLIC_THREADS', 'SEND_MESSAGES_IN_THREADS'), }, ]; export const CHANNELS: ChannelPlan[] = [ { key: 'willkommen', name: '👋│willkommen', aliases: ['willkommen', 'willkommen-und-richtlinien'], type: CH.TEXT, category: 'info', topic: 'Was das hier ist und die drei Regeln.', overwrites: READONLY, }, { key: 'ankuendigungen', name: '📢│ankündigungen', aliases: ['ankuendigungen', 'ankündigungen'], type: CH.TEXT, category: 'info', topic: 'Stream-Start und die Zusammenfassung danach. Reden gerne in den Threads.', overwrites: ANNOUNCE, webhooks: [ { name: 'Go-Live', slot: 'goLiveWebhook' }, { name: 'Stream-Recap', slot: 'recapWebhook' }, ], }, { key: 'allgemein', name: '💬│allgemein', aliases: ['allgemein'], type: CH.TEXT, category: 'community', topic: 'Der Hauptkanal. Im Zweifel hier.', }, { key: 'clips', name: '🎬│clips', aliases: ['clips'], type: CH.TEXT, category: 'community', topic: 'Gute Momente aus dem Stream. Der Clip-Knopf postet hier hin.', webhooks: [{ name: 'Clips', slot: 'clipsWebhook' }], }, { key: 'voice', name: '🔊 Zusammen zocken', aliases: ['zusammen-zocken', 'Zusammen zocken'], type: CH.VOICE, category: 'community', }, { key: 'mod-chat', name: '🔨│mod-chat', aliases: ['mod-chat'], type: CH.TEXT, category: 'intern', topic: 'Nur Mods.', }, { key: 'technik-log', name: '🤖│technik-log', aliases: ['technik-log'], type: CH.TEXT, category: 'intern', topic: 'Meldungen des Toolkits: Probeläufe, Fehler, AutoMod-Treffer.', // Der Entwurfsmodus des Go-Live-Posts landet hier: dieselbe Nachricht, // die sonst in die Ankuendigungen ginge, nur ohne Ping und nur fuer Mods. // So sieht man das Ergebnis, bevor es der Server sieht. webhooks: [{ name: 'Technik', slot: 'technikWebhook' }], }, ]; // ---------------------------------------------------------------- Server /** * Servereinstellungen. * - verification_level 2 verlangt ein Konto, das aelter als fuenf Minuten * ist. Kostet echte Leute nichts und haelt Wegwerf-Konten draussen, was bei * einem oeffentlich von Twitch aus verlinkten Server der Normalfall ist. * - default_message_notifications 1 heisst: nur bei Erwaehnung. Sonst * klingelt jede Nachricht bei jedem, und der Server wird stummgeschaltet. */ export const GUILD_SETTINGS = { verification_level: 2, explicit_content_filter: 2, default_message_notifications: 1, system_channel_flags: 4, // Einrichtungstipps unterdruecken, Beitritte zeigen }; /** * Wird einmalig in #willkommen gepostet, wenn der Kanal noch leer ist. * {key} wird durch die Erwaehnung des Kanals mit diesem `key` ersetzt. */ export const WELCOME = `**Willkommen bei abdulive_** Das hier ist der Ort zwischen den Streams. Wer mitliest, gehört dazu. **Drei Regeln** > **1.** Sei anständig. Keine Beleidigungen, kein Rassismus, kein Sexismus. > **2.** Kein Spam, keine Werbung, keine fremden Streams im Chat. > **3.** Wenn ein Mod etwas sagt, ist das erledigt. Diskussion per DM. **Wo was passiert** > {allgemein} - alles andere > {clips} - die guten Momente > {ankuendigungen} - Stream-Start und die Zusammenfassung danach **Bescheid wissen, wenn der Stream startet** Knopf drücken. Angepingt wird nur, wer das auch möchte, und ein zweiter Druck nimmt die Rolle wieder weg. Twitch: `; // ---------------------------------------------------------- Selbstbedienung export type SelfRolePlan = { /** Landet im Knopf und kommt bei jedem Klick zurueck. Nie aendern. */ customId: string; /** Name der Rolle aus ROLES. */ role: string; label: string; emoji: string; /** Antwort beim Holen und beim Abgeben - sieht nur, wer geklickt hat. */ onText: string; offText: string; }; /** * Rollen, die man sich per Knopf selbst gibt. * * Discords Onboarding kann das auch, verlangt aber sieben Vorgabekanaele mit * fuenf beschreibbaren. Der Server hat fuenf und drei. Zwei Kanaele anzulegen, * damit eine Schwelle passt, macht den Server leerer statt besser - also ein * Knopf unter dem Willkommenstext. Siehe docs/projects/discord-community.md. */ export const SELF_ROLES: SelfRolePlan[] = [ { customId: 'rolle:live-ping', role: 'Live-Ping', label: 'Sag mir Bescheid, wenn der Stream startet', emoji: '🔴', onText: 'Passt. Du wirst in den Ankündigungen angepingt, wenn der Stream startet. ' + 'Nochmal drücken nimmt es wieder weg.', offText: 'Erledigt, du wirst nicht mehr angepingt.', }, ]; // ---------------------------------------------------------------- AutoMod /** * Zwei Regeln, die Discord selbst durchsetzt - ohne Bot, auch nachts um drei. * Mehr braucht es bei dieser Groesse nicht; Wortfilter kosten mehr Aerger * durch Fehlalarme, als sie an Spam verhindern. */ export type AutomodPlan = { name: string; /** Frueher benutzte Namen, damit umbenannt statt doppelt angelegt wird. */ aliases?: string[]; trigger_type: number; trigger_metadata: Record; }; export const AUTOMOD: AutomodPlan[] = [ { name: 'Erwähnungs-Spam', aliases: ['Erwaehnungs-Spam'], trigger_type: 5, trigger_metadata: { mention_total_limit: 5 }, }, { name: 'Spam-Erkennung', trigger_type: 3, trigger_metadata: {} }, ]; /** Was jemand zu sehen bekommt, dessen Nachricht AutoMod abgefangen hat. */ export const AUTOMOD_BLOCK = 'Das hat die Spam-Erkennung abgefangen. Bei Fragen an die Mods.';