Umfragen: /umfrage als native Discord-Abstimmung
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Bewusst Discords eigenes Poll-Objekt statt einer Nachbildung mit Knoepfen —
Oberflaeche, Auszaehlen und Ergebnisanzeige macht Discord dann selbst, auch
auf dem Handy. Wir bauen nur die Frage zusammen.

Bis zu vier Antworten (Discord erlaubt zehn, vier halten den Befehl
uebersichtlich), Laufzeit von einer Stunde bis einer Woche, Mehrfachauswahl
optional. Rechte: ManageMessages.

Zwei gleiche Antworten werden abgefangen. Discord stoert das nicht, aber
abstimmen kann darauf niemand sinnvoll.

Nicht zu verwechseln mit /wunsch: das sammelt Feature-Wuensche dauerhaft und
zeigt sie auf der Roadmap. /umfrage ist die schnelle Frage zwischendurch und
laeuft von selbst ab. Steht so auch als Kommentar im Befehl.

Geprueft gegen die installierte discord.js-Fassung (14.27) statt gegen die
Dokumentation: Feldnamen aus den Typdefinitionen gelesen, und die fertige
Nutzlast durch discord.js' eigene Serialisierung gejagt. Kommt als
poll_media / allow_multiselect / layout_type 1 raus, genau wie Discords API
es erwartet.

Dazu: 18 Befehle laden ohne Namenskollision, Befehl steht auf der
oeffentlichen Liste in beiden Sprachen, Zahl auf der Produktseite von 17
auf 18.

Nutzungsbedingungen als Entwurf unter docs/ — Text fuer den Seiten-Editor,
keine Rechtsberatung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 06:34:04 +02:00
co-authored by Claude Fable 5
parent 52a735f83a
commit c8b1f604f0
9 changed files with 186 additions and 3 deletions
+85
View File
@@ -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.
+1
View File
@@ -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.',
+3
View File
@@ -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.',
+1 -1
View File
@@ -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,
+1
View File
@@ -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'],
+1 -1
View File
@@ -67,7 +67,7 @@ export default function BotLanding() {
</div>
)}
<div className="srv-sum-item">
<span className="srv-sum-n">17</span>
<span className="srv-sum-n">18</span>
<span className="srv-sum-label">{t('bot.statCommands')}</span>
</div>
<div className="srv-sum-item">
+2 -1
View File
@@ -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() {
+87
View File
@@ -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,
});
}
+5
View File
@@ -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.',