15 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Twitch Drops Miner is a Python application that automatically mines timed Twitch drops without downloading stream data. It uses Twitch's GraphQL API and websocket connections to simulate watching streams while tracking drop progress.
Key Characteristics:
- Python 3.10+ required
- Web-based GUI using FastAPI and Socket.IO
- Async/await architecture with asyncio
- Session persistence via cookies
- No stream video/audio download (bandwidth-efficient)
- Docker-ready for easy deployment
Development Commands
IMPORTANT: Always activate the virtual environment first!
The project uses a virtual environment located at env/. All Python commands must be run within this environment:
# Activate the virtual environment (required before any Python commands)
source env/bin/activate
Running the Application
# Run from source (remember to activate venv first!)
source env/bin/activate && python main.py
# With verbose logging (stackable: -vv, -vvv)
source env/bin/activate && python main.py -v
# Create data dump for debugging
source env/bin/activate && python main.py --dump
# Access the web interface at http://localhost:8080
Development Setup
The application requires:
- Python 3.10+
- Virtual environment at
env/(must be activated before running commands) - Dependencies from
pyproject.toml(includes FastAPI, uvicorn, Socket.IO)
Docker deployment:
# Build and run with docker-compose
docker-compose up -d
# Access at http://localhost:8080
Architecture
⚠️ IMPORTANT: The codebase has been refactored into a modular structure.
The application now uses a clean src/ package structure with clear separation of concerns.
Project Structure
src/
├── models/ # Domain models (Game, Channel, Campaign, Drop, Benefit)
├── config/ # Configuration (constants, paths, operations, settings, client_info)
├── utils/ # Pure utilities (string, JSON, async helpers, rate_limiter, backoff)
├── i18n/ # Translation system (Translator class, TypedDict schemas)
├── auth/ # Authentication (auth_state for OAuth and token management)
├── api/ # External API (HTTP client, GraphQL client)
├── websocket/ # Real-time updates (websocket connection, pool)
├── web/ # Web GUI (app, gui_manager, api/)
│ └── managers/ # Individual UI managers (status, console, channels, campaigns, inventory, login, settings, cache, broadcaster)
├── services/ # Business logic services (channel, inventory, watch, maintenance, message_handlers)
├── core/ # Core client (Twitch client)
├── exceptions.py # Custom exceptions
├── version.py # Version string
└── __main__.py # Entry point (replaces old main.py)
lang/ # Translation JSON files (19 languages)
├── English.json # Default/fallback translations
├── Español.json
├── Français.json
├── Deutsch.json
└── ... # 15 more languages
Core Components
main.py - Simple launcher:
- Runs the
srcpackage as a module usingrunpy.run_module("src") - All application logic is now in
src/__main__.py
src/main.py - Entry point:
- Parses command-line arguments
- Initializes Settings, Twitch client, and WebGUIManager
- Starts the FastAPI web server (uvicorn on port 8080)
- Runs the main asyncio event loop
- Handles signals (SIGINT, SIGTERM on Linux) and exit codes
src/core/client.py - Central client (Twitch class):
- State machine: IDLE, INVENTORY_FETCH, GAMES_UPDATE, CHANNELS_CLEANUP, CHANNELS_FETCH, CHANNEL_SWITCH, EXIT
- Composes
_AuthState,HTTPClient, andGQLClient - Delegates to service layer for business logic
- Drop progress monitoring via periodic "watch" payloads
- Manages WebsocketPool and maintenance tasks
src/services/ - Business logic layer (fully implemented):
ChannelService: Channel management and selection logicInventoryService: Campaign and drop inventory operationsWatchService: Drop mining watch payload logicMaintenanceService: Periodic maintenance tasksMessageHandlerService: Websocket message routing and handling
Note: The services layer is complete and handles all business logic that was previously in the monolithic client file.
src/models/channel.py - Channel and Stream:
Channelclass: Twitch channel with online/offline statusStreamclass: Active stream with game, viewers, drop status- Stream URL fetching and validation
- ACL-based vs directory channels
src/models/campaign.py - Drop campaigns:
DropsCampaign: Campaign with game, timeframe, allowed channels- Time-based eligibility and progress tracking
src/models/drop.py - Drop types:
TimedDrop: Drops with minute requirements and progressBaseDrop: Base class with claim logic- Precondition chains for sequential drops
src/web/gui_manager.py - Web GUI:
WebGUIManager: Main GUI coordinator- Composes individual managers for different UI concerns (status, console, channels, campaigns, inventory, login, settings, cache)
- Uses
WebSocketBroadcasterfor real-time Socket.IO updates - Pure asyncio, no tkinter dependency
src/web/app.py - FastAPI application:
- REST API endpoints:
/api/status,/api/channels,/api/campaigns,/api/settings,/api/login,/api/oauth/confirm,/api/reload,/api/close - Socket.IO server for real-time bi-directional communication
- Serves static web frontend from
web/directory - Integrates with WebGUIManager via
set_managers()
src/websocket/pool.py - WebSocket management:
- Sharded connections (up to 50 topics per socket, max 199 channels)
- Topics: User.Drops, User.Notifications, Channel.StreamState, Channel.StreamUpdate
- Automatic reconnection with exponential backoff
- Message routing to registered callbacks
src/config/settings.py - Application settings:
- Games to watch list (auto-populated from available campaigns if empty)
- Connection quality multiplier
- Language selection
- Proxy support
- Logging and dump flags from command-line arguments
- Persistence to JSON file (
settings.json) in DATA_DIR
State Machine Flow
- IDLE - Waiting for campaigns or user action
- INVENTORY_FETCH - Fetch campaigns from GraphQL, claim completed drops
- GAMES_UPDATE - Determine wanted games based on priority/exclude lists
- CHANNELS_CLEANUP - Remove channels not streaming wanted games
- CHANNELS_FETCH - Discover channels via ACL lists or game directories
- CHANNEL_SWITCH - Select best channel to watch based on priority/ACL
- Loop between CHANNEL_SWITCH and periodic INVENTORY_FETCH (hourly)
Authentication
- Uses OAuth device code flow (user enters code at twitch.tv/activate)
- Managed by
src/auth/auth_state.py(_AuthStateclass) - Access tokens stored in
cookies.jarin DATA_DIR - Device ID from Twitch's
unique_idcookie - Session ID generated per run
- Client info defined in
src/config/client_info.py(presents as Android app with Client-Id and User-Agent spoofing)
Drop Mining Mechanism
The application sends periodic "watch" payloads to a spade URL every ~20 seconds:
- Payload contains minute-watched events with channel/broadcast IDs
- Twitch reports progress via websocket (User.Drops topic)
- If websocket updates stop, fallback to GQL CurrentDrop query
- Extrapolation via "bump minutes" when no updates received
GraphQL Operations
Defined in src/config/operations.py as GQL_OPERATIONS:
- Inventory - Fetch in-progress campaigns and claimed benefits
- Campaigns - List available active/upcoming campaigns
- CampaignDetails - Detailed drop info for a campaign
- GameDirectory - Find live streams for a game with drops enabled
- GetStreamInfo - Check if channel is online and get stream details
- CurrentDrop - Query currently mined drop progress
- ClaimDrop - Claim a completed drop
- AvailableDrops - Check which campaigns a channel qualifies for (badge validation)
- NotificationsDelete - Delete Twitch notifications
Channel Selection Priority
- Selected channel (if user clicked one)
- ACL-based channels over directory channels
- Game priority order (from settings)
- Viewer count (descending)
- Maximum 199 channels tracked simultaneously
Maintenance Task
Runs in background to trigger:
- Channel cleanup when drops start/end (based on time_triggers)
- Inventory reload every ~60 minutes
Translation System
Architecture:
- All translations stored as JSON files in
lang/directory (19 languages supported) - English (
lang/English.json) is the single source of truth and fallback language - Strongly typed with TypedDict schema defined in
src/i18n/translator.py - Translator class (
src/i18n/translator.py) handles language loading and fallback - Singleton instance
_available viafrom src.i18n import _
Supported Languages:
- English, Dansk (Danish), Deutsch (German), Español (Spanish), Français (French)
- Indonesian, Italiano (Italian), Nederlandse (Dutch), Polski (Polish), Português (Portuguese)
- Română (Romanian), Türkçe (Turkish), Čeština (Czech)
- Русский (Russian), Українська (Ukrainian), العربية (Arabic)
- 日本語 (Japanese), 简体中文 (Simplified Chinese), 繁體中文 (Traditional Chinese)
Translation Structure:
Translation = {
"language_name": str, # Display name of language
"english_name": str, # English name of language
"status": StatusMessages, # Console status messages
"login": LoginMessages, # Login-related messages
"error": ErrorMessages, # Error messages
"gui": GUIMessages # All web GUI text (tabs, settings, help, etc.)
}
Usage:
from src.i18n import _
# Access translations
status_text = _.t["gui"]["status"]["idle"] # Returns "Idle"
login_text = _.t["login"]["status"]["logged_in"] # Returns "Logged in"
Language Persistence:
- Language selection persisted in
settings.json(DATA_DIR) - Dynamic language switching supported in web GUI
- Changes take effect immediately without restart
Key Files
- src/config/constants.py - Core enums (State, WebsocketTopic), logging config, type aliases
- src/config/operations.py - GraphQL operation definitions (GQL_OPERATIONS)
- src/config/paths.py - Path management and Docker environment detection
- src/config/client_info.py - Twitch client info (Client-Id, User-Agent)
- src/config/settings.py - Application settings with JSON persistence
- src/exceptions.py - Custom exceptions (LoginException, CaptchaRequired, ExitRequest, etc.)
- src/utils/ - Helper utilities (string_utils, json_utils, async_helpers, rate_limiter, backoff)
- src/i18n/ - Internationalization package with TypedDict schema and Translator class
- translator.py - Translator class with typed translation schema (Translation TypedDict)
- init.py - Exports translation types and
_(Translator instance)
- lang/ - Translation JSON files for 19 languages (English.json is the single source of truth)
- src/version.py - Version string
- src/web/app.py - FastAPI application with REST API and Socket.IO
- src/web/managers/cache.py - ImageCache for campaign artwork caching
- web/ - Frontend assets (index.html, static/app.js, static/styles.css)
Testing
The project does not include a test suite. Manual testing workflow:
- Run with
-vvvfor maximum verbosity (levels: -v, -vv, -vvv, -vvvv) - Use
--dumpto generate debug data dumps - Check log files in
./logs/directory - Use
--debug-wsfor websocket debug logging - Use
--debug-gqlfor GraphQL debug logging - Monitor web GUI console output and browser developer tools
Web GUI Architecture
The application uses a web-based interface accessible via browser:
Web GUI Components
src/web/gui_manager.py - WebGUIManager class:
- Managers: StatusManager, ConsoleOutputManager, ChannelListManager, CampaignProgressManager, InventoryManager, LoginFormManager, SettingsManager, CacheManager
- Uses WebSocketBroadcaster to push real-time updates to connected clients via Socket.IO
- Pure async/await implementation
src/web/app.py - FastAPI application:
- REST API endpoints:
/api/status,/api/channels,/api/campaigns,/api/console,/api/settings,/api/login,/api/oauth/confirm,/api/reload,/api/close - Socket.IO events for real-time bi-directional communication
- Serves static web frontend from
web/directory - Integrates with WebGUIManager via
set_managers(gui, twitch)
web/ - Frontend assets:
index.html- Single-page application layout with tabsstatic/app.js- Socket.IO client, real-time UI updates, API callsstatic/styles.css- Responsive design with dark mode support
Communication Protocol
Server → Client (Socket.IO events):
initial_state- Full state on connectstatus_update- Status bar changesconsole_output- New log lineschannel_add/update/remove- Channel list changesdrop_progress- Drop mining progresscampaign_add- New campaign addedlogin_required- Prompt for credentialssettings_updated- Settings changed
Client → Server:
- REST API for actions (login, settings, channel selection)
- Socket.IO for connection management
Docker Integration
src/config/paths.py:
- Detects Docker environment via
DOCKER_ENVenv var or/.dockerenvfile - Docker: Uses
/appfor code,/app/datafor persistent storage - Development: Uses
<project_root>/datafor persistent storage - All user data (cookies, settings, cache, logs) stored in DATA_DIR
- Provides
_resource_path()helper for locating bundled resources
Dockerfile:
- Based on
python:3 - Installs dependencies from
pyproject.toml - Exposes port 8080
- Health check on
/api/status
docker-compose.yml:
- Volume mounts
./data:/app/datafor persistence - Port mapping
8080:8080 - Auto-restart policy
- Timezone configuration
Key Design Decisions
- WebSocket for real-time - Socket.IO chosen for reliability (fallback to polling)
- Single-page app - Simpler than full framework (React/Vue), fast load times
- Direct Docker support - Environment detection, proper path handling
- OAuth device code flow - Works great for web-based deployment
Project Scope
Supported:
- ✅ Web GUI - browser-based interface
- ✅ Docker deployment - containerized for any platform
- ✅ Remote access - access from any device on network
- ✅ Headless operation - no display server required
NOT supported:
- Multi-account support
- Channel points mining
- Mining for unlinked campaigns
- Desktop GUI (removed in favor of web-only)