Initiales Setup: minimaler Discord-Bot mit /ping, Docker-Deployment und README

- discord.js v14 Bot mit Slash-Command-Registry (Guild oder global)
- Konfiguration über .env mit Validierung (config.js)
- Dockerfile + docker-compose.yml für Portainer-Deployment
- README mit Schritt-für-Schritt-Anleitung (Discord Developer Portal)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:10:19 +02:00
co-authored by Claude Opus 4.8
commit 2b3fa3eb69
12 changed files with 633 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
// Discord-Client: Commands laden, registrieren und Interactions verarbeiten
import { Client, Collection, Events, GatewayIntentBits, REST, Routes } from 'discord.js';
import { config } from '../config.js';
import * as ping from './commands/ping.js';
// Alle Commands hier eintragen — jedes Modul exportiert { data, execute }
const commandModules = [ping];
export async function startBot() {
const client = new Client({
intents: [GatewayIntentBits.Guilds],
});
// Commands in Collection ablegen für schnellen Zugriff im Interaction-Handler
client.commands = new Collection();
for (const command of commandModules) {
client.commands.set(command.data.name, command);
}
client.once(Events.ClientReady, async (readyClient) => {
console.log(`[bot] Eingeloggt als ${readyClient.user.tag}`);
await registerCommands();
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) return;
try {
await command.execute(interaction);
} catch (error) {
console.error(`[bot] Fehler bei /${interaction.commandName}:`, error);
const reply = { content: '❌ Da ist etwas schiefgelaufen.', ephemeral: true };
if (interaction.replied || interaction.deferred) {
await interaction.followUp(reply).catch(() => {});
} else {
await interaction.reply(reply).catch(() => {});
}
}
});
await client.login(config.discordToken);
return client;
}
/** Slash-Commands bei Discord registrieren (Guild = sofort sichtbar, global = bis zu 1h Delay) */
async function registerCommands() {
const rest = new REST().setToken(config.discordToken);
const body = commandModules.map((c) => c.data.toJSON());
const route = config.discordGuildId
? Routes.applicationGuildCommands(config.discordClientId, config.discordGuildId)
: Routes.applicationCommands(config.discordClientId);
await rest.put(route, { body });
console.log(
`[bot] ${body.length} Slash-Command(s) registriert (${config.discordGuildId ? 'Guild' : 'global'})`
);
}
+14
View File
@@ -0,0 +1,14 @@
// Lebenszeichen-Command: /ping → antwortet mit Latenz
import { SlashCommandBuilder } from 'discord.js';
export const data = new SlashCommandBuilder()
.setName('ping')
.setDescription('Lebenszeichen — zeigt die Bot-Latenz');
export async function execute(interaction) {
const sent = await interaction.reply({ content: '🏓 Pong!', withResponse: true });
const latency = sent.resource.message.createdTimestamp - interaction.createdTimestamp;
await interaction.editReply(
`🏓 Pong! Latenz: **${latency}ms** | API: **${Math.round(interaction.client.ws.ping)}ms**`
);
}
+20
View File
@@ -0,0 +1,20 @@
// Zentrale Konfiguration — liest alle Werte aus der Umgebung (.env lokal, env_file im Container)
import 'dotenv/config';
/** Pflicht-Variable lesen, bei Fehlen sofort mit klarer Meldung abbrechen */
function required(name) {
const value = process.env[name];
if (!value) {
console.error(`[config] Fehlende Umgebungsvariable: ${name} — siehe .env.example`);
process.exit(1);
}
return value;
}
export const config = {
// Discord Bot
discordToken: required('DISCORD_TOKEN'),
discordClientId: required('DISCORD_CLIENT_ID'),
// Optional: Guild-ID für sofortige Slash-Command-Registrierung (global dauert bis zu 1h)
discordGuildId: process.env.DISCORD_GUILD_ID || null,
};
+13
View File
@@ -0,0 +1,13 @@
// Einstiegspunkt — startet den Discord-Bot (Webserver folgt in Feature 2)
import { startBot } from './bot/client.js';
process.on('unhandledRejection', (error) => {
console.error('[main] Unhandled Rejection:', error);
});
try {
await startBot();
} catch (error) {
console.error('[main] Bot-Start fehlgeschlagen:', error);
process.exit(1);
}