A Twitch chat bot for requesting and playing music directly in your stream via chat commands.
- 🎵 Song Requests — Type
!sr <song name>in chat to add songs to a queue - 📻 YouTube Music Downloading — Downloads music from YouTube (Music category only, max 15 min)
- 🔊 Audio Playback — Plays songs in real-time using Pygame
- 🗳️ Democratic Skip — 3 different users can vote
!voteto skip the current song - 📋 Persistent Queue — Queue survives bot restarts (saved to JSON with atomic writes)
- 🎲 Random Playback — Plays random songs from your downloads folder when the queue is empty
- 📊 Analytics — Tracks requests, plays, top songs, and top users
- 📁 Playlists — Create and manage custom playlists with
!pumpfor random batch queuing - 👑 Admin Commands — Blacklist users, trusted channel whitelist, manage queue, skip songs, control volume
- 🛡️ Trusted Channel Whitelist — Restrict downloads to specific YouTube channels (opt-in via
CHANNEL_WHITELIST_ENABLED) - 🔁 Repeat Modes — Off, count-based, or infinite repeat
- 💾 Local File Matching — Word-boundary matching on disk avoids re-downloading known songs
- 🏷️ Duplicate Detection — LRU cache (100 entries) prevents redundant downloads
git clone https://github.com/dilidin2/melos.git
cd melosFollow the installation instructions at: https://docs.astral.sh/uv/getting-started/installation/
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv syncCreate a .env file in the project root:
TARGET_CHANNEL=your_twitch_channel
ADMIN_USERS=admin1,admin2
LOG_LEVEL=INFO
LOG_FILE=twitch_sr.log
DOWNLOAD_DIR=./downloads
The bot uses a built-in Client ID for OAuth — no need to register your own app on Twitch.
Note: No Client Secret is needed — the bot uses Device Code Flow for OAuth authentication.
By default, the bot downloads from any YouTube channel in the "Music" category. To restrict downloads to specific channels only:
CHANNEL_WHITELIST_ENABLED=true
When enabled, only videos from trusted channels are downloaded. The seed file data/trusted_channels.example.json ships with one pre-configured channel (NoCopyrightSounds). On first run, it is automatically copied to data/trusted_channels.json. Add more channels with !trust_channel <query_or_url>.
If you enable the whitelist but add no channels, no video will pass validation. Use
!trusted_channelsto see the current list.
On first run (or after deleting tokens.json), the bot will print a URL and a code in the console:
Twitch Device Code Flow — go to https://www.twitch.tv/activate and enter code: ABCD-1234
Open the URL in your browser logged in with your main Twitch account, enter the code, and authorize. The bot will authenticate as you and join the channel specified by TARGET_CHANNEL.
If you want the bot to appear as a distinct user in chat:
- Create a separate Twitch account (e.g.,
melos-bot) - Log into your browser with that account
- Open the URL printed by the bot and enter the code
- Authorize — the bot will now authenticate as the bot account
- It will join the channel specified by
TARGET_CHANNEL(your streaming channel)
In both cases, tokens are saved to tokens.json and auto-refreshed every ~4 hours.
Key point: The bot always joins the channel set in
TARGET_CHANNEL, regardless of which account authenticates. The account you log in with is only the identity the bot uses in chat.
python run.pyThe bot will connect to your Twitch channel and start listening for commands. Press Ctrl+C for a graceful shutdown (drains queue, persists state).
| Command | Description |
|---|---|
!sr <query> |
Request a song (title or YouTube URL) |
!random [N] |
Play N random songs from library |
!vote |
Vote to skip the current song (needs 3 different users) |
!duration <song> |
Show song duration |
!when |
Estimate when your next song will play |
!next |
Show the next song in the queue |
!queue_info |
Show queue stats (count, estimated duration, repeat status) |
!ops |
Remove the last song you added from the queue |
| Command | Description |
|---|---|
!volume <0-100> |
Set volume level |
!pause |
Pause playback |
!resume |
Resume playback |
!repeat [N] |
Repeat current song N times (no arg = infinite) |
!repeat_off |
Disable repeat mode (admin only) |
| Command | Description |
|---|---|
!create <name> |
Create a new playlist |
!playlist [name] [page] |
List your playlists or show songs in one |
!add <playlist> <song> |
Add a song to a playlist (matches local files) |
!remove <playlist> <pos/name> |
Remove a song by position or name |
!move <playlist> <p1> <p2> |
Swap two songs in a playlist |
!clear <playlist> |
Clear all songs from a playlist |
!pump <playlist> [N] |
Queue N random songs from a playlist (sliding window) |
| Command | Description |
|---|---|
!blacklist <user> |
Blacklist a user (blocks song requests) |
!unblacklist <user> |
Remove a user from blacklist |
!blacklist_list |
Show all blacklisted users |
!trust_channel <query_or_url> |
Add a YouTube channel to the trusted whitelist (resolves from URL or search) |
!untrust_channel <channel_id> |
Remove a channel from the trusted whitelist |
!trusted_channels |
List all trusted channels with names and IDs |
!skip |
Skip the current song |
!remove_queue <pos> |
Remove a specific position from the queue |
!clear_queue |
Clear the entire queue |
Admin users are configured via the ADMIN_USERS environment variable (comma-separated usernames).
This feature restricts song downloads to videos from whitelisted YouTube channels. It is disabled by default — set CHANNEL_WHITELIST_ENABLED=true in .env to activate.
- Seed file:
data/trusted_channels.example.jsonships with NoCopyrightSounds (NCS) pre-configured. On first run, it's copied to the runtime filedata/trusted_channels.json. - Add channels: Admins use
!trust_channel <video_url>or!trust_channel <channel_name>— the bot resolves the channel ID automatically via YouTube search. - Remove channels:
!untrust_channel <channel_id>(exact ID, case-sensitive). - View list:
!trusted_channelsshows all trusted channels with readable names and IDs. - Error feedback: If all search results come from untrusted channels, the user gets a clear chat message: "None of the search results come from a trusted channel. Use !trusted_channels to see authorized channels."
| Command | Description |
|---|---|
!stats |
Show general bot statistics (total, today, this week) |
!top_songs [N] |
Show top N most requested songs |
!top_users [N] |
Show top N most active users |
!history [N] |
Show last N songs played |
| Command | Description |
|---|---|
!help [cmd] |
Show help or details for a specific command |
!version |
Show bot version |
!uptime |
Show how long the bot has been running |
melos/
├── run.py # Entry point — wires all services, starts bot
├── main/ # Core bot logic and services
│ ├── config.py # All configuration (Twitch, audio, queue, commands)
│ ├── main_core.py # SongRequestBot, RequestQueue, DuplicateCache
│ ├── twitch_handler.py # Twitch IRC connection + OAuth flow
│ ├── chat_message.py # Chat message formatting utilities
│ ├── music_downloader.py # YouTube download (yt-dlp), local file matching
│ ├── audio_player.py # Pygame mixer playback
│ ├── playlist_manager.py # User playlist CRUD + pompa feature
│ ├── admin_handler.py # Blacklist/whitelist management
│ ├── analytics.py # Request/play tracking + statistics
│ ├── command_processor.py # Regex command router + permission checks
│ ├── services.py # DI container (CommandServices dataclass)
│ └── queue_persistence.py # Atomic JSON save/load for the queue
├── commands/ # Command handler classes (one per domain)
│ ├── music.py # Playback control commands
│ ├── playlists.py # Playlist management commands
│ ├── admin.py # Admin/moderation commands
│ ├── analytics.py # Statistics commands
│ └── utility.py # Help, version, uptime, queue_info
├── utils/ # Shared utilities
│ └── logger.py # Loguru setup (console + file with rotation)
├── data/ # Persistent data (mostly gitignored)
│ ├── trusted_channels.example.json # Seed file (tracked in git, NCS pre-configured)
│ ├── queue.json # Persisted request queue
│ ├── blacklist.json # Blacklisted users
│ ├── whitelist.json # Whitelisted users
│ ├── trusted_channels.json # Trusted YouTube channels (runtime, copied from example)
│ ├── stats.json # Analytics data
│ └── playlists/ # Per-user playlist files
├── downloads/ # Downloaded audio files (gitignored)
├── tests/ # Pytest test suite (~20 modules)
├── AGENT/ # Project index for AI agents (tracked in git)
│ ├── PI.md # Root index with tree + section navigation
│ └── sections/ # One .md per logical section with data flows
├── pyproject.toml # Project metadata, dependencies, tool configs
├── .env # Environment variables (gitignored)
└── tokens.json # Cached Twitch OAuth tokens (gitignored)
- Dependency Injection: All services are wired explicitly via
CommandServicesdataclass inrun.py. No global state or implicit coupling. - Thread Safety:
RequestQueueuses asyncio.Lock for logical race protection at await boundaries. Audio playback runs in aThreadPoolExecutorto avoid blocking the event loop. - Local File Matching: Word-boundary regex (
\bword\b) on disk filenames avoids false positives. All query words must match as whole words. - Atomic Persistence: Queue saves use temp file +
os.replace()for crash safety. - Command Routing: Precompiled regex patterns with dynamic admin permission discovery via
@admin_requireddecorator. - Channel Whitelist Bootstrap: On first run,
data/trusted_channels.example.json(tracked in git) is copied todata/trusted_channels.json. This guarantees every clone starts with at least one trusted channel (NCS) — no manual setup needed. The bootstrap runs once: if the runtime file already exists, it is never overwritten. - Error Granularity: Download failures are classified into specific exceptions (
_SongTooLongError,_CategoryError,_NoValidCandidateError,_ChannelNotTrustedError) and surfaced to chat with actionable messages.
| Package | Purpose |
|---|---|
| twitchAPI | Twitch IRC chat integration (auth, messaging, commands) |
| yt-dlp | YouTube music downloading + metadata extraction |
| pygame | Audio playback (mixer module) |
| python-dotenv | Environment variable loading from .env |
| loguru | Structured logging with rotation and compression |
python -m pytest # Run all tests
python -m pytest -v # Verbose output
python -m pytest -k "queue" # Keyword filter
python -m pytest -m "not slow" # Exclude slow testsTest markers: slow, integration, unit. Pytest config in pyproject.toml (asyncio_mode: auto).
Ruff is configured in pyproject.toml (target: py310, line length: 100, rules: E, F, I, W).
MIT
