Giter VIP home page Giter VIP logo

csz-bot's Introduction

CSZ-Bot

CSZ Discord


Official Coding Shitpost Central Discord Bot

ℹ️ OwO wats dis?

Please ignore this repo. This is just a management bot I made for my stupid german coding discord server...

German description of the Discord Server:

Deutscher Server für diverse programmier- und nerd Themen.
Language-Bashing, shitposting und Autismus stehen an der Tagesordnung.
Jeder ist willkommen da jede Programmiersprache gleichermaßen diskreditiert wird!

I'm sorry


I want to make this stupid bot even worse

Read: CONTRIBUTING.md

🔧 Installation

Du hast 2 Optionen: Lokale installation und GitHub Codespaces. Ersteres ist aufwändiger. Bei letzterem musst du deine Config am besten abspeichern, weil sie bei Codespaces irgendwan zusammen mit dem Codespace gelöscht wird.

Lokale Installation

bun Version: >=1.irgendwas

  1. Terminal aufmachen und dorthin navigieren, wo man es downloaden möchte
  2. Sichergehen, dass bun installiert ist. Teste mit: bun --version. Wenn es eine Versionsnummer zurückgibt, ist bun installiert. Wenn nicht, bun hier runterladen.
  3. Repository klonen und hinein navigieren. Wenn Git installiert ist:
git clone https://github.com/NullDev/CSZ-Bot.git && cd $_

Wenn nicht, hier herunterladen und die ZIP extrahieren (Gott stehe dir bei) und dann in den Ordner navigieren.

  1. Dependencies installieren:
bun i
  1. Weiter machen mit den gemeinsamen Schritten (siehe unten)

GitHub Codespaces

  1. Klicke auf den grünen "Code"-Button
  2. Wähle den Tab "Codespaces"
  3. Klicke auf das "+" für einen neuen Codespace
  4. Warte
  5. Weiter machen mit den gemeinsamen Schritten (siehe unten)

Gemeinsame Schritte

  1. Das Config-Template config.template.json kopieren und als config.json einfügen und bearbeiten:
cp config.template.json config.json
$EDITOR config.json

Tip

Die Datei kann Kommentare und Trailing-Commas (JSONC). Wenn du nicht VSCode verwendest, musst du das ggf. noch einstellen.

  1. Das Template ist für die Coding-Test-Zentrale vorausgefüllt. Es fehlen noch:
    • Um einen Bot zum Testen anzulegen, einfach den Instruktionen im Discord Developer Portal folgen.
      • Die Applikation muss als "Bot" gesetzt werden.
      • Es müssen beide Gateway Intents eingeschalten werden.
      • Ersetze <CLIENT_ID> durch die Application-ID
      • Ersetze <BOT_TOKEN> durch das Bot-Token
    • Um IDs kopieren zu können, den "Developer Mode" in den Discord Einstellungen aktivieren. Mit Rechtsklick kann man dann die IDs kopieren.
  2. Das Script starten.

Mit Hot-Reload:

bun watch

Ohne Hot-Reload:

bun start

Housekeeping

Formatieren und Linten passiert durch lefthook automatisch beim Committen/Pushen. Manuell kannst du das machen:

  • Formatieren: bun format
  • Linten: bun lint
  • Fixbare Linter-Fehler automatisch fixen: bun lint:fix
  • CI-Checks lokal laufen lassen: bun ci
  • Unit-Tests ausführen: bun test
    • Nur Tests, die auf ein Pattern matchen: bun test <pattern> (z. B. bun test smoke)

❄ Nix

Entweder via nix-shell oder nix develop letzteres benötigt Nix-Flake support. Nix-Flakes nutzen ohne diese eingeschaltet zu haben geht via: nix --extra-experimental-features "flakes nix-command" develop

Wer auch immer einen Plan von Nix hat, kann das hier gerne weiter ausführen.

csz-bot's People

Contributors

5yn74x avatar amazingturtle avatar arellak avatar braintelligence avatar creepsore avatar dependabot[bot] avatar diewellenlaenge avatar ecklf avatar etlon avatar faro1991 avatar feluin avatar fionera avatar fivekwbassmachine avatar github-actions[bot] avatar holzmaster avatar j-brn avatar jonas32 avatar jw-ch avatar kavejra avatar kugelpfusch avatar lolritter avatar nulldev avatar patchrequest avatar raketexyz avatar scarwolf avatar soratenshi avatar twobiers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

csz-bot's Issues

Zitate in Zitatechannel

Funktionswunsch

Funktionen:
"Wertvolle" bzw. lustige Nachrichten sollen im Channel #zitate (888414626734280825). Dafür sollten alle Trusted-Leute eine Berechtigung haben, eine bestimmte Reaction auf eine Nachricht zu setzen. Sobald diese Reaction auf eine Nachricht gesetzt wurde, soll der Bot die Nachricht vernünftig formatiert mit einer Referenz auf die Originalnachricht in den o. g. Channel packen.

Todos:

  • Reaction hochladen und vernünftig benennen
  • Reaction-Handler bauen
  • Rechteprüfung auf Trusted
  • Jemanden beleidigen, der die Reaction nutzt aber nicht darf
  • Sollte die Nachricht aus einem dieser Channel kommen, dann soll die Nachricht anonym zitiert werden (mit Referenz aber ohne Nickname):
    • Gründerväter: 675723082073243648
    • Intern: 620734386639339520

Weitere Diskussion:
Bitte hier lesen: https://discord.com/channels/618781839338897443/620753789598564352/821657478521159741

Stempelkarte (Schritt 1: Stempeln)

Funktions Wunsch

Funktionen:

Wir machen eine Stempelkarte wie beim Dönermann. Erster Schritt das Stempeln an sich.
Also wir brauchen einen Mod Command, nennen wir ihn mal ~stempel damit kann ich für einen User einen bestimmten Invite stempeln. Das wird dann über unsere Tolle SQLite Datenbank gespeichert.

