Compare commits

...

2 Commits

Author SHA1 Message Date
SimolZimol
3ed9c3270a modified: .env.example
modified:   README.md
2025-10-27 23:11:46 +01:00
SimolZimol
0388fa241d modified: app.py 2025-10-27 23:09:01 +01:00
3 changed files with 131 additions and 24 deletions

View File

@@ -1,13 +0,0 @@
# Discord Bot Token (wird in Coolify als Umgebungsvariable gesetzt)
DISCORD_TOKEN=your_discord_bot_token_here
# Database Connection - Option 1: Full URL (PostgreSQL/MySQL)
DATABASE_URL=postgresql://username:password@host:port/database_name
# DATABASE_URL=mysql://username:password@host:port/database_name
# Database Connection - Option 2: Individual Variables
DB_HOST=localhost
DB_PORT=5432
DB_NAME=hoi4_elo
DB_USER=username
DB_PASSWORD=password

View File

@@ -4,18 +4,23 @@ Ein Discord Bot für Hearts of Iron IV, der über Coolify deployed werden kann.
## Features
- **Basic Commands**: Ping, Info, Server-Informationen
- **HOI4 Theme**: Speziell für Hearts of Iron IV Communities
- **Docker Support**: Bereit für Deployment über Coolify
- **Error Handling**: Robuste Fehlerbehandlung
- **Logging**: Ausführliche Logs für Debugging
- ELO-System (Standard/Competitive), Spiele-Lifecycle (/hoi4create, /hoi4setup, /hoi4end)
- Stats, History und Leaderboard
- Emoji-/Länder-Tag-Unterstützung (tags.txt, emotes.markdown)
- Automatische Rollenvergabe nach ELO (pro Kategorie)
- YouTube Musikplayer (yt-dlp + ffmpeg) mit Queue und Lautstärke
- Docker/Coolify ready
## Verfügbare Befehle
## Verfügbare Befehle (Auszug)
- `!ping` - Zeigt die Bot-Latenz an
- `!info` - Zeigt Bot-Informationen an
- `!help_hoi4` - Zeigt alle verfügbaren Befehle an
- `!server_info` - Zeigt Informationen über den aktuellen Server
- `/hoi4create <type> <name>` Neues Spiel anlegen (type: standard/competitive)
- `/hoi4setup <game> <user> <team> <t-level> [country]` Spieler hinzufügen (T1/2/3; country optional als HOI4-Tag)
- `/hoi4end <game> <winner_team|draw>` Spiel beenden, ELO berechnen, Rollen syncen
- `/hoi4stats [user]` ELO, Rank, Win/Draw/Loss
- `/hoi4history [limit] [player] [name] [type]` Abgeschlossene Spiele filtern
- `/hoi4leaderboard [type] [limit]` Top ELOs
- Musik: `/join`, `/leave`, `/play <url|search>`, `/skip`, `/stop`, `/pause`, `/resume`, `/queue`, `/np`, `/volume <0-200>`
- Diagnose: `/webtest [query]` Testet Web-Konnektivität und yt-dlp-Extraction
## Setup für Coolify
@@ -38,8 +43,13 @@ Der Bot benötigt folgende Permissions:
### 3. Coolify Deployment
1. **Repository**: Verbinde dein Git Repository mit Coolify
2. **Umgebungsvariablen**: Setze folgende Variable in Coolify:
2. **Umgebungsvariablen**: Setze folgende Variablen in Coolify:
- `DISCORD_TOKEN` = Dein Bot Token
- Entweder `DATABASE_URL` (mysql://user:pass@host:port/db) ODER Einzelwerte `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
- Für YouTube (optional, empfohlen bei „Sign in to confirm youre not a bot“):
- `YTDL_COOKIES_B64` = Base64-kodierte cookies.txt (Netscape-Format), ODER
- `YTDL_COOKIES_FILE` = Pfad zu cookies.txt im Container (z. B. /app/cookies.txt)
- Optional: `YTDL_UA` (User-Agent), `YTDL_YT_CLIENT` (z. B. android/web)
3. **Build Settings**:
- Coolify wird automatisch das Dockerfile verwenden
@@ -76,6 +86,37 @@ Der Bot benötigt folgende Permissions:
python app.py
```
### YouTube-Cookies bereitstellen (empfohlen)
YouTube kann Server/Container ohne Cookies blockieren („Sign in to confirm youre not a bot“). Übergib Cookies im Netscape-Format:
Variante A Base64 via Env (einfach in Coolify)
1. Exportiere cookies.txt aus deinem Browser (Erweiterung „Get cookies.txt“ oder yt-dlp Export aus lokaler Browser-Session).
2. Base64-kodiere die Datei (Windows PowerShell):
```powershell
[Convert]::ToBase64String([IO.File]::ReadAllBytes("cookies.txt")) | Set-Clipboard
```
3. In Coolify `YTDL_COOKIES_B64` setzen (Inhalt aus der Zwischenablage einfügen) und neu deployen.
Variante B Datei mounten
- cookies.txt in den Container mounten (z. B. `/app/cookies.txt`) und `YTDL_COOKIES_FILE=/app/cookies.txt` setzen.
Optional: `YTDL_UA` setzen (aktueller Browser UA), `YTDL_YT_CLIENT` (z. B. `android`).
### Web testen (/webtest)
Mit `/webtest` prüft der Bot:
- HTTP-Reachability (Google 204, YouTube, Discord API)
- yt-dlp-Extraction mit deiner Query oder einem Default-Suchbegriff
- ob Cookies konfiguriert sind (Datei/B64/Browser)
So kannst du schnell sehen, ob der Container Internetzugriff hat und ob yt-dlp wegen Cookies scheitert.
## Docker
### Lokal mit Docker testen

79
app.py
View File

@@ -11,6 +11,8 @@ from typing import Optional, List, Dict
from concurrent.futures import ThreadPoolExecutor
import functools
import traceback
import aiohttp
import time
import base64
from pathlib import Path
@@ -644,6 +646,83 @@ async def volume(ctx: commands.Context, percent: int):
now['source'].volume = vol
await ctx.reply(f"🔊 Volume set to {percent}%.")
# ------------- Connectivity / Web Test -------------
@bot.hybrid_command(name='webtest', description='Test outbound web access and YouTube extraction')
async def webtest(ctx: commands.Context, *, query: Optional[str] = None):
"""Check basic web reachability and try a lightweight yt-dlp extraction.
- Tests HTTP GET to a few endpoints
- Tries yt-dlp search or URL extraction (if yt-dlp is available)
"""
urls = [
("Google 204", "https://www.google.com/generate_204"),
("YouTube", "https://www.youtube.com"),
("Discord API", "https://discord.com/api/v10/gateway"),
]
results = []
timeout = aiohttp.ClientTimeout(total=8)
try:
async with aiohttp.ClientSession(timeout=timeout) as session:
for name, url in urls:
t0 = time.monotonic()
status = None
err = None
try:
async with session.get(url, allow_redirects=True) as resp:
status = resp.status
except Exception as e:
err = str(e)
dt = (time.monotonic() - t0) * 1000
results.append((name, url, status, err, dt))
except Exception as e:
results.append(("session", "<session>", None, f"Session error: {e}", 0.0))
# Try yt-dlp extraction if available
ytdlp_ok = False
ytdlp_msg = ""
chosen = query or "ytsearch1:never gonna give you up"
try:
loop = asyncio.get_running_loop()
info = await _ytdlp_extract(loop, chosen)
if info:
ytdlp_ok = True
ytdlp_msg = f"Success: {info.get('title', 'unknown title')}"
else:
ytdlp_msg = "No info returned (possibly cookies required or blocked)."
except Exception as e:
ytdlp_msg = f"Error: {e}"
# Cookies configured?
cookies_file = os.getenv('YTDL_COOKIES_FILE')
cookies_b64 = bool(os.getenv('YTDL_COOKIES_B64'))
from_browser = os.getenv('YTDL_COOKIES_FROM_BROWSER')
desc_lines = []
for name, url, status, err, dt in results:
if status is not None:
desc_lines.append(f"{name}: {status} ({dt:.0f} ms)")
else:
desc_lines.append(f"{name}: ❌ {err or 'unknown error'}")
desc = "\n".join(desc_lines)
embed = discord.Embed(title="🌐 Web Test", description=desc, color=discord.Color.teal())
embed.add_field(name="yt-dlp", value=("" + ytdlp_msg) if ytdlp_ok else ("" + ytdlp_msg), inline=False)
cookie_status = []
if cookies_file and os.path.exists(cookies_file):
cookie_status.append(f"file: {cookies_file}")
elif cookies_file:
cookie_status.append(f"file missing: {cookies_file}")
if cookies_b64:
cookie_status.append("b64: provided")
if from_browser:
cookie_status.append(f"from-browser: {from_browser}")
if not cookie_status:
cookie_status.append("none")
embed.add_field(name="yt-dlp cookies", value=", ".join(cookie_status), inline=False)
await ctx.reply(embed=embed)
def _flag_from_iso2(code: str) -> Optional[str]:
"""Return unicode flag from 2-letter ISO code (e.g., 'DE' -> 🇩🇪)."""
if not code or len(code) != 2: