diff --git a/docs/nutzungsbedingungen.md b/docs/nutzungsbedingungen.md new file mode 100644 index 0000000..9d5b4a5 --- /dev/null +++ b/docs/nutzungsbedingungen.md @@ -0,0 +1,85 @@ +# Nutzungsbedingungen + +Diese Bedingungen gelten für den Discord-Bot **d4rkbot** und die Webseiten +`bot.d4rkst3r.de` und `hub.d4rkst3r.de`. Wer den Bot benutzt oder sich auf den +Seiten anmeldet, akzeptiert sie. + +Betreiber und Kontakt stehen im [Impressum](/impressum), was mit deinen Daten +passiert in der [Datenschutzerklärung](/datenschutz). + +## 1. Was das hier ist + +Der Bot ist ein privates Projekt für die D4RKST3R-Community. Er ist kostenlos, +es gibt keine bezahlten Funktionen und keine Premium-Stufen. + +Er läuft auf einem selbst betriebenen Server. Es gibt keine Zusage, dass er +durchgehend erreichbar ist, und keine feste Reaktionszeit bei Störungen. +Funktionen können jederzeit dazukommen, sich ändern oder wegfallen. + +## 2. Wer ihn nutzen darf + +Die Nutzung setzt ein Discord-Konto und damit die Zustimmung zu Discords +eigenen [Nutzungsbedingungen](https://discord.com/terms) voraus. Es gelten +Discords Altersgrenzen. + +## 3. Was nicht geht + +Beim Benutzen des Bots und der Seiten ist untersagt: + +* Inhalte einzureichen, die gegen geltendes Recht oder Discords Richtlinien + verstoßen — das gilt für Screenshots in der Galerie, Feature-Wünsche, + Bewerbungen, Tickets und Direktnachrichten an den Bot gleichermaßen +* zu versuchen, den Bot, die API oder das Webinterface zu überlasten, zu + umgehen oder in fremde Konten zu gelangen +* die API automatisiert über das hinaus zu nutzen, wofür ein Schlüssel + ausgegeben wurde +* fremde Inhalte einzureichen, an denen du keine Rechte hast + +Bei Verstößen können Zugriff, API-Schlüssel und Rollen entzogen und Inhalte +gelöscht werden — bei schweren Fällen ohne Vorwarnung. + +## 4. Deine Inhalte + +Was du einreichst — Screenshots, Wünsche, Texte — bleibt deins. Du erlaubst +uns, es im Rahmen der Community-Seiten anzuzeigen: Screenshots in der Galerie, +Wünsche auf der Roadmap, jeweils mit deinem Discord-Namen. + +Diese Erlaubnis kannst du zurückziehen. Schreib uns oder nutze die +Löschfunktion, dann verschwinden die Inhalte von den Seiten. + +## 5. Verknüpfte Rollen und API-Schlüssel + +Wer sein Konto für verknüpfte Rollen freigibt, erlaubt dem Bot, Level, +Playtester-Status und die Dauer der Mitgliedschaft an Discord zu übermitteln. +Die Verknüpfung lässt sich jederzeit in Discord unter *Einstellungen → +Autorisierte Apps* zurücknehmen. + +API-Schlüssel sind persönlich und nicht zur Weitergabe gedacht. Wer einen +Schlüssel weitergibt, haftet für das, was damit passiert. + +## 6. Haftung + +Der Bot wird ohne Gewähr bereitgestellt. Für Schäden aus der Nutzung wird nur +gehaftet, soweit sie auf Vorsatz oder grober Fahrlässigkeit beruhen oder eine +wesentliche Vertragspflicht verletzt wurde. + +Die Haftung für Verletzungen von Leben, Körper und Gesundheit sowie nach dem +Produkthaftungsgesetz bleibt davon unberührt. + +Für verlinkte fremde Seiten und für die Erreichbarkeit von Discord selbst wird +keine Haftung übernommen. + +## 7. Beendigung + +Du kannst die Nutzung jederzeit beenden — Bot vom Server entfernen, Konto +entkoppeln, Löschung deiner Daten verlangen. Wir können den Betrieb jederzeit +einstellen; in dem Fall wird es vorher im Discord angekündigt, soweit möglich. + +## 8. Änderungen + +Diese Bedingungen können sich ändern, etwa wenn Funktionen dazukommen. Die +jeweils gültige Fassung steht auf dieser Seite. + +## 9. Recht + +Es gilt deutsches Recht. diff --git a/frontend/src/locales/de.js b/frontend/src/locales/de.js index 008a705..d43383d 100644 --- a/frontend/src/locales/de.js +++ b/frontend/src/locales/de.js @@ -267,6 +267,7 @@ export default { 'commands.remind': 'Erinnert dich per Direktnachricht — etwa „2h" oder „30m".', 'commands.tag': 'Ruft einen gespeicherten Textbaustein ab, mit Vorschlägen beim Tippen.', 'commands.ping': 'Lebenszeichen des Bots.', + 'commands.umfrage': 'Startet eine Abstimmung mit bis zu vier Antworten — Discord zählt selbst aus.', 'commands.warn': 'Verwarnt jemanden, schickt eine Nachricht und schreibt es ins Protokoll.', 'commands.warns': 'Zeigt die Verwarnungs-Historie.', 'commands.timeout': 'Setzt eine Auszeit.', diff --git a/frontend/src/locales/en.js b/frontend/src/locales/en.js index c0d8a45..10ecf7d 100644 --- a/frontend/src/locales/en.js +++ b/frontend/src/locales/en.js @@ -279,6 +279,8 @@ export default { 'feat.gallery.desc': 'Images from one channel show up in the public gallery.', 'feat.voting': 'Feature wishes', 'feat.voting.desc': 'Submit wishes and vote on them — on Discord and on the roadmap.', + 'feat.polls': 'Polls', + 'feat.polls.desc': 'Quick votes as a native Discord poll — /umfrage.', 'feat.giveaways': 'Giveaways', 'feat.giveaways.desc': 'Enter with a button, automatic draw, reroll available.', 'feat.birthdays': 'Birthdays', @@ -352,6 +354,7 @@ export default { 'commands.remind': 'Reminds you by direct message — something like “2h” or “30m”.', 'commands.tag': 'Fetches a saved text snippet, with suggestions as you type.', 'commands.ping': 'A sign of life from the bot.', + 'commands.umfrage': 'Starts a vote with up to four answers — Discord does the counting.', 'commands.warn': 'Warns someone, sends them a message and writes it to the log.', 'commands.warns': 'Shows the warning history.', 'commands.timeout': 'Puts someone in timeout.', diff --git a/frontend/src/module-icons.jsx b/frontend/src/module-icons.jsx index b0a49f6..c7b82c1 100644 --- a/frontend/src/module-icons.jsx +++ b/frontend/src/module-icons.jsx @@ -20,7 +20,7 @@ export const MODULE_ICONS = { events: IconEvents, playtester: IconGame, alpha_keys: IconKey, role_menus: IconRoles, applications: IconApplications, moderation: IconShield, anti_raid: IconLock, modlog: IconArchive, modmail: IconMail, - linked_roles: IconLink, + linked_roles: IconLink, polls: IconThumbsUp, tickets: IconSupport, welcome: IconMagic, autorole: IconTeam, sticky_roles: IconRefresh, bug_reports: IconWarning, server_monitor: IconServer, watchdog: IconMonitor, temp_voice: IconVoice, diff --git a/frontend/src/pages/BotCommands.jsx b/frontend/src/pages/BotCommands.jsx index 9c09c9d..2f95c09 100644 --- a/frontend/src/pages/BotCommands.jsx +++ b/frontend/src/pages/BotCommands.jsx @@ -14,6 +14,7 @@ const GROUPS = [ ['/remind dauer:… text:…', 'commands.remind'], ['/tag name:…', 'commands.tag'], ['/ping', 'commands.ping'], + ['/umfrage frage:… a:… b:…', 'commands.umfrage'], ]], ['commands.g.mod', true, [ ['/warn user:… grund:…', 'commands.warn'], diff --git a/frontend/src/pages/BotLanding.jsx b/frontend/src/pages/BotLanding.jsx index ee9fe15..c0b3638 100644 --- a/frontend/src/pages/BotLanding.jsx +++ b/frontend/src/pages/BotLanding.jsx @@ -67,7 +67,7 @@ export default function BotLanding() { )}
- 17 + 18 {t('bot.statCommands')}
diff --git a/src/bot/client.js b/src/bot/client.js index 8f23529..cea1a10 100644 --- a/src/bot/client.js +++ b/src/bot/client.js @@ -40,12 +40,13 @@ import * as tag from './commands/tag.js'; import * as remind from './commands/remind.js'; import * as geburtstag from './commands/geburtstag.js'; import * as raid from './commands/raid.js'; +import * as umfrage from './commands/umfrage.js'; // Alle Commands hier eintragen — jedes Modul exportiert { data, execute } const commandModules = [ ping, devlogBackfill, bug, playtesterSetup, galerieBackfill, wunsch, giveaway, ticketSetup, warn, warns, timeout, purge, rank, tag, remind, geburtstag, - raid, + raid, umfrage, ]; export async function startBot() { diff --git a/src/bot/commands/umfrage.js b/src/bot/commands/umfrage.js new file mode 100644 index 0000000..6928ab1 --- /dev/null +++ b/src/bot/commands/umfrage.js @@ -0,0 +1,87 @@ +// /umfrage — native Discord-Umfrage starten. +// +// Bewusst Discords eigenes Poll-Objekt statt einer Nachbildung mit Knöpfen: +// die Oberfläche, das Auszählen und die Ergebnisanzeige macht Discord dann +// selbst, auch auf dem Handy. Wir bauen nur die Frage zusammen. +// +// Nicht zu verwechseln mit /wunsch — das sammelt Feature-Wünsche dauerhaft +// und zeigt sie auf der Roadmap. Diese hier ist für die schnelle Frage +// zwischendurch und läuft nach der eingestellten Zeit von selbst ab. +import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, PollLayoutType } from 'discord.js'; +import { moduleEnabled } from '../../modules.js'; + +// Discord erlaubt bis zu 10 Antworten; vier reichen für den Alltag und halten +// den Befehl übersichtlich. Die ersten beiden sind Pflicht. +const ANTWORT_FELDER = ['a', 'b', 'c', 'd']; + +const DAUER = [ + { name: '1 Stunde', value: 1 }, + { name: '4 Stunden', value: 4 }, + { name: '1 Tag', value: 24 }, + { name: '3 Tage', value: 72 }, + { name: '1 Woche', value: 168 }, +]; + +export const data = new SlashCommandBuilder() + .setName('umfrage') + .setDescription('Startet eine Umfrage in diesem Kanal') + .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) + .addStringOption((o) => o.setName('frage').setDescription('Worum geht es?').setRequired(true).setMaxLength(300)) + .addStringOption((o) => o.setName('a').setDescription('Erste Antwort').setRequired(true).setMaxLength(55)) + .addStringOption((o) => o.setName('b').setDescription('Zweite Antwort').setRequired(true).setMaxLength(55)) + .addStringOption((o) => o.setName('c').setDescription('Dritte Antwort').setMaxLength(55)) + .addStringOption((o) => o.setName('d').setDescription('Vierte Antwort').setMaxLength(55)) + .addIntegerOption((o) => o.setName('dauer').setDescription('Wie lange offen? (Standard: 1 Tag)').addChoices(...DAUER)) + .addBooleanOption((o) => o.setName('mehrfach').setDescription('Mehrere Antworten erlaubt? (Standard: nein)')); + +export async function execute(interaction) { + if (!moduleEnabled('polls')) { + await interaction.reply({ + content: '❌ Umfragen sind ausgeschaltet — im Panel unter **Community** einschaltbar.', + flags: MessageFlags.Ephemeral, + }); + return; + } + + const frage = interaction.options.getString('frage').trim(); + const antworten = ANTWORT_FELDER + .map((feld) => interaction.options.getString(feld)?.trim()) + .filter(Boolean) + .map((text) => ({ text })); + + // Zwei gleiche Antworten sind kein Fehler für Discord, aber Unsinn für die + // Auswertung — hier abfangen statt eine kaputte Umfrage zu posten. + const einmalig = new Set(antworten.map((a) => a.text.toLowerCase())); + if (einmalig.size !== antworten.length) { + await interaction.reply({ + content: '❌ Zwei Antworten sind gleich — dann kann niemand sinnvoll abstimmen.', + flags: MessageFlags.Ephemeral, + }); + return; + } + + try { + await interaction.channel.send({ + poll: { + question: { text: frage }, + answers: antworten, + duration: interaction.options.getInteger('dauer') ?? 24, + allowMultiselect: interaction.options.getBoolean('mehrfach') ?? false, + layoutType: PollLayoutType.Default, + }, + }); + } catch (error) { + console.error('[umfrage]', error); + await interaction.reply({ + content: '❌ Umfrage konnte nicht gepostet werden — darf der Bot hier schreiben?', + flags: MessageFlags.Ephemeral, + }); + return; + } + + // Die Umfrage selbst ist die sichtbare Antwort; die Bestätigung bleibt still + await interaction.reply({ + content: `✅ Umfrage läuft — ${antworten.length} Antworten.`, + flags: MessageFlags.Ephemeral, + }); +} diff --git a/src/modules.js b/src/modules.js index eb5d088..966e140 100644 --- a/src/modules.js +++ b/src/modules.js @@ -98,6 +98,11 @@ export const MODULES = [ tab: 'community', default: true, requires: [{ setting: 'voting_channel_id', label: 'Voting-Kanal', create: { name: 'ideen', topic: 'Wuensche und Abstimmungen' } }], }, + { + id: 'polls', group: 'community', name: 'Umfragen', + desc: 'Schnelle Abstimmungen als native Discord-Umfrage — /umfrage.', + tab: 'community', default: true, + }, { id: 'giveaways', group: 'community', name: 'Verlosungen', desc: 'Teilnahme per Knopf, automatische Ziehung, Neuauslosung möglich.',