Als direktes Beispiel:
~stempel @UserA @UserB
würde bedeuten, dass der UserA UserB invited hat und einen Stempel dafür bekommt.
Wichtig ist jetzt, dass ein User nicht mehrfach gestempelt werden kann (Constraints und so blabla).

Wichtige Attribute, die in der Datenbank liegen müssen:
Zeitpunkt des Stempelns
User-ID Inviter
User-ID Invite

Application Commands Part 7: Actions

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114 )
  • Geblockt von der Migration #87 & Migration Poll #120 (das muss zu erst erledigt werden)

Beschreibung

Da nun die schwierigste Arbeit erledigt ist und der Grundstein gelegt ist, können wir die Module weiter vergrößern und dank der States auch Actions (z. B. Buttons, Select-Menüs) einbauen.

Todos

  • Je nach Modul, z. B. Buttons für Woisping

Beispiele

Aus dem Fork:

Update discord.js

discord.js ist alt. Muss neu (am besten auf einen Tag binden relativ aktuell mit Support für Slashcommands, Menus, Buttons etc.).

Problem: das Update hat breaking changes. Daher ist ein einfaches pullen von discord.js nicht ausreichend.

Tweak Prune

Funktions Wunsch

Funktionen:

Bot Vorschlag: (ich bin faul soll n anderer machen).
In 📖┃regeln-und-faq steht aktuell
Um die anderen Channel sehen zu können, müssen Sie unseren AGB's zustimmen. Sollten Sie dies nicht innerhalb von 48h tun, werden Sie automatisch gekickt.

das ist leider nicht ganz korrekt. Der Bot macht täglich nen prune für leute die 2tage / 48h inaktiv waren und keine rollen haben. hier:

https://github.com/NullDev/CSC-Bot/blob/master/src/app.js#L68-L76

Das bedeutet aber wenn die keine rollen haben aber auf discord trotzdem aktiv sind, werden die nie weg gepruned und bleiben (für uns inaktiv) weiterhin hier.

Deshalb request: Bot soll alle jockel die zwei tage nach joinen immer noch keine nerd rolle haben, kicken.
(Im 13:37 cron alle member mit ohne nerd-rolle auflisten und wenn (datetime.now - join.timestamp) > 48h -> kick)

Application Commands Part 0: Übersicht

Einzelne Issues

Bitte lest erst dieses Issue zu Ende, bevor ihr an einzelnen Issues arbeitet!

  1. Registrierung: #115
  2. Interactionhandler: #116
  3. Messagehandler-Bridge: #117
  4. Stateful Commands: #118
  5. Migration: #119
  6. Migration Poll: #120
  7. Actions: #121

Beschreibung

Wir wollen Application Commands (ehemals Slash Commands) in den CSZ-Bot einbauen. Wir haben das ganze schon einmal in einer Art Rewrite versucht: https://github.com/NullDev/CSC-Bot/tree/slash-commands-ts
Uns ist dort aufgefallen, dass es einige Probleme mit Application Commands gibt und insbesondere mit dem Weg, den wir gegangen sind. Wir haben versucht, alles auf einmal zu machen:

  • Umbau auf Application Commands
  • Direkt komplett in "vernünftigem" TypeScript geschrieben
  • Mit der Nutzung von Actions/Buttons/Select-Menüs
  • Bridge zur weiteren Nutzung von .-Commands, weil Application Commands limitiert sind

Das war ein Fehler. Viel zu groß und alles musste gleichzeitig geschehen, damit das veröffentlicht werden kann. Daher wollen wir unser Vorgehen ändern und dieses in mehrere Issues und PRs separieren, die in der angegebenen Reihenfolge abgearbeitet werden müssen. Mit dem nächsten Issue sollte erst angefangen werden, sobald das vorige Issue mit PR (komplett getestet) gelöst wurde. Nur so haben wir eine realistische Chance, das ganze im Laufe des Hacktobers umzusetzen, ohne dass irgendwelche Branches zu sehr divergieren.

Lessons learned

Um einmal die Fallstricke aufzuzeigen, ist hier eine Liste aus meinem Gedächtnis, was für Probleme aufgetreten sind:

  • Application Commands können global oder serverweit registriert werden. Globale Commands brauchen ewig (>1h) zum updaten. Das ist nicht gut debugbar. Daher sollten für die Entwicklung ausschließlich serverweite (d. h. auf den CSZ-Server angepasste) Commands genutzt werden.
  • Application Commands sind in den Parametern limitiert. Es gibt keine "variadic arguments". Somit ist es beispielsweise bei Polls nervig, mehrere verschiedene Optionen anzugeben. Per Discord-Vorgabe müsste man immer folgendes schreiben, um fünf Optionen zu haben: /poll multi Option 1 Option 2 o3:Option 3 o4:Option 4 o5:Option 5. Das ist nicht benutzerfreundlich (glaubt es uns, wir haben es probiert). Dieses Problem existiert auch beim .sdm (Beispiel hier oder bei .extend). Es gibt mehrere Lösungsmöglichkeiten:
    • Man lebt damit (nicht meine Präferenz).
    • Man baut eine Funktion, um komfortabel "variadic arguments" entgegenzunehmen. Zum Beispiel wie bisher mit ; getrennt.
  • Die Ausführung eines Application Commands resultiert in einer Interaction. Der Bot kann auf eine Interaction 15min lang reagieren, zum Beispiel per öffentlich sichtbarer Antwort oder per persönlicher Antwort. Dies bedeutet, dass die bisherige Struktur der Commands, einfach nur einen Antworttext zu returnen, nicht mehr ausreichend ist. Stattdessen müssen "riche" Antworten returnbar sein (Beispiel mit Woisping).
  • Application Commands können nicht als Reply genutzt werden. Dies ist problematisch für z. B. .mock. Daher ist weiterhin eine Art Bridge nötig, damit .mock auch weiterhin genutzt werden kann.
  • Die Abstraktion war in unserem Fork viel zu hoch. Baut nicht sowas hier. Lasst es einfach und stupide.
  • Application Commands werden mit der späteren Umsetzung von Buttons/Select-Menüs einen State haben müssen. Man wird auf eine Nachricht per Button individuell reagieren können (z. B. Polls). Das ist in den bisherigen Modulen eher rudimentär umgesetzt.

