AFK mine timed Twitch drops without watching streams. Saves bandwidth by only fetching stream metadata.
- Stream-less mining — No video/audio download, only metadata
- Game priority list — Prioritize games with boxart icons
- Game exclude list — Exclude games from mining
- Auto channel switching — Automatically switches to the next best channel when the current one goes offline
- Manual channel cycling — The Switch button cycles through available channels in order
- Auto-claim — Claims drops automatically when ready
- 8 WebSocket connections — Track up to 199 channels simultaneously
- Web dashboard — Modern React UI with real-time updates
- Built-in FAQ page — Troubleshooting and help directly in the UI
- REST API — Integrate with other systems
- API Key authentication — Protect the REST API with a configurable key
- Rate limiting — 30 requests/minute/IP to prevent abuse
- 21 languages — Full translation support
- Tooltips — Contextual help on all settings
- Responsive mobile UI — Hamburger menu, drawer sidebar, optimized for phones
- Python 3.10+
- Node.js 18+ (for building frontend)
git clone https://github.com/ZoniBoy00/TwitchDropMiner.git
cd TwitchDropMiner
# Install Python dependencies
cd backend
pip install -r requirements.txt
cd ..
# Install and build frontend
cd frontend
npm install
npm run build
cd ..# Production mode (recommended)
# Windows:
start.bat
# Linux/Mac:
chmod +x start.sh && ./start.sh
# Or manually:
cd backend && python server.py --port 1337Open http://localhost:1337 in your browser.
npm run dev # Starts both backend + frontend with hot reloadTwitchDropMiner/
├── backend/ # Python server
│ ├── server.py # Entry point
│ ├── web_gui.py # WebSocket + REST API
│ ├── twitch.py # Core Twitch client
│ ├── channel.py # Channel management
│ ├── settings.py # Settings (JSON)
│ ├── constants.py # Configuration
│ ├── utils.py # Utilities
│ ├── translate.py # Translations (21 languages)
│ ├── websocket.py # WebSocket pool
│ ├── inventory.py # Drops & campaigns
│ ├── cache.py # Image cache
│ ├── exceptions.py # Exceptions
│ ├── registry.py # Windows registry
│ ├── version.py # Version info
│ ├── requirements.txt # Python deps
│ ├── LICENSE # MIT License
│ └── lang/ # 21 language files
├── frontend/ # React TypeScript
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ │ ├── GameIcon.tsx
│ │ │ ├── LoginOverlay.tsx
│ │ │ ├── ProgressBar.tsx
│ │ │ ├── StatusDot.tsx
│ │ │ ├── Tip.tsx
│ │ │ └── Toast.tsx
│ │ ├── layout/ # Layout components
│ │ │ ├── Sidebar.tsx
│ │ │ └── Topbar.tsx
│ │ ├── pages/ # Page components
│ │ │ ├── APIPage.tsx
│ │ │ ├── ChannelsPage.tsx
│ │ │ ├── Dashboard.tsx
│ │ │ ├── DropsPage.tsx
│ │ │ ├── FAQPage.tsx
│ │ │ ├── LogsPage.tsx
│ │ │ └── SettingsPage.tsx
│ │ ├── hooks/
│ │ │ └── useWebSocket.ts # WebSocket hook
│ │ ├── App.tsx # Root component with state management
│ │ ├── types.ts # TypeScript types
│ │ ├── main.tsx # Entry point
│ │ └── index.css # Tailwind styles
│ ├── public/favicon.ico # Twitch favicon
│ ├── package.json
│ ├── vite.config.ts
│ └── tailwind.config.js
├── web_static/ # Build output (auto-served)
├── start.bat # Windows launcher
├── start.sh # Linux/Mac launcher
├── package.json # Root npm scripts
├── .gitignore
├── LICENSE # MIT License
└── README.md
| Flag | Description |
|---|---|
--port PORT |
Web server port (default: 1337) |
-v |
Verbosity (-v info, -vv call, -vvv debug) |
--log |
Enable file logging to log.txt |
--debug-ws |
Debug websocket messages |
--debug-gql |
Debug GQL requests |
| Page | Description |
|---|---|
| Dashboard | Status cards, drop/campaign progress, watching channel, 8 WebSocket connections |
| Channels | Channel table with status, game, drops, viewers, ACL |
| Drops | Campaign inventory with game boxart icons, progress bars |
| API | API key management, authentication settings, endpoint reference with built-in testing |
| Settings | Proxy, language, priority mode, connection quality, priority/exclude lists, login status |
| Logs | Real-time filtered logs with timestamps and search |
| FAQ | Built-in troubleshooting guide and common questions |
🖼️ Screenshots (click to expand)
Dashboard — status cards, current drop, campaign progress, watching channel, WebSocket connections
Channels — live channel table with status, game, drops, viewers and ACL
Drops — campaign inventory with game icons, progress bars, drop status
Settings — proxy, language, priority/exclude lists, login status, connection quality
Logs — real-time filtered logs with timestamps, search and auto-scroll
FAQ — built-in troubleshooting guide with expandable questions
A new API page has been added to the web dashboard sidebar, providing:
- API Key Management — Set, view, copy, and test your API key from a dedicated page (moved from Settings)
- Endpoint Reference — Complete list of all REST API endpoints with methods, paths, and descriptions
- Built-in API Tester — Test the
/api/statusendpoint directly from the UI with the current API key - Authentication Guide — Example curl commands and rate limit information
When the currently watched channel goes offline, the miner automatically switches to the next best available channel. No more getting stuck in an idle state — mining continues seamlessly.
The Switch button now cycles through available channels in order (wrapping around). Useful when you want to force a specific channel or skip a stream.
Settings toggles (like "Enable Badges & Emotes") no longer revert after saving. The frontend now re-fetches settings from the server after each save, ensuring the UI stays in sync.
The logout functionality works reliably via both the sidebar button and the /api/logout endpoint (GET and POST). Cookies are cleared and the miner resets to a clean state.
A dedicated FAQ page is now part of the Web UI — no need to check the README for common issues. Access it from the sidebar.
Login status and user ID are now included in the initial WebSocket message, so the UI shows the correct state immediately on page load.
A configurable API key can be set in Settings to protect all /api/* endpoints. When enabled, every API request must include the X-API-Key header. The key is stored in settings.json and can be changed at any time from the Web UI.
All API endpoints are rate-limited to 30 requests per minute per IP to prevent abuse. Excessive requests return 429 Too Many Requests with a Retry-After header.
Note: API management is available through the API page in the web dashboard (sidebar). All
/api/*endpoints require theX-API-Keyheader if an API key is configured. Requests without a valid key return401 Unauthorized. Rate limiting (30 req/min/IP) applies to all/api/*routes.
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Web dashboard |
| GET | /ws |
WebSocket (live updates) |
| GET | /api/status |
Server status |
| GET | /api/settings |
Get settings |
| POST | /api/settings |
Update settings |
| POST | /api/login |
Submit login |
| POST | /api/code |
Confirm activation code |
| GET/POST | /api/logout |
Logout and clear session |
| POST | /api/restart |
Restart server |
| POST | /api/action/reload |
Reload inventory |
| POST | /api/action/switch |
Switch channel |
|
Server to Client:
{"type": "init", "status": "Idle", "channels": [], "campaigns": [], "games": {}, "uptime": "00:05:30", "login_status": "Logged out", "login_user_id": null, "api_key": ""}
{"type": "status", "text": "Watching: ChannelName"}
{"type": "log", "message": "14:32:05 [INFO] Drop claimed"}
{"type": "channels", "channels": [...]}
{"type": "drop", "active": true, "drop_name": "...", "rewards": "..."}
{"type": "settings_saved"}
{"type": "games_update", "games": {...}}
{"type": "toast", "message": "Settings saved", "style": "success"}Client to Server:
{"action": "reload"}
{"action": "switch"}
{"action": "restart"}
{"action": "logout"}
{"action": "login_submit", "username": "...", "password": "...", "token": "..."}
{"action": "save_settings", "priority": [...], "exclude": [...]}# /etc/systemd/system/twitch-drops.service
[Unit]
Description=Twitch Drops Miner
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/TwitchDropMiner/backend
ExecStart=/usr/bin/python3 server.py --port 1337
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable twitch-drops
sudo systemctl start twitch-dropsEnglish, Deutsch, Français, Español, Português, Italiano, Nederlands, Polski, Română, Türkiye, Čeština, Dansk, Norsk, Indonesian, Українська, Русский, العربية, 日本語, 简体中文, 繁體中文
A: This usually means no eligible campaigns were found. Try these:
- Go to Settings and enable "Enable Badges & Emotes" — many drops (including Tarkov) require this
- Make sure your game is in the Priority List
- Click Reload in the top bar to refresh campaigns
- Check Logs page for error messages
- Verify you're logged in (sidebar shows green "Logged in" status)
A: When the page loads, you'll see a LoginOverlay with the device activation code flow. Enter the code at twitch.tv/activate to log in.
Note: The backend also supports username/password + 2FA, but the web UI only shows the device code method.
A: Try these:
- Click the logout icon in the sidebar (next to login status)
- Or visit
/api/logoutdirectly in your browser - After logout, refresh the page with Ctrl+F5 (hard refresh)
- If still stuck, restart the server from the top bar
A: After pressing Save Settings, the page automatically re-fetches settings from the server. If the toggle still looks wrong:
- Refresh the page (F5)
- Make sure you see the green "Settings saved" toast
- Check the server-side file (
settings.json) — it always has the correct values
A: Check the logs with journalctl -u twitch-drops -n 50. Common causes:
- OAuth token expired (use the logout button and re-login)
- Twitch API rate limiting (wait a few minutes)
- Network connectivity issues
A: The miner runs a scheduled task every hour that triggers a campaign refresh. This is normal behavior. The task will automatically reload inventory when needed — no action required.
A: This means campaigns exist but no live channels are streaming the game with drops enabled. The server will automatically check again when new streams go live, or click Reload to force an immediate refresh.
A: See the Running as a Service section above. On Linux, use the provided systemd unit file.