Skip to content

dilidin2/melos

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

melos

A Twitch chat bot for requesting and playing music directly in your stream via chat commands.

melos

Features

  • 🎵 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 !vote to 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 !pump for 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

Installation

1. Clone the repository

git clone https://github.com/dilidin2/melos.git
cd melos

2. Install uv (Python package manager)

Follow the installation instructions at: https://docs.astral.sh/uv/getting-started/installation/

3. Create a virtual environment and install dependencies

uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
uv sync

4. Configure environment variables

Create 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.

Channel whitelist (optional)

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_channels to see the current list.

5. Authenticate the bot

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

Option A — Use your streaming account (simplest)

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.

Option B — Use a separate bot account (recommended for streams)

If you want the bot to appear as a distinct user in chat:

  1. Create a separate Twitch account (e.g., melos-bot)
  2. Log into your browser with that account
  3. Open the URL printed by the bot and enter the code
  4. Authorize — the bot will now authenticate as the bot account
  5. 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.

Usage

python run.py

The bot will connect to your Twitch channel and start listening for commands. Press Ctrl+C for a graceful shutdown (drains queue, persists state).

Commands

Music Requests & Playback

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

Playback Control (Admin)

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)

Playlists

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)

Admin Commands

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).

Trusted Channel Whitelist

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.json ships with NoCopyrightSounds (NCS) pre-configured. On first run, it's copied to the runtime file data/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_channels shows 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."

Analytics

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

Utility

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

Architecture

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)

Key Design Decisions

  • Dependency Injection: All services are wired explicitly via CommandServices dataclass in run.py. No global state or implicit coupling.
  • Thread Safety: RequestQueue uses asyncio.Lock for logical race protection at await boundaries. Audio playback runs in a ThreadPoolExecutor to 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_required decorator.
  • Channel Whitelist Bootstrap: On first run, data/trusted_channels.example.json (tracked in git) is copied to data/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.

Dependencies

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

Development

Running Tests

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 tests

Test markers: slow, integration, unit. Pytest config in pyproject.toml (asyncio_mode: auto).

Code Quality

Ruff is configured in pyproject.toml (target: py310, line length: 100, rules: E, F, I, W).

License

MIT

About

A local service to manage the Twitch song request queue

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages