Sync main into feature/nightly

This commit is contained in:
vibe-bot 2025-08-10 21:33:52 +02:00
commit a27eb76777
12 changed files with 79 additions and 85 deletions

View file

@ -29,13 +29,13 @@ jobs:
run: | run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "tag=main" >> $GITHUB_OUTPUT echo "tag=main" >> $GITHUB_OUTPUT
echo "version=1.0.0" >> $GITHUB_OUTPUT echo "version=1.1.0" >> $GITHUB_OUTPUT
echo "channel=stable" >> $GITHUB_OUTPUT echo "channel=stable" >> $GITHUB_OUTPUT
else else
# Ersetze Slashes durch Bindestriche für gültige Docker Tags # Ersetze Slashes durch Bindestriche für gültige Docker Tags
CLEAN_TAG=$(echo "${{ github.ref_name }}" | sed 's/\//-/g') CLEAN_TAG=$(echo "${{ github.ref_name }}" | sed 's/\//-/g')
echo "tag=$CLEAN_TAG" >> $GITHUB_OUTPUT echo "tag=$CLEAN_TAG" >> $GITHUB_OUTPUT
echo "version=1.0.0-nightly" >> $GITHUB_OUTPUT echo "version=1.1.0-nightly" >> $GITHUB_OUTPUT
echo "channel=nightly" >> $GITHUB_OUTPUT echo "channel=nightly" >> $GITHUB_OUTPUT
fi fi

View file

@ -8,7 +8,7 @@ RUN npm install --no-audit --no-fund
COPY web/ . COPY web/ .
# Umgebungsvariable für React Build verfügbar machen (Vite liest nur VITE_*) # Umgebungsvariable für React Build verfügbar machen (Vite liest nur VITE_*)
ARG VITE_BUILD_CHANNEL=stable ARG VITE_BUILD_CHANNEL=stable
ARG VITE_APP_VERSION=1.0.0 ARG VITE_APP_VERSION=1.1.0
ENV VITE_BUILD_CHANNEL=$VITE_BUILD_CHANNEL ENV VITE_BUILD_CHANNEL=$VITE_BUILD_CHANNEL
ENV VITE_APP_VERSION=$VITE_APP_VERSION ENV VITE_APP_VERSION=$VITE_APP_VERSION
RUN npm run build RUN npm run build

View file

@ -35,6 +35,11 @@ git push origin main
git branch -d feature/mein-experiment git branch -d feature/mein-experiment
``` ```
## Versionierung & Changelog
- Versionen werden in `README.md` (Badge) gepflegt
- Änderungen dokumentieren wir in `CHANGELOG.md`
- Nightly-Entwicklung: Features zuerst im Branch `feature/nightly`, Merge nach `main` für Release
## Docker Images ## Docker Images
- `:latest` - Hauptversion (main branch) - `:latest` - Hauptversion (main branch)
- `:feature-nightly` - Feature Version - `:feature-nightly` - Feature Version

View file

@ -1,93 +1,59 @@
# 🎵 Jukebox 420 - Discord Soundboard v1.0.0 # Jukebox 420 Discord Soundboard (v1.1.1)
Ein modernes, feature-reiches Discord Soundboard mit Web-Frontend, Discord-Bot und Docker-Deployment. Perfekt für Gaming-Communities, Streamer und Discord-Server. A modern, selfhosted Discord soundboard with a slick web UI and a Discord bot that plays sounds into your voice channels. Easy to run via Docker, fun to use with friends.
![Version](https://img.shields.io/badge/version-1.0.0-blue) ![Version](https://img.shields.io/badge/version-1.1.1-blue)
![Docker](https://img.shields.io/badge/docker-ready-green) ![Docker](https://img.shields.io/badge/docker-ready-green)
![Discord](https://img.shields.io/badge/discord-bot-purple) ![Discord](https://img.shields.io/badge/discord-bot-purple)
## ✨ Features ## ✨ Features
### 🎮 **Kern-Funktionen** - Web UI (Vite + React + TypeScript), 3 themes (Dark, Rainbow, 420)
- **Web-Frontend** mit modernem UI und 3 Themes (Dark, Rainbow, 420) - Discord bot (discord.js + @discordjs/voice)
- **Discord-Bot** für Voice-Channel Integration - MP3 & WAV playback, ffmpeg normalization
- **Sound-Management** mit Ordner-Unterstützung - Favorites, search, folders view (auto counters)
- **Live-Uhrzeit** (Berlin Timezone) - Live counters and a clean header/footer
- **Volume Control** pro Server - Admin area: bulk delete, inline rename, categories (CRUD) + bulk assign, remove custom badges
- **Favoriten-System** mit Cookie-Persistenz - Partymode: serverside random playback every 3090 seconds, globally synced via SSE; Panic stops for everyone
- Persistent state: volumes, plays, totalPlays, categories, badges in `/data/sounds/state.json`
### 🎨 **UI/UX Features** ## 🚀 Quick start
- **3 Themes**: Dark, Rainbow, 420 (Cannabis/Trippy)
- **Responsive Design** für Desktop & Mobile
- **Glassmorphism-Effekte** mit Backdrop-Blur
- **Animierte Hintergründe** (Rainbow & 420 Theme)
- **Live-Zähler** für Sounds und Abspielungen
### 🔧 **Admin-Funktionen** ### 1. Requirements
- **Admin-Login** (Passwort-basiert) - Docker & Docker Compose
- **Bulk-Delete** für mehrere Sounds - Discord bot token with intents: `Guilds`, `GuildVoiceStates`, `DirectMessages`
- **Sound-Umbenennen** mit Inline-Editor
- **Checkbox-Auswahl** für Massenoperationen
### 🎵 **Audio-Features**
- **MP3 & WAV Support** für Uploads und Playback
- **Audio-Normalisierung** (Loudnorm)
- **URL-Download** für MP3/WAV Links
- **Random-Play** für zufällige Sounds
- **Panic-Button** zum sofortigen Stoppen
### 📁 **Organisation**
- **Ordner-Unterstützung** mit Tab-Navigation
- **Favoriten-Tab** für gespeicherte Sounds
- **Neu-Tab** für die letzten 10 Uploads
- **Most Played** für Top 3 Sounds
- **Suchfunktion** für alle Sounds
## 🚀 Quick Start
### 1. Voraussetzungen
- **Docker & Docker Compose**
- **Discord Bot Token** mit folgenden Intents:
- `Guilds`
- `GuildVoiceStates`
- `DirectMessages`
- `MessageContent`
### 2. Setup ### 2. Setup
```bash ```bash
# Repository klonen # Clone repository
git clone https://github.com/flex420/jukebox-vibe.git git clone https://github.com/flex420/jukebox-vibe.git
cd jukebox-vibe cd jukebox-vibe
# .env Datei erstellen # Create .env
cp .env.example .env cp .env.example .env
``` ```
### 3. Konfiguration ### 3. Configuration
```env ```env
# .env Datei bearbeiten # Edit the .env file
DISCORD_TOKEN=dein_discord_bot_token_hier DISCORD_TOKEN=your_discord_bot_token_here
ADMIN_PWD=choose-a-strong-password
PORT=8080 PORT=8080
SOUNDS_DIR=/data/sounds SOUNDS_DIR=/data/sounds
# Optional: Bestimmte Server erlauben # Optionally restrict allowed guilds
ALLOWED_GUILD_IDS=GUILD_ID_1,GUILD_ID_2 ALLOWED_GUILD_IDS=GUILD_ID_1,GUILD_ID_2
# Optional: Audio-Normalisierung
NORMALIZE_AUDIO=true
NORMALIZE_TARGET=-14
NORMALIZE_THRESHOLD=-70
``` ```
### 4. Deployment ### 4. Deployment
```bash ```bash
# Container starten # Start container
docker compose up --build -d docker compose up --build -d
# Logs anzeigen # Logs
docker compose logs -f docker compose logs -f
# Status prüfen # Status
docker compose ps docker compose ps
``` ```
@ -204,6 +170,12 @@ docker pull flex420/jukebox-vibe:latest
docker run -d --name jukebox-420 -p 8199:8080 --env-file .env -v $(pwd)/data/sounds:/data/sounds flex420/jukebox-vibe:latest docker run -d --name jukebox-420 -p 8199:8080 --env-file .env -v $(pwd)/data/sounds:/data/sounds flex420/jukebox-vibe:latest
``` ```
## 🔒 SSL/HTTPS Hinweis (wichtig für Discord)
- Das Web-Frontend MUSS hinter HTTPS (SSL) ausgeliefert werden. Empfohlen ist ein DomainMapping (Reverse Proxy) mit gültigem Zertifikat (z. B. Traefik, Nginx, Caddy, Cloudflare).
- Hintergrund: Ohne TLS kann es zu Verschlüsselungs-/EncryptFehlern kommen, und Audio wird in Discord nicht korrekt wiedergegeben.
- Praxis: Richte eine Domain wie `https://soundboard.deinedomain.tld` auf das Frontend ein und aktiviere SSL (Lets Encrypt). Danach sollten Uploads/Playback stabil funktionieren.
## 📁 Projekt-Struktur ## 📁 Projekt-Struktur
``` ```

View file

@ -13,7 +13,7 @@ services:
- GUILD_ID=${GUILD_ID} - GUILD_ID=${GUILD_ID}
- ADMIN_PWD=${ADMIN_PWD} - ADMIN_PWD=${ADMIN_PWD}
- VITE_BUILD_CHANNEL=nightly - VITE_BUILD_CHANNEL=nightly
- VITE_APP_VERSION=1.0.0-nightly - VITE_APP_VERSION=1.1.1-nightly
volumes: volumes:
- ./data/sounds:/data/sounds - ./data/sounds:/data/sounds
- ./data/uploads:/data/uploads - ./data/uploads:/data/uploads

View file

@ -8,7 +8,7 @@ services:
- .env - .env
environment: environment:
- VITE_BUILD_CHANNEL=stable - VITE_BUILD_CHANNEL=stable
- VITE_APP_VERSION=1.0.0 - VITE_APP_VERSION=1.1.1
volumes: volumes:
- ./data/sounds:/data/sounds - ./data/sounds:/data/sounds
restart: unless-stopped restart: unless-stopped

View file

@ -1,6 +1,6 @@
{ {
"name": "jukebox-vibe", "name": "jukebox-vibe",
"version": "1.0.0", "version": "1.1.1",
"description": "Discord Soundboard mit Web-Interface", "description": "Discord Soundboard mit Web-Interface",
"private": true, "private": true,
"scripts": { "scripts": {

View file

@ -1,6 +1,6 @@
{ {
"name": "discord-soundboard-server", "name": "discord-soundboard-server",
"version": "0.1.0", "version": "1.1.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View file

@ -941,6 +941,8 @@ app.post('/api/volume', (req: Request, res: Response) => {
// Kein aktiver Player: nur persistieren für nächste Wiedergabe // Kein aktiver Player: nur persistieren für nächste Wiedergabe
persistedState.volumes[guildId] = safeVolume; persistedState.volumes[guildId] = safeVolume;
writePersistedState(persistedState); writePersistedState(persistedState);
// Broadcast neue Lautstärke an alle Clients
sseBroadcast({ type: 'volume', guildId, volume: safeVolume });
return res.json({ ok: true, volume: safeVolume, persistedOnly: true }); return res.json({ ok: true, volume: safeVolume, persistedOnly: true });
} }
state.currentVolume = safeVolume; state.currentVolume = safeVolume;
@ -950,6 +952,8 @@ app.post('/api/volume', (req: Request, res: Response) => {
} }
persistedState.volumes[guildId] = safeVolume; persistedState.volumes[guildId] = safeVolume;
writePersistedState(persistedState); writePersistedState(persistedState);
// Broadcast neue Lautstärke an alle Clients
sseBroadcast({ type: 'volume', guildId, volume: safeVolume });
return res.json({ ok: true, volume: safeVolume }); return res.json({ ok: true, volume: safeVolume });
} catch (e: any) { } catch (e: any) {
console.error('Volume-Fehler:', e); console.error('Volume-Fehler:', e);
@ -1075,7 +1079,7 @@ app.get('/api/events', (req: Request, res: Response) => {
// Snapshot senden // Snapshot senden
try { try {
res.write(`data: ${JSON.stringify({ type: 'snapshot', party: Array.from(partyActive), selected: persistedState.selectedChannels ?? {} })}\n\n`); res.write(`data: ${JSON.stringify({ type: 'snapshot', party: Array.from(partyActive), selected: persistedState.selectedChannels ?? {}, volumes: persistedState.volumes ?? {} })}\n\n`);
} catch {} } catch {}
// Ping, damit Proxies die Verbindung offen halten // Ping, damit Proxies die Verbindung offen halten

View file

@ -1,7 +1,7 @@
{ {
"name": "discord-soundboard-web", "name": "discord-soundboard-web",
"private": true, "private": true,
"version": "0.1.0", "version": "1.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View file

@ -106,6 +106,15 @@ export default function App() {
setSelected(newVal); setSelected(newVal);
} }
} catch {} } catch {}
try {
const vols = msg?.volumes || {};
const cur = selectedRef.current || '';
const gid = cur ? cur.split(':')[0] : '';
if (gid && typeof vols[gid] === 'number') {
const v = vols[gid];
setVolume(v);
}
} catch {}
} else if (msg?.type === 'channel') { } else if (msg?.type === 'channel') {
try { try {
const gid = msg.guildId; const gid = msg.guildId;
@ -116,6 +125,16 @@ export default function App() {
if (curGid === gid) setSelected(`${gid}:${cid}`); if (curGid === gid) setSelected(`${gid}:${cid}`);
} }
} catch {} } catch {}
} else if (msg?.type === 'volume') {
try {
const gid = msg.guildId;
const v = msg.volume;
const cur = selectedRef.current || '';
const curGid = cur ? cur.split(':')[0] : '';
if (gid && curGid === gid && typeof v === 'number') {
setVolume(v);
}
} catch {}
} }
}); });
return () => { try { unsub(); } catch {} }; return () => { try { unsub(); } catch {} };
@ -653,15 +672,7 @@ export default function App() {
); );
})} })}
</main> </main>
{/* Footer: Version/Channel */} {/* Footer intentionally left without version display */}
<footer className="footer-info">
<span>
v{import.meta.env.VITE_APP_VERSION || ''}
{import.meta.env.VITE_BUILD_CHANNEL === 'nightly' && (
<span className="ml-2"> Nightly</span>
)}
</span>
</footer>
</div> </div>
{showTop && ( {showTop && (
<button type="button" className="back-to-top" aria-label="Nach oben" onClick={()=>window.scrollTo({top:0, behavior:'smooth'})}> Top</button> <button type="button" className="back-to-top" aria-label="Nach oben" onClick={()=>window.scrollTo({top:0, behavior:'smooth'})}> Top</button>

View file

@ -1,6 +1,6 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap');
:root { color-scheme: dark; } :root { color-scheme: dark; --range-track-h: 8px; --range-thumb-d: 20px; }
* { box-sizing: border-box; } * { box-sizing: border-box; }
[data-theme="dark"] body, [data-theme="dark"] body,
body { body {
@ -476,12 +476,13 @@ header p {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
width: 100%; width: 100%;
height: 8px; height: var(--range-track-h, 8px);
border-radius: 5px; border-radius: 5px;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
/* Sichtbarer Füllbalken über ein Hintergrund-Gradient, Breite via --_fill gesteuert */ /* Sichtbarer Füllbalken über ein Hintergrund-Gradient, Breite via --_fill gesteuert */
background: linear-gradient(var(--range-accent), var(--range-accent)) 0/var(--_fill, 0%) 100% no-repeat, var(--range-track-bg, #2c2c2c); background: linear-gradient(var(--range-accent), var(--range-accent)) 0/var(--_fill, 0%) 100% no-repeat, var(--range-track-bg, #2c2c2c);
vertical-align: middle;
} }
/* Tracks transparent halten, damit der Hintergrund-Gradient sichtbar ist */ /* Tracks transparent halten, damit der Hintergrund-Gradient sichtbar ist */
@ -496,8 +497,9 @@ header p {
.volume-slider::-webkit-slider-thumb { .volume-slider::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
width: 20px; width: var(--range-thumb-d, 20px);
height: 20px; height: var(--range-thumb-d, 20px);
margin-top: calc((var(--range-track-h, 8px) - var(--range-thumb-d, 20px)) / 2);
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
border: none; border: none;
@ -521,7 +523,7 @@ header p {
--range-track-bg: #2c2c2c; --range-track-bg: #2c2c2c;
accent-color: #0a84ff; accent-color: #0a84ff;
border-radius: 5px; border-radius: 5px;
height: 8px; height: var(--range-track-h, 8px);
} }
[data-theme="dark"] .control.volume input[type="range"]::-webkit-slider-thumb, [data-theme="dark"] .control.volume input[type="range"]::-webkit-slider-thumb,
@ -543,7 +545,7 @@ header p {
--range-track-bg: rgba(44,44,44,.8); --range-track-bg: rgba(44,44,44,.8);
accent-color: #23a6d5; accent-color: #23a6d5;
border-radius: 5px; border-radius: 5px;
height: 8px; height: var(--range-track-h, 8px);
border: 1px solid #3a3a3c; border: 1px solid #3a3a3c;
} }