kyuing-bot is a Discord TTS bot and web dashboard. It reads messages from configured text channels into Discord voice channels, supports Google Cloud Text-to-Speech by default, and can run multiple Discord bot tokens from one server/dashboard.
A fresh installation starts one main bot process and the web dashboard:
Docker container: kyuing-bot
└─ main process: python bot.py
├─ Discord bot #1 from DISCORD_TOKEN
└─ Web dashboard on WEB_PORT
Additional bots added in the dashboard are stored in SQLite and started as worker subprocesses inside the same container:
Docker container: kyuing-bot
├─ main process
│ ├─ Discord bot #1
│ ├─ Web dashboard
│ └─ BotProcessManager
└─ worker process
└─ python bot.py --worker --bot-id <BOT_ID> --no-kill-existing
Bot tokens are not passed as command-line arguments. Workers receive only --bot-id and read their token from the database, avoiding token exposure in process listings.
- Read messages from configured text channels into a voice channel
- Google TTS as the default TTS engine (
ko-KR-Standard-A) - Optional Supertonic-2 engine support
- Per-user TTS preferences through slash commands
- Per-bot TTS channels, keyword replacements, pronunciation rules, usage stats, and dashboard metrics
- Multi-bot management from one dashboard: add, start, stop, restart, enable, and disable bots
- Discord OAuth login for the admin dashboard
- Daily usage snapshots and rotating application logs
/join: summon the bot to your current voice channel/leave: disconnect the bot from the voice channel/stop: stop current playback/setchannel: register the current text channel as a TTS channel/unsetchannel: remove TTS from the current text channel/channels: list registered TTS channels in the current guild/engine: change your TTS engine/voice: choose your voice for the selected engine/speed: set playback speed/lang: set language for Supertonic/quality: set Supertonic synthesis quality/settings: view your current TTS preferences/voices: list available voices for your selected engine/pronounce: preview keyword/pronunciation replacement/usage: view Google TTS monthly character usage
- Docker and Docker Compose plugin
- Python 3.11+ if running without Docker
- FFmpeg, included in the Docker image
- For Google TTS: Google Cloud Text-to-Speech API key
- RAM: 1 GB+ is usually enough for Google TTS; 4 GB+ recommended if using Supertonic-2 because the model is loaded locally
After cloning this repository, you only have code and deployment files.
Included:
bot.py
database.py
tts_engine.py
tts_engines/
cogs/
web/
tests/
Dockerfile
docker-compose.yml
.env.example
README.md
Not included:
.env
data/bot.db
logs/app.log
Discord bot tokens
Google TTS API keys
Existing bot registrations
Existing dashboard data / TTS channels / keyword rules / usage stats
On first startup, SQLite creates data/bot.db and seeds one default bot record using DISCORD_TOKEN from .env:
bot_id=1
name=Default Bot
token=<DISCORD_TOKEN from .env>
enabled=1
Additional bots must be added later from the dashboard.
- Open the Discord Developer Portal.
- Create a new application.
- Go to Bot and create/reset a bot token.
- Enable Message Content Intent. This bot reads channel messages for TTS, so this intent is required.
- Copy the bot token into
.envasDISCORD_TOKEN.
In the same Discord application:
- Go to OAuth2.
- Copy the Client ID and Client Secret into
.env. - Add a redirect URI that exactly matches
DISCORD_REDIRECT_URI.
Local example:
http://localhost:5001/callback
Production HTTPS example:
https://your-domain.example/callback
Use OAuth2 URL Generator or construct an invite with these scopes:
bot
applications.commands
Recommended permissions:
View Channels
Send Messages
Read Message History
Connect
Speak
Use Voice Activity
A commonly used permission integer for these permissions is:
36768768
Google TTS is the default engine. To use it:
- Create or select a Google Cloud project.
- Enable Cloud Text-to-Speech API.
- Create an API key.
- Put it in
.envasGOOGLE_TTS_API_KEY.
Google TTS input is limited by the Google API request limit:
5,000 UTF-8 bytes per request
That is roughly:
- 5,000 ASCII characters
- about 1,666 Korean characters, because Korean characters are usually 3 bytes in UTF-8
git clone https://github.com/KHR0907/kyuing-bot.git
cd kyuing-botcp .env.example .envEdit .env and fill in real values.
Local development example:
DISCORD_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_discord_client_id
DISCORD_CLIENT_SECRET=your_discord_client_secret
DISCORD_REDIRECT_URI=http://localhost:5001/callback
DASHBOARD_ADMIN_IDS=your_discord_user_id
WEB_SECRET_KEY=replace_with_a_long_random_string
WEB_PORT=5001
DATABASE_PATH=data/bot.db
DAILY_STATS_RETENTION_DAYS=365
LOG_PATH=logs/app.log
LOG_RETENTION_DAYS=30
SESSION_COOKIE_SECURE=false
SESSION_COOKIE_SAMESITE=Lax
GOOGLE_TTS_API_KEY=your_google_tts_api_keyProduction HTTPS example differences:
DISCORD_REDIRECT_URI=https://your-domain.example/callback
SESSION_COOKIE_SECURE=truemkdir -p data logsdocker compose up -d --builddocker compose ps
docker compose logs -f appOpen the dashboard:
http://localhost:5001/
or, on a server:
http://<server-ip>:5001/
If you use a reverse proxy, point it to 127.0.0.1:5001.
DISCORD_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_discord_client_id
DISCORD_CLIENT_SECRET=your_discord_client_secret
DISCORD_REDIRECT_URI=https://your-domain.example/callback
DASHBOARD_ADMIN_IDS=123456789012345678,234567890123456789
WEB_SECRET_KEY=replace_with_a_long_random_string
WEB_PORT=5001
DATABASE_PATH=data/bot.db
DAILY_STATS_RETENTION_DAYS=365
LOG_PATH=logs/app.log
LOG_RETENTION_DAYS=30
SESSION_COOKIE_SECURE=true
SESSION_COOKIE_SAMESITE=Lax
GOOGLE_TTS_API_KEY=your_google_tts_api_keyDISCORD_TOKEN: token for the first/default Discord bot.DISCORD_CLIENT_ID: Discord OAuth client ID for dashboard login.DISCORD_CLIENT_SECRET: Discord OAuth client secret for dashboard login.DISCORD_REDIRECT_URI: OAuth callback URL registered in the Discord Developer Portal.DASHBOARD_ADMIN_IDS: comma-separated Discord user IDs that are allowed to access the dashboard by default.WEB_SECRET_KEY: secret key for signing web sessions. Use a long random value in production.WEB_PORT: dashboard port. Docker Compose maps hostWEB_PORTto containerWEB_PORT.DATABASE_PATH: SQLite database path inside the container. With the default Compose file,data/bot.dbpersists to./data/bot.dbon the host.DAILY_STATS_RETENTION_DAYS: daily statistics retention period.LOG_PATH: application log path inside the container. With the default Compose file,logs/app.logpersists to./logs/app.logon the host.LOG_RETENTION_DAYS: application log retention period.SESSION_COOKIE_SECURE: settruebehind HTTPS, setfalsefor local HTTP testing.SESSION_COOKIE_SAMESITE: session cookie SameSite value.Laxis the default.GOOGLE_TTS_API_KEY: Google Cloud Text-to-Speech API key.
After the first bot and dashboard are running:
- Create another Discord application/bot in the Discord Developer Portal.
- Enable Message Content Intent for the new bot.
- Invite the new bot to the desired server with
botandapplications.commandsscopes. - Log in to the dashboard.
- Open the bot management section.
- Enter the new bot name and bot token.
- The dashboard validates the token with Discord, stores it in SQLite, and starts a worker process.
The new bot will have separate bot_id-scoped settings:
TTS channels
keyword replacements
pronunciation rules
usage statistics
dashboard metrics
user settings
On container restart, enabled bots are automatically started again by BotProcessManager.
Restart:
docker compose restart appUpdate from git:
git pull --ff-only
docker compose build
docker compose up -dView logs:
docker compose logs -f appCheck container status:
docker compose psRun tests locally:
python -m pytest tests/ -qThe default Compose file persists these directories on the host:
./data -> /app/data
./logs -> /app/logs
The important file is:
./data/bot.db
Back it up before migrations or major updates:
mkdir -p data/backups
cp data/bot.db "data/backups/bot.db.backup-$(date +%Y%m%d-%H%M%S)"The app requires DISCORD_TOKEN for the initial bot. Create .env from .env.example and fill in the token.
Check that DISCORD_REDIRECT_URI exactly matches the Redirect URI registered in Discord Developer Portal, including protocol, domain, port, and path.
For HTTPS production deployments, use:
SESSION_COOKIE_SECURE=trueFor local HTTP testing, use:
SESSION_COOKIE_SECURE=falseCheck that:
GOOGLE_TTS_API_KEYis set- Cloud Text-to-Speech API is enabled in Google Cloud
- The API key is allowed to call Text-to-Speech
- Your text does not exceed 5,000 UTF-8 bytes
The Docker container runs as a non-root appuser. If mounted directories are not writable:
mkdir -p data logs
chmod -R u+rwX,g+rwX data logsIf needed, adjust ownership for your server setup.
- Never commit
.env. - Never paste bot tokens or API keys into issues, logs, or screenshots.
- Bot tokens are stored in SQLite for multi-bot operation; protect
data/bot.dblike a secret. - Use HTTPS and a strong
WEB_SECRET_KEYin production.