Ziele

  • Einfache Umsetzung ohne großartige Abstraktionen.
  • Für die Übergangsphase muss es möglich sein, noch nicht nach Application Commands migrierte Module auch weiterhin so wie bisher nutzen zu können.
  • Einige Applicaion Commands müssen auch in Zukunft mit . nutzbar sein, z. B. .mock.
  • Wir müssen eine vernünftige Lösung für "variadic arguments" finden.
  • Erst nach der Migration auf Application Commands sollte die Migration auf Actions/Buttons/Select-Menüs erfolgen.
  • Wir brauchen einen vernünftigen Umgang mit States für Application Commands, z. B. für Woisping oder anonymen Polls.

Non-Goals

  • Nicht alles auf einmal machen.
  • Nicht durch eine super geniale Abstraktion (TM) alles kompliziert machen (been there, it hurts).

TimeZone Bug #2

TimeZone Fix.
Mein issue #60 wurde nur sehr hacky gefixed indem eine falsche Zeit im Cron hinterlegt wurde. Siehe:

747a674

danke @diewellenlaenge


Problem ist, Geburtstage sind ebenfalls einen ganzen Tag verzögert.

.sdm user mention zeigt nur ID an anstatt Namen

Bug

Fehler beschreibung:

Wenn man .sdm benutzt und jemanden in seiner Nachricht pingt, dann wird im Embed nur die User ID angegeben anstatt bspw. der Name. Die ID ist an der Stelle also wertlos.

Reproduzierung:

.sdm Küsse ich heute @ShadowByte#1337 ?
Ansonsten siehe oben.

Erwartetes Resultat:

Normalerweise würde man erwarten, dass dort ein Name steht, statt die ID.

Screenshots:

grafik

.mock vermockt emojis

Fehler Report

Fehler beschreibung:

Eine genaue beschreibung des Fehlers
wenn .mock ein emoji beinhaltet, wird dieses weggemockt.

Reproduzierung:

Wie kann man den Fehler reproduzieren?
.mock <a:BOOBA:831436659169689642>
-> :BoObA: wird halt dann weggemockt.

Erwartetes Resultat:

Was sollte eigentlich passieren?
das emoji bleibt erhalten.

Poll / Vote Images

Funktions Wunsch

Funktionen:

Ich hab ein bisschen mit den Embed Farben gespielt, dass man besser auf einen Blick erkennen kann was nun genau Vote / Strawpoll / Poll ist. Find ich aber irgendwie hässlich. Wäre cool mal mit Icons zu testen.

Anregungen von meiner Seite:

Transparent, Cartoon-Style oder Sketch-Style. Ähnlich zu SDM

*Edit: Vorschläge von mir sind in https://github.com/NullDev/CSC-Bot/tree/master/assets zu finden.

Update DevContainer

Die streetsidesoftware.code-spell-checker-german wird vor der streetsidesoftware.code-spell-checker geladen und funktioniert daher nicht. in der devcontainer.json muss die Reihenfolge geändert werden.

Ansonsten wäre es noch cool, wenn jemand das Ding rund machen würde, notwendige Settings für Formatting, linting, etc. pp alles was notwendig ist mit reinklatscht. Referenz: https://code.visualstudio.com/docs/remote/devcontainerjson-reference

Vorschlag von meiner Seite: SonarLint Extension sollte mit rein.

Stempelkarte (Schritt 2: Die Stempelkarte)

Funktions Wunsch

Vorraussetzung:
#85

Funktionen:

Command .stempelkarte zeigt die persönliche Stempelkarte an. Sprich wie viele User invited wurden.
Jetzt wäre es sexy, wenn das nicht schnöde als Text ausgegeben wird, sondern ein Bild generiert wird oder irgendwie sowas. Keine Ahnung, das Issue ist nicht sonderlich durchdacht. Lasst eure Kreativität spielen. Ein Vorschlag ist hier zu finden: https://cdn.discordapp.com/attachments/620753789598564352/862963212010258432/image.png

Tempbans / Selfbans persistieren

Funktions Wunsch

Funktionen:

Wir haben SQLite, der ban zeitrum wird aber noch in-mem gespeichert, ist also nach einem redeploy weg. Also persistieren. Dazu #76 beachten.

Application Commands Part 5: Migration

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114)
  • Geblockt von Statuful Commands #118 (das muss zu erst erledigt werden)

Beschreibung

Da die Grundlagen nun vorhanden sind, können wir alle Module migrieren. Es ist unnötig, für jedes Modul ein eigenes Issue zu erstellen. Stattdessen sollte jedes Modul einen eigenen PR kriegen. Jede Menge Potential fürs Hacktoberfest :)

Todos

  • Es muss eine Lösung oder Entscheidung für oder gegen „variadic arguments“ getroffen werden

  • Wir müssen eine Entscheidung treffen wie das Zusammenspiel der Permissions von Slash-Commands und MessageCommands laufen soll (Vorschlag von @tobi6112: Commands definieren ihre Permissions)

  • Normale Commands

    • Erleuchtung
    • Extend (siehe hier: #120)
    • Ficktabelle
    • Info
    • Invite
    • Metafrage
    • Min
    • Mock
    • Never
    • Poll (sehr komplex, bitte hier schauen: #120)
    • Roll
    • SDM
    • Selfban
    • Woisping
    • Vote (siehe hier: #120)
  • Modcommands

    • Assigner
    • Ban (am besten mit unban vereinen in eine Datei)
    • Listroles
    • Stempeln
  • Hilfe am Ende komplett refactoren, sodass nur noch eine Liste aller Commands angezeigt wird. Die einzelnen Infos bietet Discord direkt per Hilfe an.

Beispiele

Aus dem Fork:

Application Commands Part 2: Interactionhandler

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114)
  • Geblockt von der Registrierung #115 (das muss zu erst erledigt werden)

Beschreibung

Es muss ein Interactionhandler registriert werden, der die Anfragen an die einzelnen Module aufteilt. Dafür ist es notwendig, dass die App die Module und deren Commands kennt, um die Weiterleitung durchzuführen

Todos

  • App muss Infos über alle Commands haben
  • Interactionhandler muss auf Application Commands reagieren
  • Interactionhandler muss die Daten ans jeweilige Modul weitergeben
  • [x] Es muss eine Lösung oder Entscheidung für oder gegen „variadic arguments“ getroffen werden (Verschoben nach: #119)
  • Module müssen "riche" Antworten geben können (ggf. abhängig/relevant für #107)

Beispiele

Aus dem Fork:

Mock über Reply

Funktions Wunsch

Funktionen:

Der Mock Apparillo soll die Nachricht im Reply mocken, falls reply da ist.

Komplett auf ES6 Modules switchen und auf require verzichten

Mussten Upgrade der Dependencies reverten: a9bc420

Weil wegen:

node:internal/modules/cjs/loader:1112
      throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
      ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /app/node_modules/node-fetch/src/index.js
require() of ES modules is not supported.
require() of /app/node_modules/node-fetch/src/index.js from /app/built/commands/erleuchtung.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /app/node_modules/node-fetch/package.json.

    at new NodeError (node:internal/errors:370:5)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1112:13)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:816:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:93:18)
    at Object.<anonymous> (/app/built/commands/erleuchtung.js:7:15)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1124:10)
    at Module.load (node:internal/modules/cjs/loader:975:32) {
  code: 'ERR_REQUIRE_ESM'
}

Wäre cool, wenn wir dementsprechend auch mal auf ES6 Modules switchen könnten. Es muss also auch den CommandHandler refactored werden. Ganz schön dickes Ding hier.

Bot kaputt

image

Unbenutzbar. würde Geld zurückverlangen-

Erweiterbare Umfragen mit einer Option erlauben

Funktions Wunsch

Funktionen:

Erweiterbare Umfragen sollen auch mit einer vorgegebenen Antwortmöglichkeit erstellbar sein.

.poll -e Ich heiße ...; Klaus Peter erlaubt
.poll -s Ich heiße ...; Klaus Peter nicht erlaubt

Application Commands Part 4: Stateful Commands

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114)
  • Geblockt von der Messagehandler-Bridge #117 (das muss zu erst erledigt werden)

Beschreibung

Damit das volle Potential von Application Commands genutzt werden kann, sollten die Module irgendwie mit States umgehen können. Dies ist beispielsweise notwendig für anonyme Polls oder Woisgang-Abfragen. Bisher bastelt jedes Modul sich seinen eigenen temporären Store. Das ist kacke, weil der verloren geht, wenn der Bot neustartet.

Todos

  • Konzept ausarbeiten (bisher existiert dazu noch nichts)
  • Irgendwie versuchen, das mit Sequelize o. ä. zu lösen, damit der State auch einen Restart überlebt
  • Das irgendwie versuchen, generisch zu halten, sodass jedes Modul sich einen eigenen State bauen kann

Refactoring vom reactionhandler

Jo bitte Mal refactoren, ist echt hässliches Ding und wir werden den auch mit Slash Commands nicht los. Erst wenn wir Interactions angreifen und das dauert noch. Also pls Mal bisschen aufräumen. Thx

Fehler bei hochlichtung

Hier wird ein invalid User verlinkt, obwohl die ID korrekt ist. Liegt es an dem "!", da in den PNs und außerhalb des Servercontext kein Nickname gesetzt ist?

"Bei Fragen kannst du dich an @ShadowByte#1337 (<@!371724846205239326>) wenden!"

Spamschutz für "unnötige" Commands

Eine Funktion, die übermäßiges benutzten von unnötigen Commands sperrt. Stattdessen wird eine Fehlermeldung, wie "ne du Hurensohn, ich hab grade kein bock und hör auf zu spammen" ausgegeben.

Der Channel bot-spam soll von dieser Funktion ignoriert werden.

Jedes Command soll zunächst als unnötig gelten. Ausnahmen von dieser Regelung soll für ein Command eingestellt werden können.
Implementationsvorschlag:
Die letzten 20? Commands werden mit user und timestamp gespeichert. (Datenbank ist optional, in mem reicht eigentlich) Wenn ein User mehr als 5? oder einem gewissen prozentsatz der letzten commands in einem gewissen zeitraum (30 min) abgeschickt hat, soll diese Funktion greifen.

Mehr Umfrageoptionen erlauben

Funktions Wunsch

Funktionen:

Im Moment gehen nur 10 Umfrageoptionen. Wir wollen mehr. Also erweitern mit den Buchstaben A-Z. Beachte maximale Anzahl an Reactions und Zeichenlimit in Embeds.

Update Node Version

Wir sind jetzt auf Node >= 16.6.0 .

Das muss noch an zwei Stellen angepasst werden:

  • README.md
  • .nvmrc

Unit-Tests

Funktions Wunsch

Funktionen:

xDDDDDDDDDDDD ach was mach ich mir vor... Wäre trotzdem cool und vielleicht hat wer Bock dazu.

Metafrage Command

Funktions Wunsch

Funktionen:

Command gegen Metafragen, warum doof, warum besser nicht. Link zu http://metafrage.de/ und Zusammenfassung als Nachricht.

Wenn man ganz fancy sein möchte vielleicht automatische Erkennung solcher Fragen durch die üblichen "Kennt sich jemand ...", "Jemand Ahnung von ..." Floskeln.

Tempbans implementieren

Funktions Wunsch

Funktionen:

Mods sollen User temporär bannen können. Als Grundlage dafür sollte die selfban Funktion verwendet werden und entsprechend angepasst werden.

Application Commands Part 6: Migration Poll

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114)
  • Geblockt von Statuful Commands #119 (das muss zu erst erledigt werden)

Beschreibung

Das Poll-Modul ist gerade schon groß und wird in Zukunft noch größer. Es soll für eine Umfrage mehrere Modi geben. So soll zum Beispiel die bisherige Funktionalität aus .vote und .extend hier mit einfließen.

Modi

  • multi: bekannter Standardmodus
  • straw: Strawmodus mit mehreren Möglichkeiten
  • list: wie multi, nur dass auch nur eine Antwortmöglichkeit erlaubt ist und soll automatisch immer erweiterbar sein
  • vote: wie heutiges .vote (also nur ja/nein)

Todos

  • Modi einbauen
  • .vote entfernen
  • .extend einbauen
  • Anonyme Umfragen erlauben
  • Umfragen mit Endzeitpunkt erlauben
  • Anonymität soll auch ohne Endzeitpunkt möglich sein
  • Restricted Mode: man soll seine Stimme nicht zurückziehen können wenn gewünscht

Beispiele

Aus dem Fork (bisher nicht committed):

Code (poll.ts)
"use strict";

import { VerifiedCommandInteraction, Result } from "../types";

// ========================= //
// = Copyright (c) NullDev = //
// ========================= //

// Dependencies
let moment = require("moment");
let parseOptions = require("minimist");
let cron = require("node-cron");
const AdditionalMessageData = require("../storage/model/AdditionalMessageData");
const logger = require("../utils/logger");

// Utils
let config = require("../utils/configHandler").getConfig();

const NUMBERS = [
    ":one:",
    ":two:",
    ":three:",
    ":four:",
    ":five:",
    ":six:",
    ":seven:",
    ":eight:",
    ":nine:",
    ":keycap_ten:"
];

const EMOJI = [
    "1️⃣",
    "2️⃣",
    "3️⃣",
    "4️⃣",
    "5️⃣",
    "6️⃣",
    "7️⃣",
    "8️⃣",
    "9️⃣",
    "🔟"
];

/**
 * @typedef {Object} DelayedPoll
 * @property {String} pollId
 * @property {Date} createdAt
 * @property {Date} finishesAt
 * @property {string[][]} reactions
 * @property {string[]} reactionMap
 */

/**
 * @type {DelayedPoll[]}
 */
exports.delayedPolls = [];

/**
 * Creates a new poll (multiple answers) or strawpoll (single selection)
 *
 * @param {import("discord.js").Client} client
 * @param {import("discord.js").Message} message
 * @param {Array} args
 * @param {Function} callback
 * @returns {Function} callback
 */
//exports.run = (client, message, args, callback) => {
async function handler(interaction: VerifiedCommandInteraction): Promise<Result> {
    let options = parseOptions(args, {
        "boolean": [
            "channel",
            "extendable",
            "straw"
        ],
        string: [
            "delayed"
        ],
        alias: {
            channel: "c",
            extendable: "e",
            straw: "s",
            delayed: "d"
        }
    });

    let parsedArgs = options._;
    let delayTime = Number(options.delayed);

    if (!parsedArgs.length) return callback("Bruder da ist keine Umfrage :c");

    let pollArray = parsedArgs.join(" ").split(";").map(e => e.trim()).filter(e => e.replace(/\s/g, "") !== "");
    let pollOptions = pollArray.slice(1);

    if (!pollOptions.length) return callback("Bruder da sind keine Antwortmöglichkeiten :c");
    else if (pollOptions.length < 2) return callback("Bruder du musst schon mehr als eine Antwortmöglichkeit geben 🙄");
    else if (pollOptions.length > 10) return callback("Bitte gib nicht mehr als 10 Antwortmöglichkeiten an!");


    let optionstext = "";
    pollOptions.forEach((e, i) => (optionstext += `${NUMBERS[i]} - ${e}\n`));

    let finishTime = new Date(new Date().valueOf() + (delayTime * 60 * 1000));
    if(options.delayed) {
        if(isNaN(delayTime) || delayTime <= 0) {
            return callback("Bruder keine ungültigen Zeiten angeben 🙄");
        }
        else if(delayTime > 60 * 1000 * 24 * 7) {
            return callback("Bruder du kannst maximal 7 Tage auf ein Ergebnis warten 🙄");
        }
        // Haha oida ist das cancer
        optionstext += `\nAbstimmen möglich bis ${new Date(finishTime.valueOf() + 60000).toLocaleTimeString("de").split(":").splice(0, 2).join(":")}`;
    }

    let embed = {
        embed: {
            title: pollArray[0],
            description: optionstext,
            timestamp: moment.utc().format(),
            author: {
                name: `${options.straw ? "Strawpoll" : "Umfrage"} von ${message.author.username}`,
                icon_url: message.author.displayAvatarURL()
            }
        }
    };

    let footer = [];
    let extendable = options.extendable && pollOptions.length < 10;

    if (extendable) {
        if(options.delayed) {
            return callback("Bruder du kannst -e nicht mit -d kombinieren. 🙄");
        }

        footer.push("Erweiterbar mit .extend als Reply");
        embed.embed.color = "GREEN";
    }

    if(options.delayed) {
        footer.push("⏳");
        embed.embed.color = "#a10083";
    }

    if (!options.straw) footer.push("Mehrfachauswahl");

    if (footer.length) {
        embed.embed.footer = {
            text: footer.join(" • ")
        };
    }

    let voteChannel = client.guilds.cache.get(config.ids.guild_id).channels.cache.get(config.ids.votes_channel_id);
    let channel = options.channel ? voteChannel : message.channel;
    if(options.delayed && channel !== voteChannel) {
        return callback("Du kannst keine verzögerte Abstimmung außerhalb des Umfragenchannels machen!");
    }

    /** @type {import("discord.js").TextChannel} */
    (channel).send(/** @type {Object} embed */(embed))
        .then(async msg => {
            message.delete();
            for (let i in pollOptions) await msg.react(EMOJI[i]);

            if(options.delayed) {
                const reactionMap = [];
                /** @type {string[][]} */
                const reactions = [];
                pollOptions.forEach((option, index) => {
                    reactionMap[index] = option;
                    reactions[index] = [];
                });

                let delayedPollData = {
                    pollId: msg.id,
                    createdAt: new Date().valueOf(),
                    finishesAt: finishTime.valueOf(),
                    reactions,
                    reactionMap
                };

                let additionalData = await AdditionalMessageData.fromMessage(msg);
                let newCustomData = additionalData.customData;
                newCustomData.delayedPollData = delayedPollData;
                additionalData.customData = newCustomData;
                await additionalData.save();

                exports.delayedPolls.push(delayedPollData);
            }
        });

    return callback();
};

exports.importPolls = async() => {
    let additionalDatas = await AdditionalMessageData.findAll();
    let count = 0;
    additionalDatas.forEach(additionalData => {
        if(!additionalData.customData.delayedPollData) {
            return;
        }

        exports.delayedPolls.push(additionalData.customData.delayedPollData);
        count++;
    });
    logger.info(`Loaded ${count} polls from database`);
};

/**
 * Initialized crons for delayed polls
 * @param {import("discord.js").Client} client
 */
exports.startCron = (client) => {
    cron.schedule("* * * * *", async() => {
        const currentDate = new Date();
        const pollsToFinish = exports.delayedPolls.filter(delayedPoll => currentDate >= delayedPoll.finishesAt);
        /** @type {import("discord.js").TextChannel} */
        const channel = client.guilds.cache.get(config.ids.guild_id).channels.cache.get(config.ids.votes_channel_id);

        for(let i = 0; i < pollsToFinish.length; i++) {
            const delayedPoll = pollsToFinish[i];
            const message = await channel.messages.fetch(delayedPoll.pollId);

            let users = {};
            await Promise.all(delayedPoll.reactions
                .flat()
                .filter((x, uidi) => delayedPoll.reactions.indexOf(x) !== uidi)
                .map(async uidToResolve => {
                    users[uidToResolve] = await client.users.fetch(uidToResolve);
                }));

            let toSend = {
                embed: {
                    title: `Zusammenfassung: ${message.embeds[0].title}`,
                    description: `${delayedPoll.reactions.map((x, index) => `${NUMBERS[index]} ${delayedPoll.reactionMap[index]} (${x.length}):
${x.map(uid => users[uid]).join("\n")}\n\n`).join("")}
`,
                    timestamp: moment.utc().format(),
                    author: {
                        name: `${message.embeds[0].author.name}`,
                        icon_url: message.embeds[0].author.iconURL
                    },
                    footer: {
                        text: `Gesamtabstimmungen: ${delayedPoll.reactions.map(x => x.length).reduce((a, b) => a + b)}`
                    }
                }
            };

            await channel.send(toSend);
            await Promise.all(message.reactions.cache.map(reaction => reaction.remove()));
            await message.react("✅");
            exports.delayedPolls.splice(exports.delayedPolls.indexOf(delayedPoll), 1);

            let messageData = await AdditionalMessageData.fromMessage(message);
            let {customData} = messageData;
            delete customData.delayedPollData;
            messageData.customData = customData;
            await messageData.save();
        }
    });
};

exports.description = `Erstellt eine Umfrage mit mehreren Antwortmöglichkeiten (standardmäßig mit Mehrfachauswahl) (maximal 10).
Usage: ${config.bot_settings.prefix.command_prefix}poll [Optionen?] [Hier die Frage] ; [Antwort 1] ; [Antwort 2] ; [...]
Optionen:
\t-c, --channel
\t\t\tSendet die Umfrage in den Umfragenchannel, um den Slowmode zu umgehen
\t-e, --extendable
\t\t\tErlaubt die Erweiterung der Antwortmöglichkeiten durch jeden User mit .extend als Reply
\t-s, --straw
\t\t\tStatt mehrerer Antworten kann nur eine Antwort gewählt werden
\t-d <T>, --delayed <T>
\t\t\tErgebnisse der Umfrage wird erst nach <T> Minuten angezeigt. (Noch) inkompatibel mit -e`;

export const applicationCommands: ApplicationCommandDefinition[] = [
    {
        handler,
        data: {
            name: "poll",
            description: "Erstellt eine Umfrage.",
            options: [
                {
                    name: "single",
                    type: "SUB_COMMAND",
                    description: "Erstellt eine Secure Decision aufgrund einer Fragestellung",
                    options: [
                        {
                            name: "question",
                            type: "STRING",
                            description: "Fragestellung",
                            required: true
                        }
                    ]
                },
                {
                    name: "multi",
                    type: "SUB_COMMAND",
                    description: "Erstellt eine Secure Decision anhand einer Auswahl",
                    options: [
                        {
                            name: "s1",
                            type: "STRING",
                            description: "Auswahlelement 1",
                            required: true
                        },
                        {
                            name: "s2",
                            type: "STRING",
                            description: "Auswahlelement 2",
                            required: true
                        },
                        {
                            name: "s3",
                            type: "STRING",
                            description: "Auswahlelement 3"
                        },
                        {
                            name: "s4",
                            type: "STRING",
                            description: "Auswahlelement 4"
                        },
                        {
                            name: "s5",
                            type: "STRING",
                            description: "Auswahlelement 35"
                        },
                        {
                            name: "s6",
                            type: "STRING",
                            description: "Auswahlelement 6"
                        },
                        {
                            name: "s7",
                            type: "STRING",
                            description: "Auswahlelement 7"
                        },
                        {
                            name: "s8",
                            type: "STRING",
                            description: "Auswahlelement 8"
                        },
                        {
                            name: "s9",
                            type: "STRING",
                            description: "Auswahlelement 9"
                        }
                    ]
                }
            ]
        }
    }
];
Code (vote.js)
"use strict";

const { MessageEmbed } = require("discord.js");
let moment = require("moment");
const { VoteData } = require("../storage/model/VoteData.js");

/**
 *
 * @param {import("discord.js").ButtonInteraction} interaction
 */
async function updateVotes(interaction) {
    /* eslint-disable */
    const { id, message, guild } = interaction;
    const votes = await VoteData.getVotes(id);
    const userNamesYes = votes.filter(v => v.vote).map(v => guild.members.cache.get(v.userId).user.username);
    const userNamesNo = votes.filter(v => !v.vote).map(v => guild.members.cache.get(v.userId).user.username);

    /**
     *  TODO: Edit the interaction reply embed - show votes
     *  If the interaction reply is not editable (or only within a timeslot),
     *  it may be necessary to create a separate message. However, this would
     *  require some additional steps as we need to keep track about the message
     *  ID.
     */
    await message.edit("test");
}

/**
 *
 * @param {import("discord.js").ButtonInteraction} interaction
 * @param {Function} callback
 */
async function handleYes(interaction, callback) {
    const interactionId = interaction.message.interaction?.id;
    const userId = interaction.member.id;
    await VoteData.setVote(interactionId, userId, true);
    interaction.reply({ content: "Hast mit Ja abgestimmt", ephemeral: true });
    updateVotes(interaction);
    return callback();
}

/**
 *
 * @param {import("discord.js").ButtonInteraction} interaction
 * @param {Function} callback
 */
async function handleNo(interaction, callback) {
    const interactionId = interaction.message.interaction?.id;
    const userId = interaction.member.id;
    await VoteData.setVote(interactionId, userId, false);
    interaction.reply({ content: "Hast mit Nein abgestimmt", ephemeral: true });
    updateVotes(interaction);
    return callback();
}

/**
 * @param {import("discord.js").CommandInteraction} interaction
 * @param {Function} callback
 */
async function handler(interaction, callback) {
    let question = interaction.options.get("question").value;
    if(!question.endsWith("?")) question += "?";

    let privacy = interaction.options.get("privacy").value;
    if(privacy !== "public") {
        interaction.reply({ content: "Geht noch nicht"});
        return callback();
    }

    interaction.reply({
        embeds: [
            new MessageEmbed()
                .setAuthor(`Abstimmung von ${interaction.member.user.username}`, interaction.member.user.displayAvatarURL())
                .setTitle(question)
                .setColor(0x206694)
                .setTimestamp(moment.utc().format())
        ],
        components: [
            {
                type: 1,
                components: [
                    {
                        type: 2,
                        label: "Ja!",
                        customID: "vote_yes",
                        style: 3,
                        emoji: {
                            id: null,
                            name: "👍"
                        }
                    },
                    {
                        type: 2,
                        label: "Nein!",
                        customID: "vote_no",
                        style: 4,
                        emoji: {
                            id: null,
                            name: "👎"
                        }
                    }
                ]
            }
        ]
    });

    return callback();
}

exports.description = "Erstellt ne Umfrage";

exports.applicationCommands = [
    {
        handler,
        data: {
            name: "vote",
            description: "Erstellt eine Umfrage",
            options: [
                {
                    name: "privacy",
                    type: "STRING",
                    description: "Privatsphäre der Umfrage",
                    required: true,
                    choices: [
                        {
                            name: "public",
                            value: "public"
                        },
                        {
                            name: "anonym",
                            value: "anonym"
                        }
                    ]
                },
                {
                    name: "question",
                    type: "STRING",
                    description: "Fragestellung",
                    required: true
                }
            ]
        },
        buttonHandler: {
            vote_yes: handleYes,
            vote_no: handleNo
        }
    }
];
Code (extend.js)
"use strict";

// =========================================== //
// = Copyright (c) NullDev & diewellenlaenge = //
// =========================================== //

/**
 * @typedef {import("discord.js").TextChannel} TC
 */

// Utils
let log = require("../utils/logger");
let config = require("../utils/configHandler").getConfig();

const NUMBERS = [
    ":one:",
    ":two:",
    ":three:",
    ":four:",
    ":five:",
    ":six:",
    ":seven:",
    ":eight:",
    ":nine:",
    ":keycap_ten:"
];

const EMOJI = [
    "1️⃣",
    "2️⃣",
    "3️⃣",
    "4️⃣",
    "5️⃣",
    "6️⃣",
    "7️⃣",
    "8️⃣",
    "9️⃣",
    "🔟"
];

/**
 * Extends an existing poll or strawpoll
 *
 * @param {import("discord.js").Client} client
 * @param {import("discord.js").Message} message
 * @param {Array} args
 * @param {Function} callback
 * @returns {Promise<Function>} callback
 */
