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:
@@ -0,0 +1,11 @@
|
||||
# Vorlage — nach .env kopieren und Werte eintragen (NIEMALS .env committen!)
|
||||
|
||||
# Discord Developer Portal → deine App → Bot → "Reset Token"
|
||||
DISCORD_TOKEN=
|
||||
|
||||
# Discord Developer Portal → deine App → General Information → "Application ID"
|
||||
DISCORD_CLIENT_ID=
|
||||
|
||||
# Rechtsklick auf deinen Discord-Server → "Server-ID kopieren" (Entwicklermodus nötig)
|
||||
# Optional: wenn gesetzt, sind Slash-Commands sofort verfügbar statt nach bis zu 1h
|
||||
DISCORD_GUILD_ID=
|
||||
@@ -0,0 +1,2 @@
|
||||
# Einheitliche LF-Zeilenenden (wichtig für Docker/Linux trotz Windows-Entwicklung)
|
||||
* text=auto eol=lf
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Secrets — niemals committen!
|
||||
.env
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Laufzeitdaten (SQLite etc.)
|
||||
data/
|
||||
|
||||
# Logs & OS-Kram
|
||||
*.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# EcoBot — Discord-Bot + (später) Webinterface
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Erst nur Manifest kopieren → Docker-Layer-Cache für npm ci
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY src ./src
|
||||
|
||||
# Laufzeitdaten (SQLite ab Feature 2) landen in /app/data → Volume
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD ["node", "src/index.js"]
|
||||
@@ -0,0 +1,114 @@
|
||||
# EcoBot
|
||||
|
||||
Discord-Bot + Webinterface für die EcoGame-Community.
|
||||
|
||||
**Features (Roadmap):**
|
||||
1. ✅ Bot online + `/ping` Slash-Command
|
||||
2. ⬜ Commit-Feed: Gitea-Push-Webhooks → Discord-Embeds + SQLite-Archiv
|
||||
3. ⬜ Devlog-Archiv: tägliche Devlogs archivieren
|
||||
4. ⬜ Webinterface: Discord-Login, Devlog- & Commit-Seiten
|
||||
|
||||
**Stack:** Node.js 20+, discord.js v14, Fastify (ab Feature 2), React (ab Feature 4), SQLite, Docker
|
||||
|
||||
---
|
||||
|
||||
## Setup: Discord-App anlegen (einmalig)
|
||||
|
||||
### 1. App erstellen
|
||||
1. Öffne das [Discord Developer Portal](https://discord.com/developers/applications)
|
||||
2. **New Application** → Name: `EcoBot` → Create
|
||||
3. Unter **General Information** die **Application ID** kopieren
|
||||
→ das ist `DISCORD_CLIENT_ID`
|
||||
|
||||
### 2. Bot-Token holen
|
||||
1. Linke Seitenleiste → **Bot**
|
||||
2. **Reset Token** → Token kopieren → das ist `DISCORD_TOKEN`
|
||||
⚠️ Der Token wird nur einmal angezeigt — direkt in die `.env` eintragen!
|
||||
3. Auf der Bot-Seite weiter unten: **Message Content Intent** aktivieren
|
||||
(brauchen wir ab Feature 3 zum Lesen der Devlog-Nachrichten)
|
||||
|
||||
### 3. Bot auf den Server einladen
|
||||
1. Linke Seitenleiste → **OAuth2** → **URL Generator**
|
||||
2. Scopes ankreuzen: `bot` und `applications.commands`
|
||||
3. Bot Permissions ankreuzen:
|
||||
- **Send Messages**
|
||||
- **Embed Links**
|
||||
- **Read Message History**
|
||||
4. Generierte URL unten kopieren, im Browser öffnen, deinen Server auswählen → **Autorisieren**
|
||||
|
||||
### 4. Server-ID holen (für sofortige Slash-Commands)
|
||||
1. In Discord: Einstellungen → Erweitert → **Entwicklermodus** aktivieren
|
||||
2. Rechtsklick auf deinen Server → **Server-ID kopieren**
|
||||
→ das ist `DISCORD_GUILD_ID`
|
||||
|
||||
### 5. .env anlegen
|
||||
```
|
||||
cp .env.example .env
|
||||
```
|
||||
Dann die drei Werte eintragen. Die `.env` ist in `.gitignore` und landet nie im Repo.
|
||||
|
||||
---
|
||||
|
||||
## Lokal starten (Entwicklung)
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Erwartete Ausgabe:
|
||||
```
|
||||
[bot] Eingeloggt als EcoBot#1234
|
||||
[bot] 1 Slash-Command(s) registriert (Guild)
|
||||
```
|
||||
|
||||
Dann in Discord: `/ping` → Bot antwortet mit Latenz. 🎉
|
||||
|
||||
---
|
||||
|
||||
## Deployment (Docker / Portainer)
|
||||
|
||||
### Variante A: Portainer-Stack aus Git (empfohlen)
|
||||
1. Portainer → **Stacks** → **Add stack**
|
||||
2. **Repository** wählen:
|
||||
- URL: `https://git.d4rkst3r.de/D4rkst3r/ecobot.git`
|
||||
- Compose path: `docker-compose.yml`
|
||||
3. Unter **Environment variables** die drei Werte aus der `.env` eintragen
|
||||
(`DISCORD_TOKEN`, `DISCORD_CLIENT_ID`, `DISCORD_GUILD_ID`)
|
||||
— alternativ `env_file` weglassen und die Variablen direkt im Stack setzen
|
||||
4. **Deploy the stack**
|
||||
|
||||
### Variante B: Manuell auf dem Server
|
||||
```
|
||||
git clone git@gitea:D4rkst3r/ecobot.git
|
||||
cd ecobot
|
||||
cp .env.example .env # Werte eintragen
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Logs prüfen:
|
||||
```
|
||||
docker logs -f ecobot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```
|
||||
ecobot/
|
||||
├── src/
|
||||
│ ├── index.js # Einstiegspunkt
|
||||
│ ├── config.js # Env-Konfiguration mit Validierung
|
||||
│ └── bot/
|
||||
│ ├── client.js # Discord-Client, Command-Registry, Interactions
|
||||
│ └── commands/
|
||||
│ └── ping.js # /ping — Lebenszeichen
|
||||
├── .env.example # Vorlage für Secrets (nach .env kopieren)
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
Neue Slash-Commands: Datei in `src/bot/commands/` anlegen (exportiert `data` + `execute`)
|
||||
und in `src/bot/client.js` bei `commandModules` eintragen.
|
||||
@@ -0,0 +1,17 @@
|
||||
# EcoBot Stack — deploybar über Portainer (Stacks → Add stack)
|
||||
services:
|
||||
ecobot:
|
||||
build: .
|
||||
image: ecobot:latest
|
||||
container_name: ecobot
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
# Ab Feature 2: HTTP-Endpoint für Gitea-Webhooks + Webinterface
|
||||
# ports:
|
||||
# - "3080:3080"
|
||||
volumes:
|
||||
# SQLite & Co. überleben Container-Neustarts
|
||||
- ecobot_data:/app/data
|
||||
|
||||
volumes:
|
||||
ecobot_data:
|
||||
Generated
+331
@@ -0,0 +1,331 @@
|
||||
{
|
||||
"name": "ecobot",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ecobot",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.16.3",
|
||||
"dotenv": "^16.4.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz",
|
||||
"integrity": "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "^0.6.2",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.38.40",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/collection": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
||||
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/formatters": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
|
||||
"integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.3.tgz",
|
||||
"integrity": "sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.1",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@sapphire/async-queue": "^1.5.3",
|
||||
"@sapphire/snowflake": "^3.5.5",
|
||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||
"discord-api-types": "^0.38.50",
|
||||
"magic-bytes.js": "^1.13.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "^6.27.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/util": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
|
||||
"integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
|
||||
"integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.0",
|
||||
"@discordjs/rest": "^2.5.1",
|
||||
"@discordjs/util": "^1.1.0",
|
||||
"@sapphire/async-queue": "^1.5.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vladfrangu/async_event_emitter": "^2.2.4",
|
||||
"discord-api-types": "^0.38.1",
|
||||
"tslib": "^2.6.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/async-queue": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
||||
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/shapeshift": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
|
||||
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v16"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/snowflake": {
|
||||
"version": "3.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
|
||||
"integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vladfrangu/async_event_emitter": {
|
||||
"version": "2.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
|
||||
"integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.38.50",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.50.tgz",
|
||||
"integrity": "sha512-J2n/bpIETX3DQ6AJ7/0xbsTLmYiJQtO/LKcXKC1YDbB56OUwtDbdXOFE8Q4g8jVGHBR2VAy1+D4ngaIgkMNV9w==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"scripts/actions/documentation"
|
||||
]
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "14.27.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.27.0.tgz",
|
||||
"integrity": "sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.14.1",
|
||||
"@discordjs/collection": "1.5.3",
|
||||
"@discordjs/formatters": "^0.6.2",
|
||||
"@discordjs/rest": "^2.6.2",
|
||||
"@discordjs/util": "^1.2.0",
|
||||
"@discordjs/ws": "^1.2.3",
|
||||
"@sapphire/snowflake": "3.5.5",
|
||||
"discord-api-types": "^0.38.49",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"magic-bytes.js": "^1.13.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "^6.27.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.snakecase": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-bytes.js": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz",
|
||||
"integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-mixer": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
|
||||
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.21.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
||||
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "ecobot",
|
||||
"version": "0.1.0",
|
||||
"description": "Discord-Bot + Webinterface für die EcoGame-Community (Commit-Feed, Devlog-Archiv)",
|
||||
"author": "D4rkst3r",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "node --watch src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.16.3",
|
||||
"dotenv": "^16.4.7"
|
||||
}
|
||||
}
|
||||
@@ -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'})`
|
||||
);
|
||||
}
|
||||
@@ -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**`
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user