exports.run = async(client, message, args, callback) => {
    if (!message.reference) return callback("Bruder schon mal was von der Replyfunktion gehört?");
    if (message.reference.guildID !== config.ids.guild_id || !message.reference.channelID) return callback("Bruder bleib mal hier auf'm Server.");

    let channel = client.guilds.cache.get(config.ids.guild_id).channels.cache.get(message.reference.channelID);

    if (!channel) return callback("Bruder der Channel existiert nicht? LOLWUT");

    let replyMessage = null;

    try {
        replyMessage = await /** @type {TC} */ (channel).messages.fetch(message.reference.messageID);
    }
    catch (err) {
        log.error(err);
        return callback("Bruder irgendwas stimmt nicht mit deinem Reply ¯\_(ツ)_/¯");
    }

    if (replyMessage.author.id !== client.user.id || replyMessage.embeds.length !== 1) return callback("Bruder das ist keine Umfrage ಠ╭╮ಠ");
    if (!replyMessage.embeds[0].author.name.startsWith("Umfrage") && !replyMessage.embeds[0].author.name.startsWith("Strawpoll")) return callback("Bruder das ist keine Umfrage ಠ╭╮ಠ");
    if (!replyMessage.editable) return callback("Bruder aus irgrndeinem Grund hat der Bot verkackt und kann die Umfrage nicht bearbeiten :<");
    if (replyMessage.embeds[0].color !== 3066993) return callback("Bruder die Umfrage ist nicht erweiterbar (ง'̀-'́)ง");

    let oldPollOptions = replyMessage.embeds[0].description.split("\n");

    if (oldPollOptions.length === 10) return callback("Bruder die Umfrage ist leider schon voll (⚆ ͜ʖ⚆)");

    for (let i = 0; i < oldPollOptions.length; ++i) {
        if (!oldPollOptions[i].startsWith(NUMBERS[i])) {
            return callback("Bruder das ist keine Umfrage ಠ╭╮ಠ");
        }
    }

    if (!args.length) return callback("Bruder da sind keine Antwortmöglichkeiten :c");

    let additionalPollOptions = args.join(" ").split(";").map(e => e.trim()).filter(e => e.replace(/\s/g, "") !== "");

    if (!additionalPollOptions.length) return callback("Bruder da sind keine Antwortmöglichkeiten :c");
    if (oldPollOptions.length + additionalPollOptions.length > 10) return callback(`Bruder die Umfrage hat schon ${oldPollOptions.length} Antwortmöglichkeiten und du wolltest noch ${additionalPollOptions.length} hinzufügen, dumm oder sowas?`);

    let originalAuthor = replyMessage.embeds[0].author.name.split(" ")[2];
    let authorNote = originalAuthor !== message.author.username ? ` (von ${message.author.username})` : "";

    let embed = replyMessage.embeds[0];
    embed.description += "\n";
    additionalPollOptions.forEach((e, i) => (embed.description += `${NUMBERS[oldPollOptions.length + i]} - ${e}${authorNote}\n`));

    if (oldPollOptions.length + additionalPollOptions.length === 10) {
        embed.color = null;
        delete embed.footer;
    }

    replyMessage.edit(undefined, embed).then(async msg => {
        for (let i in additionalPollOptions) await msg.react(EMOJI[oldPollOptions.length + Number(i)]);
    }).then(() => message.delete());

    return callback();
};

exports.description = `Nutzbar als Reply auf eine mit --extendable erstellte Umfrage, um eine/mehrere Antwortmöglichkeit/en hinzuzüfgen. Die Anzahl der bestehenden und neuen Antwortmöglichkeiten darf 10 nicht übersteigen.\nUsage: ${config.bot_settings.prefix.command_prefix}extend [Antwort 1] ; [...]`;

Timeout feature

Ein neues feature wurde auf Discord hinzugefügt und kann über die API verwendet werden.
Weitere Infos gibt es auf Timeout FAQ.

Application Commands Part 3: Messagehandler-Bridge

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114)
  • Geblockt vom Interactionhandler #116 (das muss zu erst erledigt werden)

Beschreibung

Da auch weiterhin einige Application Commands in Zukunft (und für die Übergangszeit, bis alles migriert wurde) per .-Befehl nutzbar sein muss, benötigen wir einen Messagehandler, der als eine Art Bridge fungiert und klassische Befehle in Application Commands übersetzen kann.

Todos

  • Messagehandler über Application Commands informieren
  • Callbacks in den Modulen müssen mit beiden Arten von Inputs (klassisch und Application Command) umgehen können (wo nötig, z. B. .mock)
  • Messagehandler muss irgendwie eine Interaction emulieren (oder so?)

Beispiele

Aus dem Fork:

Info command auf hübsches Embed umstellen

Der schickt derzeit eine PN an den User. Mit Slash Commands würde ich da aber lieber ein fancy Embed anzeigen wollen. Vielleicht kannst du mal ein hübsches Embed designen, was in den Channel geschrieben wird.
Du kannst dafür Tools nutzen wie https://autocode.com/tools/discord/embed-builder/ oder https://leovoel.github.io/embed-visualizer/ um grob das Layout zu machen.

Also:

  1. Schritt: Layout des Embdes festlegen
  2. Schritt: Bestehenden Code abändern, dass das Embed gesendet wird

Application Commands Part 1: Registrierung

Bedingungen

  • Bitte zu erst die Application Commands Übersicht lesen (#114)
  • Geblockt vom discord.js Update #87 (das muss zu erst erledigt werden)
  • Der PR #113 von holzmaster sollte zu erst rein

Beschreibung

Die Module müssen vernünftig geladen werden. Das darf erst nach dem ready-Event von discord.js erfolgen. Danach sollte jedes Modul selbstständig in der Lage sein, sein Command ohne weitere Abstraktion durch uns zu registrieren.

Todos

  • Module laden bei ready-Event
  • Beispiel-Command erstellen, welches einen Command registriert

Beispiele

Aus dem Fork:

Deprecated Dependencies

Wir kriegen beim Builden ein paar Warnings - mal drum kümmern:

npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated @sapphire/[email protected]: This version has been automatically deprecated by @favware/npm-deprecate. Please use a newer version.
npm WARN deprecated [email protected]: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated [email protected]: Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future
npm WARN deprecated [email protected]: This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.
npm WARN deprecated [email protected]: No longer supported. Install the latest release (0.20.2)

Update Readme

Die Readme reflektiert nicht den aktuellen Buildprozess, seitdem wir TypeScript haben.

Für die Entwicklung muss noch ein npm run watch vor npm start gemacht werden.

Ban-Log

Funktions Wunsch

Funktionen:

In #kommen-und-gehen (ID: 618889320073265192) pls auch reinschreiben, wenn jemand gebannt wurde (Selfban, Tempban, mod-ban) inkl. Grund

Log im Woistext wer joined und leaved

Funktions Wunsch

Log im Woistext wer joined und leaved

Funktionen:

Beim Join in woistext schreibt der bot das in den channel als log. Kommt voll oft vor man hört jemand ist geleaved oder gejoined und keiner weiß wer.

Baue das Feature selbst ein wollte nur Clean hier schonmal nen Issue dafür aufmachen

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.