translation update (#24)

* update agent instructions

* - update translations
- add id to translation anchors to ensure correct update
This commit is contained in:
Fengqing Liu
2025-12-15 09:43:43 +11:00
committed by GitHub
parent 68165aace6
commit 8d2f253da0
25 changed files with 1519 additions and 186 deletions

434
AGENTS.md Normal file
View File

@@ -0,0 +1,434 @@
# AGENTS.md
## AGENTS.md Specific Instructions
This file provides guidance to AI Agents when working with code in this repository.
## Development Guidelines
1. **Testing**:
- Always add unit tests for backend changes.
- Frontend changes should have tests if possible.
2. **Code Style & Architecture**:
- **DRY (Don't Repeat Yourself)**: Codebase must follow DRY principle.
- **OOP (Object-Oriented Programming)**: Required for all backend code.
3. **Refactoring**:
- You are authorized to refactor code to align with DRY/OOP principles.
- **Permission Required**: You MUST ask for user permission before significant refactoring.
4. **Localization (i18n)**:
- Update translation files if there are changes to UI text or console messages.
5. **Documentation**:
- Always update `README.md` and all agent instruction files when making changes.
- The contents of all agent instruction files should be identical except for the `Specific Instructions` section. Any agent-specific instructions must be added to that section.
## 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.12+ 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
## Architecture
The application now uses a clean `src/` package structure with clear separation of concerns.
### Project Structure
```text
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
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 `src` package as a module using `runpy.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`, and `GQLClient`
- 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 logic
- `InventoryService`: Campaign and drop inventory operations
- `WatchService`: Drop mining watch payload logic
- `MaintenanceService`: Periodic maintenance tasks
- `MessageHandlerService`: Websocket message routing and handling
**src/models/channel.py** - Channel and Stream:
- `Channel` class: Twitch channel with online/offline status
- `Stream` class: 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 progress
- `BaseDrop`: 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 `WebSocketBroadcaster` for 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`, `/api/version`
- 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 (including verification)
- Logging and dump flags from command-line arguments
- Persistence to JSON file (`settings.json`) in DATA_DIR
- Inventory filters (Status, Benefit Type, Game Search)
### State Machine Flow
1. **IDLE** - Waiting for campaigns or user action
2. **INVENTORY_FETCH** - Fetch campaigns from GraphQL, claim completed drops
3. **GAMES_UPDATE** - Determine wanted games based on priority/exclude lists
4. **CHANNELS_CLEANUP** - Remove channels not streaming wanted games
5. **CHANNELS_FETCH** - Discover channels via ACL lists or game directories
6. **CHANNEL_SWITCH** - Select best channel to watch based on priority/ACL
7. 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` (`_AuthState` class)
- Access tokens stored in `cookies.jar` in DATA_DIR
- Device ID from Twitch's `unique_id` cookie
- 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
1. Selected channel (if user clicked one)
2. ACL-based channels over directory channels
3. Game priority order (from settings)
4. Viewer count (descending)
5. 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 via `from 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:**
```python
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:**
```python
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 (MinerException, ExitRequest, RequestException, RequestInvalid, WebsocketClosed, LoginException, CaptchaRequired, GQLException)
- **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)
## 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:
```bash
# Activate the virtual environment (required before any Python commands)
source env/bin/activate
```
### Running the Application
```bash
# 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.12+
- Virtual environment at `env/` (must be activated before running commands)
- Dependencies from `pyproject.toml` (includes FastAPI, uvicorn, Socket.IO)
Docker deployment:
```bash
# Build and run with docker-compose
docker-compose up -d
# Access at http://localhost:8080
```
## Testing
### Automated Tests
The project includes a test suite in the `tests/` directory:
```bash
# Activate virtual environment and run tests
source env/bin/activate && python -m pytest tests/
```
**Test Files:**
- `tests/test_proxy_settings.py` - Tests for proxy settings configuration
- `tests/test_verify_proxy.py` - Tests for proxy verification functionality
### Manual Testing
1. Run with `-vvv` for maximum verbosity (levels: -v, -vv, -vvv, -vvvv)
2. Use `--dump` to generate debug data dumps
3. Check log files in `./logs/` directory
4. Use `--debug-ws` for websocket debug logging
5. Use `--debug-gql` for GraphQL debug logging
6. 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/settings`, `/api/login`, `/api/oauth/confirm`, `/api/reload`, `/api/close`, `/api/version`
- Socket.IO server for real-time bi-directional communication
- Serves static web frontend from `web/` directory
- Integrates with WebGUIManager via `set_managers()`
**web/** - Frontend assets:
- `index.html` - Single-page application layout with tabs
- `static/app.js` - Socket.IO client, real-time UI updates, API calls, Inventory Filtering logic
- `static/styles.css` - Responsive design with dark mode support
### Communication Protocol
**Server → Client (Socket.IO events):**
- `initial_state` - Full state on connect
- `status_update` - Status bar changes
- `console_output` - New log lines
- `channel_add/update/remove` - Channel list changes
- `drop_progress` - Drop mining progress
- `campaign_add` - New campaign added
- `login_required` - Prompt for credentials
- `settings_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_ENV` env var or `/.dockerenv` file
- Docker: Uses `/app` for code, `/app/data` for persistent storage
- Development: Uses `<project_root>/data` for 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/data` for 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 with advanced filtering
- ✅ 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

123
CLAUDE.md
View File

@@ -1,63 +1,43 @@
# CLAUDE.md
## CLAUDE.md Specific Instructions
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Guidelines
1. **Testing**:
- Always add unit tests for backend changes.
- Frontend changes should have tests if possible.
2. **Code Style & Architecture**:
- **DRY (Don't Repeat Yourself)**: Codebase must follow DRY principle.
- **OOP (Object-Oriented Programming)**: Required for all backend code.
3. **Refactoring**:
- You are authorized to refactor code to align with DRY/OOP principles.
- **Permission Required**: You MUST ask for user permission before significant refactoring.
4. **Localization (i18n)**:
- Update translation files if there are changes to UI text or console messages.
5. **Documentation**:
- Always update `README.md` and all agent instruction files when making changes.
- The contents of all agent instruction files should be identical except for the `Specific Instructions` section. Any agent-specific instructions must be added to that section.
## 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
- Python 3.12+ 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:
```bash
# Activate the virtual environment (required before any Python commands)
source env/bin/activate
```
### Running the Application
```bash
# 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:
```bash
# Build and run with docker-compose
docker-compose up -d
# Access at http://localhost:8080
```
## Architecture
@@ -97,7 +77,7 @@ lang/ # Translation JSON files (19 languages)
- Runs the `src` package as a module using `runpy.run_module("src")`
- All application logic is now in `src/__main__.py`
**src/**main**.py** - Entry point:
**src/__main__.py** - Entry point:
- Parses command-line arguments
- Initializes Settings, Twitch client, and WebGUIManager
@@ -149,7 +129,7 @@ lang/ # Translation JSON files (19 languages)
**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`
- REST API endpoints: `/api/status`, `/api/channels`, `/api/campaigns`, `/api/settings`, `/api/login`, `/api/oauth/confirm`, `/api/reload`, `/api/close`, `/api/version`
- Socket.IO server for real-time bi-directional communication
- Serves static web frontend from `web/` directory
- Integrates with WebGUIManager via `set_managers()`
@@ -286,13 +266,56 @@ login_text = _.t["login"]["status"]["logged_in"] # Returns "Logged in"
- **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)
- **__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)
## 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:
```bash
# Activate the virtual environment (required before any Python commands)
source env/bin/activate
```
### Running the Application
```bash
# 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.12+
- Virtual environment at `env/` (must be activated before running commands)
- Dependencies from `pyproject.toml` (includes FastAPI, uvicorn, Socket.IO)
Docker deployment:
```bash
# Build and run with docker-compose
docker-compose up -d
# Access at http://localhost:8080
```
## Testing
### Automated Tests
@@ -333,10 +356,10 @@ The application uses a web-based interface accessible via browser:
**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
- REST API endpoints: `/api/status`, `/api/channels`, `/api/campaigns`, `/api/settings`, `/api/login`, `/api/oauth/confirm`, `/api/reload`, `/api/close`, `/api/version`
- Socket.IO server for real-time bi-directional communication
- Serves static web frontend from `web/` directory
- Integrates with WebGUIManager via `set_managers(gui, twitch)`
- Integrates with WebGUIManager via `set_managers()`
**web/** - Frontend assets:

117
GEMINI.md
View File

@@ -1,63 +1,43 @@
# GEMINI.md
## GEMINI.md Specific Instructions
This file provides guidance to Gemini when working with code in this repository.
## Development Guidelines
1. **Testing**:
- Always add unit tests for backend changes.
- Frontend changes should have tests if possible.
2. **Code Style & Architecture**:
- **DRY (Don't Repeat Yourself)**: Codebase must follow DRY principle.
- **OOP (Object-Oriented Programming)**: Required for all backend code.
3. **Refactoring**:
- You are authorized to refactor code to align with DRY/OOP principles.
- **Permission Required**: You MUST ask for user permission before significant refactoring.
4. **Localization (i18n)**:
- Update translation files if there are changes to UI text or console messages.
5. **Documentation**:
- Always update `README.md` and all agent instruction files when making changes.
- The contents of all agent instruction files should be identical except for the `Specific Instructions` section. Any agent-specific instructions must be added to that section.
## 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
- Python 3.12+ 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:
```bash
# Activate the virtual environment (required before any Python commands)
source env/bin/activate
```
### Running the Application
```bash
# 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:
```bash
# Build and run with docker-compose
docker-compose up -d
# Access at http://localhost:8080
```
## Architecture
@@ -149,7 +129,7 @@ lang/ # Translation JSON files (19 languages)
**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`
- REST API endpoints: `/api/status`, `/api/channels`, `/api/campaigns`, `/api/settings`, `/api/login`, `/api/oauth/confirm`, `/api/reload`, `/api/close`, `/api/version`
- Socket.IO server for real-time bi-directional communication
- Serves static web frontend from `web/` directory
- Integrates with WebGUIManager via `set_managers()`
@@ -293,6 +273,49 @@ login_text = _.t["login"]["status"]["logged_in"] # Returns "Logged in"
- **src/web/managers/cache.py** - ImageCache for campaign artwork caching
- **web/** - Frontend assets (index.html, static/app.js, static/styles.css)
## 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:
```bash
# Activate the virtual environment (required before any Python commands)
source env/bin/activate
```
### Running the Application
```bash
# 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.12+
- Virtual environment at `env/` (must be activated before running commands)
- Dependencies from `pyproject.toml` (includes FastAPI, uvicorn, Socket.IO)
Docker deployment:
```bash
# Build and run with docker-compose
docker-compose up -d
# Access at http://localhost:8080
```
## Testing
### Automated Tests
@@ -332,8 +355,8 @@ The application uses a web-based interface accessible via browser:
**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
- REST API endpoints: `/api/status`, `/api/channels`, `/api/campaigns`, `/api/settings`, `/api/login`, `/api/oauth/confirm`, `/api/reload`, `/api/close`, `/api/version`
- Socket.IO server for real-time bi-directional communication
- Serves static web frontend from `web/` directory
- Integrates with WebGUIManager via `set_managers(gui, twitch)`

View File

@@ -106,7 +106,20 @@
"starts": "Starter: {time}",
"ends": "Slutter: {time}",
"no_campaigns": "Ingen kampagner indlæst endnu...",
"claimed_drops": "hentet"
"claimed_drops": "hentet",
"filters": {
"active": "Aktiv",
"not_linked": "Ikke forbundet",
"upcoming": "Kommende",
"expired": "Udløbet",
"finished": "Afsluttet",
"item": "Genstand",
"badge": "Badge",
"emote": "Emote",
"other": "Andet",
"clear": "Ryd filtre",
"search_placeholder": "Søg efter spil..."
}
},
"settings": {
"general": {
@@ -127,7 +140,9 @@
"actions": "Handlinger",
"connection_quality": "Forbindelseskvalitet:",
"minimum_refresh": "Minimum opdateringsinterval (minutter):",
"reload_campaigns": "Genindlæs kampagner"
"reload_campaigns": "Genindlæs kampagner",
"mining_benefits": "Mining Fordele",
"mining_benefits_help": "Vælg hvilke typer drops du vil mine."
},
"help": {
"about": "Om Twitch Drops Miner",
@@ -164,6 +179,26 @@
"manual_mode": "MANUEL",
"connected": "Tilsluttet",
"disconnected": "Afbrudt"
},
"footer": {
"version": "Version:",
"loading": "Indlæser...",
"update_available": "Opdatering tilgængelig:"
},
"badges": {
"manual": {
"title": "Manuel tilstand aktiv - ser specifikt spil"
},
"auto": {
"title": "Automatisk tilstand - følger spilprioritet"
},
"proxy": {
"title": "Proxy-forbindelse aktiv"
}
},
"wanted": {
"name": "Ønskede Drops Kø",
"none": "Ingen ønskede drops i køen..."
}
}
}

View File

@@ -106,7 +106,20 @@
"starts": "Beginnt: {time}",
"ends": "Endet: {time}",
"no_campaigns": "Noch keine Kampagnen geladen...",
"claimed_drops": "abgeholt"
"claimed_drops": "abgeholt",
"filters": {
"active": "Aktiv",
"not_linked": "Nicht Verbunden",
"upcoming": "Kommend",
"expired": "Abgelaufen",
"finished": "Beendet",
"item": "Gegenstand",
"badge": "Abzeichen",
"emote": "Emote",
"other": "Andere",
"clear": "Filter löschen",
"search_placeholder": "Spiele suchen..."
}
},
"settings": {
"general": {
@@ -127,7 +140,9 @@
"all_games_selected": "Alle Spiele sind ausgewählt oder keine Spiele verfügbar.",
"actions": "Aktionen",
"connection_quality": "Verbindungsqualität:",
"minimum_refresh": "Minimales Aktualisierungsintervall (Minuten):"
"minimum_refresh": "Minimales Aktualisierungsintervall (Minuten):",
"mining_benefits": "Mining-Vorteile",
"mining_benefits_help": "Wählen Sie, welche Arten von Drops Sie minen möchten."
},
"help": {
"about": "Über Twitch Drops Miner",
@@ -164,6 +179,26 @@
"manual_mode": "MANUELL",
"connected": "Verbunden",
"disconnected": "Getrennt"
},
"footer": {
"version": "Version:",
"loading": "Laden...",
"update_available": "Update verfügbar:"
},
"badges": {
"manual": {
"title": "Manueller Modus aktiv - schaue bestimmtes Spiel"
},
"auto": {
"title": "Automatischer Modus - folge Spielpriorität"
},
"proxy": {
"title": "Proxy-Verbindung aktiv"
}
},
"wanted": {
"name": "Wunsch-Drops Warteschlange",
"none": "Keine Wunsch-Drops in der Warteschlange..."
}
}
}
}

View File

@@ -97,24 +97,14 @@
"channel_count_plural": "channels",
"viewers": "viewers"
},
"inventory": {
"no_campaigns": "No campaigns loaded yet...",
"status": {
"active": "Active ✔",
"upcoming": "Upcoming ⏳",
"expired": "Expired ❌",
"claimed": "Claimed ✔"
},
"starts": "Starts: {time}",
"ends": "Ends: {time}",
"claimed_drops": "claimed"
},
"settings": {
"general": {
"name": "General Settings",
"name": "General",
"dark_mode": "Dark Mode"
},
"reload": "Reload",
"mining_benefits": "Mining Benefits",
"mining_benefits_help": "Select which types of drops you want to mine.",
"reload": "Reload Campaigns",
"reload_campaigns": "Reload Campaigns",
"games_to_watch": "Games to Watch",
"games_help": "Select games to watch. Order matters - drag to reorder priority (top = highest priority).",
@@ -165,6 +155,51 @@
"manual_mode": "MANUAL",
"connected": "Connected",
"disconnected": "Disconnected"
},
"footer": {
"version": "Version:",
"loading": "Loading...",
"update_available": "Update Available:"
},
"badges": {
"manual": {
"title": "Manual mode active - watching specific game"
},
"auto": {
"title": "Automatic mode - following game priority"
},
"proxy": {
"title": "Proxy connection active"
}
},
"wanted": {
"name": "Wanted Drops Queue",
"none": "No wanted drops queued..."
},
"inventory": {
"no_campaigns": "No campaigns loaded yet...",
"status": {
"active": "Active ✔",
"upcoming": "Upcoming ⏳",
"expired": "Expired ❌",
"claimed": "Claimed ✔"
},
"starts": "Starts: {time}",
"ends": "Ends: {time}",
"claimed_drops": "claimed",
"filters": {
"active": "Active",
"not_linked": "Not Linked",
"upcoming": "Upcoming",
"expired": "Expired",
"finished": "Finished",
"item": "Item",
"badge": "Badge",
"emote": "Emote",
"other": "Other",
"clear": "Clear Filters",
"search_placeholder": "Type to search games..."
}
}
}
}
}

View File

@@ -106,7 +106,20 @@
"starts": "Comienza: {time}",
"ends": "Termina: {time}",
"no_campaigns": "Aún no se han cargado campañas...",
"claimed_drops": "reclamado"
"claimed_drops": "reclamado",
"filters": {
"active": "Activo",
"not_linked": "No Vinculado",
"upcoming": "Próximo",
"expired": "Caducado",
"finished": "Terminado",
"item": "Objeto",
"badge": "Insignia",
"emote": "Emote",
"other": "Otro",
"clear": "Limpiar Filtros",
"search_placeholder": "Buscar juegos..."
}
},
"settings": {
"general": {
@@ -127,7 +140,9 @@
"all_games_selected": "Todos los juegos están seleccionados o no hay juegos disponibles.",
"actions": "Acciones",
"connection_quality": "Calidad de conexión:",
"minimum_refresh": "Intervalo mínimo de actualización (minutos):"
"minimum_refresh": "Intervalo mínimo de actualización (minutos):",
"mining_benefits": "Beneficios de Minería",
"mining_benefits_help": "Selecciona qué tipos de drops deseas minar."
},
"help": {
"about": "Acerca de Twitch Drops Miner",
@@ -164,6 +179,26 @@
"manual_mode": "MANUAL",
"connected": "Conectado",
"disconnected": "Desconectado"
},
"footer": {
"version": "Versión:",
"loading": "Cargando...",
"update_available": "Actualización disponible:"
},
"badges": {
"manual": {
"title": "Modo manual activo - viendo juego específico"
},
"auto": {
"title": "Modo automático - siguiendo prioridad de juegos"
},
"proxy": {
"title": "Conexión proxy activa"
}
},
"wanted": {
"name": "Cola de Drops Deseados",
"none": "No hay drops deseados en cola..."
}
}
}
}

View File

@@ -106,7 +106,20 @@
"starts": "Début : {time}",
"ends": "Fin : {time}",
"no_campaigns": "Aucune campagne chargée pour le moment...",
"claimed_drops": "récupéré"
"claimed_drops": "récupéré",
"filters": {
"active": "Actif",
"not_linked": "Non lié",
"upcoming": "À venir",
"expired": "Expiré",
"finished": "Terminé",
"item": "Objet",
"badge": "Badge",
"emote": "Émote",
"other": "Autre",
"clear": "Effacer les filtres",
"search_placeholder": "Rechercher des jeux..."
}
},
"settings": {
"general": {
@@ -127,7 +140,9 @@
"all_games_selected": "Tous les jeux sont sélectionnés ou aucun jeu disponible.",
"actions": "Actions",
"connection_quality": "Qualité de connexion :",
"minimum_refresh": "Intervalle minimum de rafraîchissement (minutes) :"
"minimum_refresh": "Intervalle minimum de rafraîchissement (minutes) :",
"mining_benefits": "Avantages du Minage",
"mining_benefits_help": "Sélectionnez les types de drops que vous souhaitez miner."
},
"help": {
"about": "À propos de Twitch Drops Miner",
@@ -164,6 +179,26 @@
"manual_mode": "MANUEL",
"connected": "Connecté",
"disconnected": "Déconnecté"
},
"footer": {
"version": "Version :",
"loading": "Chargement...",
"update_available": "Mise à jour disponible :"
},
"badges": {
"manual": {
"title": "Mode manuel actif - regarde un jeu spécifique"
},
"auto": {
"title": "Mode automatique - suit la priorité des jeux"
},
"proxy": {
"title": "Connexion proxy active"
}
},
"wanted": {
"name": "File d'attente Drops souhaités",
"none": "Aucun drop souhaité en file d'attente..."
}
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Mulai : {time}",
"ends": "Akhir : {time}",
"no_campaigns": "Belum ada kampanye yang dimuat...",
"claimed_drops": "diklaim"
"claimed_drops": "diklaim",
"filters": {
"active": "Aktif",
"not_linked": "Tidak Terhubung",
"upcoming": "Mendatang",
"expired": "Kadaluarsa",
"finished": "Selesai",
"item": "Item",
"badge": "Lencana",
"emote": "Emote",
"other": "Lainnya",
"clear": "Hapus Filter",
"search_placeholder": "Cari game..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Tindakan",
"connection_quality": "Kualitas Koneksi:",
"minimum_refresh": "Interval Refresh Minimum (menit):",
"reload_campaigns": "Muat Ulang Kampanye"
"reload_campaigns": "Muat Ulang Kampanye",
"mining_benefits": "Manfaat Penambangan",
"mining_benefits_help": "Pilih jenis drops yang ingin Anda tambang."
},
"help": {
"about": "Tentang Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "MANUAL",
"connected": "Terhubung",
"disconnected": "Terputus"
},
"footer": {
"version": "Versi:",
"loading": "Memuat...",
"update_available": "Pembaruan Tersedia:"
},
"badges": {
"manual": {
"title": "Mode manual aktif - menonton game tertentu"
},
"auto": {
"title": "Mode otomatis - mengikuti prioritas game"
},
"proxy": {
"title": "Koneksi proxy aktif"
}
},
"wanted": {
"name": "Antrean Drops yang Diinginkan",
"none": "Tidak ada drops yang diinginkan dalam antrean..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Inizia: {time}",
"ends": "Finisce: {time}",
"no_campaigns": "Nessuna campagna caricata ancora...",
"claimed_drops": "reclamato"
"claimed_drops": "reclamato",
"filters": {
"active": "Attivo",
"not_linked": "Non Collegato",
"upcoming": "In Arrivo",
"expired": "Scaduto",
"finished": "Finito",
"item": "Oggetto",
"badge": "Badge",
"emote": "Emote",
"other": "Altro",
"clear": "Pulisci Filtri",
"search_placeholder": "Cerca giochi..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Azioni",
"connection_quality": "Qualità della connessione:",
"minimum_refresh": "Intervallo minimo di aggiornamento (minuti):",
"reload_campaigns": "Ricarica campagne"
"reload_campaigns": "Ricarica campagne",
"mining_benefits": "Vantaggi Minatori",
"mining_benefits_help": "Seleziona quali tipi di drop vuoi minare."
},
"help": {
"about": "Informazioni su Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "MANUALE",
"connected": "Connesso",
"disconnected": "Disconnesso"
},
"footer": {
"version": "Versione:",
"loading": "Caricamento...",
"update_available": "Aggiornamento disponibile:"
},
"badges": {
"manual": {
"title": "Modalità manuale attiva - guardando gioco specifico"
},
"auto": {
"title": "Modalità automatica - seguendo priorità giochi"
},
"proxy": {
"title": "Connessione proxy attiva"
}
},
"wanted": {
"name": "Coda Drops Desiderati",
"none": "Nessun drop desiderato in coda..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Begint: {time}",
"ends": "Eindigt: {time}",
"no_campaigns": "Nog geen campagnes geladen...",
"claimed_drops": "geclaimd"
"claimed_drops": "geclaimd",
"filters": {
"active": "Actief",
"not_linked": "Niet gekoppeld",
"upcoming": "Aankomend",
"expired": "Verlopen",
"finished": "Voltooid",
"item": "Item",
"badge": "Badge",
"emote": "Emote",
"other": "Overig",
"clear": "Filters wissen",
"search_placeholder": "Zoek spellen..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Acties",
"connection_quality": "Verbindingskwaliteit:",
"minimum_refresh": "Minimaal verversingsinterval (minuten):",
"reload_campaigns": "Herlaad Campagnes"
"reload_campaigns": "Herlaad Campagnes",
"mining_benefits": "Mining Voordelen",
"mining_benefits_help": "Selecteer welke soorten drops je wilt minen."
},
"help": {
"about": "Over Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "HANDMATIG",
"connected": "Verbonden",
"disconnected": "Verbinding verbroken"
},
"footer": {
"version": "Versie:",
"loading": "Laden...",
"update_available": "Update beschikbaar:"
},
"badges": {
"manual": {
"title": "Handmatige modus actief - specifiek spel kijken"
},
"auto": {
"title": "Automatische modus - spelprioriteit volgen"
},
"proxy": {
"title": "Proxy-verbinding actief"
}
},
"wanted": {
"name": "Gewenste Drops Wachtrij",
"none": "Geen gewenste drops in de wachtrij..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Rozpoczęcie: {time}",
"ends": "Koniec: {time}",
"no_campaigns": "Nie załadowano jeszcze żadnych kampanii...",
"claimed_drops": "odebrano"
"claimed_drops": "odebrano",
"filters": {
"active": "Aktywne",
"not_linked": "Niepołączone",
"upcoming": "Nadchodzące",
"expired": "Wygasłe",
"finished": "Zakończone",
"item": "Przedmiot",
"badge": "Odznaka",
"emote": "Emotka",
"other": "Inne",
"clear": "Wyczyść filtry",
"search_placeholder": "Szukaj gier..."
}
},
"settings": {
"general": {
@@ -131,7 +144,9 @@
"actions": "Akcje",
"connection_quality": "Jakość połączenia:",
"minimum_refresh": "Minimalny interwał odświeżania (minuty):",
"reload_campaigns": "Przeładuj kampanie"
"reload_campaigns": "Przeładuj kampanie",
"mining_benefits": "Korzyści z Minowania",
"mining_benefits_help": "Wybierz rodzaje dropów, które chcesz zdobywać."
},
"help": {
"about": "O Twitch Drops Miner",
@@ -168,6 +183,26 @@
"manual_mode": "RĘCZNY",
"connected": "Połączono",
"disconnected": "Rozłączono"
},
"footer": {
"version": "Wersja:",
"loading": "Ładowanie...",
"update_available": "Aktualizacja dostępna:"
},
"badges": {
"manual": {
"title": "Tryb ręczny aktywny - oglądanie konkretnej gry"
},
"auto": {
"title": "Tryb automatyczny - według priorytetu gier"
},
"proxy": {
"title": "Połączenie proxy aktywne"
}
},
"wanted": {
"name": "Kolejka Pożądanych Dropów",
"none": "Brak pożądanych dropów w kolejce..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Começa em: {time}",
"ends": "Termina em: {time}",
"no_campaigns": "Nenhuma campanha carregada ainda...",
"claimed_drops": "reivindicado"
"claimed_drops": "reivindicado",
"filters": {
"active": "Ativo",
"not_linked": "Não Vinculado",
"upcoming": "Próximo",
"expired": "Expirado",
"finished": "Terminado",
"item": "Item",
"badge": "Distintivo",
"emote": "Emote",
"other": "Outro",
"clear": "Limpar Filtros",
"search_placeholder": "Pesquisar jogos..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Ações",
"connection_quality": "Qualidade da conexão:",
"minimum_refresh": "Intervalo mínimo de atualização (minutos):",
"reload_campaigns": "Recarregar Campanhas"
"reload_campaigns": "Recarregar Campanhas",
"mining_benefits": "Benefícios de Mineração",
"mining_benefits_help": "Selecione quais tipos de drops você deseja minerar."
},
"help": {
"about": "Sobre o Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "MANUAL",
"connected": "Conectado",
"disconnected": "Desconectado"
},
"footer": {
"version": "Versão:",
"loading": "Carregando...",
"update_available": "Atualização disponível:"
},
"badges": {
"manual": {
"title": "Modo manual ativo - assistindo jogo específico"
},
"auto": {
"title": "Modo automático - seguindo prioridade de jogos"
},
"proxy": {
"title": "Conexão proxy ativa"
}
},
"wanted": {
"name": "Fila de Drops Desejados",
"none": "Nenhum drop desejado na fila..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Începe : {time}",
"ends": "Se termină : {time}",
"no_campaigns": "Nicio campanie încărcată încă...",
"claimed_drops": "revendicat"
"claimed_drops": "revendicat",
"filters": {
"active": "Activ",
"not_linked": "Neconectat",
"upcoming": "Viitor",
"expired": "Expirat",
"finished": "Terminat",
"item": "Obiect",
"badge": "Insignă",
"emote": "Emoticon",
"other": "Altele",
"clear": "Șterge filtre",
"search_placeholder": "Caută jocuri..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Acțiuni",
"connection_quality": "Calitatea conexiunii:",
"minimum_refresh": "Interval minim de reîmprospătare (minute):",
"reload_campaigns": "Reîncarcă campaniile"
"reload_campaigns": "Reîncarcă campaniile",
"mining_benefits": "Beneficii Minerit",
"mining_benefits_help": "Selectați ce tipuri de drops doriți să minați."
},
"help": {
"about": "Despre Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "MANUAL",
"connected": "Conectat",
"disconnected": "Deconectat"
},
"footer": {
"version": "Versiune:",
"loading": "Se încarcă...",
"update_available": "Actualizare disponibilă:"
},
"badges": {
"manual": {
"title": "Mod manual activ - vizionare joc specific"
},
"auto": {
"title": "Mod automat - urmărire prioritate jocuri"
},
"proxy": {
"title": "Conexiune proxy activă"
}
},
"wanted": {
"name": "Coadă Drops Dorite",
"none": "Niciun drop dorit în coadă..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Başladı: {time}",
"ends": "Bitiş: {time}",
"no_campaigns": "Henüz yüklenen kampanya yok...",
"claimed_drops": "talep edildi"
"claimed_drops": "talep edildi",
"filters": {
"active": "Aktif",
"not_linked": "Bağlı Değil",
"upcoming": "Yaklaşan",
"expired": "Süresi Dolmuş",
"finished": "Tamamlanmış",
"item": "Eşya",
"badge": "Rozet",
"emote": "İfade",
"other": "Diğer",
"clear": "Filtreleri Temizle",
"search_placeholder": "Oyun ara..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "İşlemler",
"connection_quality": "Bağlantı kalitesi:",
"minimum_refresh": "Minimum yenileme aralığı (dakika):",
"reload_campaigns": "Kampanyaları Yeniden Yükle"
"reload_campaigns": "Kampanyaları Yeniden Yükle",
"mining_benefits": "Madencilik Avantajları",
"mining_benefits_help": "Hangi tür dropları toplamak istediğinizi seçin."
},
"help": {
"about": "Twitch Drops Miner Hakkında",
@@ -167,6 +182,26 @@
"manual_mode": "MANUEL",
"connected": "Bağlandı",
"disconnected": "Bağlantı kesildi"
},
"footer": {
"version": "Sürüm:",
"loading": "Yükleniyor...",
"update_available": "Güncelleme Mevcut:"
},
"badges": {
"manual": {
"title": "Manuel mod aktif - belirli oyun izleniyor"
},
"auto": {
"title": "Otomatik mod - oyun önceliği takip ediliyor"
},
"proxy": {
"title": "Proxy bağlantısı aktif"
}
},
"wanted": {
"name": "İstenen Drop Kuyruğu",
"none": "Kuyrukta istenen drop yok..."
}
}
}

View File

@@ -108,7 +108,20 @@
"starts": "Začíná: {time}",
"ends": "Začíná: {time}",
"no_campaigns": "Zatím nejsou načteny žádné kampaně...",
"claimed_drops": "získáno"
"claimed_drops": "získáno",
"filters": {
"active": "Aktivní",
"not_linked": "Nepřipojeno",
"upcoming": "Nadcházející",
"expired": "Vypršelo",
"finished": "Dokončeno",
"item": "Předmět",
"badge": "Odznak",
"emote": "Emotikon",
"other": "Jiné",
"clear": "Vymazat filtry",
"search_placeholder": "Hledat hry..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Akce",
"connection_quality": "Kvalita připojení:",
"minimum_refresh": "Minimální interval obnovení (minuty):",
"reload_campaigns": "Znovu načíst kampaně"
"reload_campaigns": "Znovu načíst kampaně",
"mining_benefits": "Výhody Těžby",
"mining_benefits_help": "Vyberte typy dropů, které chcete těžit."
},
"help": {
"about": "O Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "MANUÁLNÍ",
"connected": "Připojeno",
"disconnected": "Odpojeno"
},
"footer": {
"version": "Verze:",
"loading": "Načítání...",
"update_available": "Dostupná aktualizace:"
},
"badges": {
"manual": {
"title": "Manuální režim aktivní - sledování konkrétní hry"
},
"auto": {
"title": "Automatický režim - podle priority her"
},
"proxy": {
"title": "Proxy připojení aktivní"
}
},
"wanted": {
"name": "Fronta chtěných Dropů",
"none": "Žádné chtěné dropy ve frontě..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Начало: {time}",
"ends": "Окончание: {time}",
"no_campaigns": "Кампании ещё не загружены...",
"claimed_drops": "получено"
"claimed_drops": "получено",
"filters": {
"active": "Активные",
"not_linked": "Не связаны",
"upcoming": "Предстоящие",
"expired": "Истекшие",
"finished": "Завершенные",
"item": "Предмет",
"badge": "Значок",
"emote": "Смайлик",
"other": "Другое",
"clear": "Очистить фильтры",
"search_placeholder": "Поиск игр..."
}
},
"settings": {
"general": {
@@ -131,7 +144,9 @@
"actions": "Действия",
"connection_quality": "Качество соединения:",
"minimum_refresh": "Минимальный интервал обновления (минуты):",
"reload_campaigns": "Перезагрузить кампании"
"reload_campaigns": "Перезагрузить кампании",
"mining_benefits": "Преимущества майнинга",
"mining_benefits_help": "Выберите типы drops, которые вы хотите майнить."
},
"help": {
"about": "О Twitch Drops Miner",
@@ -168,6 +183,26 @@
"manual_mode": "РУЧНОЙ",
"connected": "Подключено",
"disconnected": "Отключено"
},
"footer": {
"version": "Версия:",
"loading": "Загрузка...",
"update_available": "Доступно обновление:"
},
"badges": {
"manual": {
"title": "Ручной режим - просмотр конкретной игры"
},
"auto": {
"title": "Автоматический режим - по приоритету игр"
},
"proxy": {
"title": "Прокси подключен"
}
},
"wanted": {
"name": "Очередь желаемых дропов",
"none": "Нет желаемых дропов в очереди..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "Починається {time}",
"ends": "Завершується {time}",
"no_campaigns": "Кампанії ще не завантажені...",
"claimed_drops": "отримано"
"claimed_drops": "отримано",
"filters": {
"active": "Активні",
"not_linked": "Не пов'язані",
"upcoming": "Майбутні",
"expired": "Завершені",
"finished": "Закінчені",
"item": "Предмет",
"badge": "Значок",
"emote": "Смайлик",
"other": "Інше",
"clear": "Очистити фільтри",
"search_placeholder": "Пошук ігор..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "Дії",
"connection_quality": "Якість з'єднання:",
"minimum_refresh": "Мінімальний інтервал оновлення (хвилини):",
"reload_campaigns": "Перезавантажити кампанії"
"reload_campaigns": "Перезавантажити кампанії",
"mining_benefits": "Переваги майнінгу",
"mining_benefits_help": "Виберіть типи drops, які ви хочете майнити."
},
"help": {
"about": "Про Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "РУЧНИЙ",
"connected": "Підключено",
"disconnected": "Відключено"
},
"footer": {
"version": "Версія:",
"loading": "Завантаження...",
"update_available": "Доступне оновлення:"
},
"badges": {
"manual": {
"title": "Ручний режим - перегляд конкретної гри"
},
"auto": {
"title": "Автоматичний режим - за пріоритетом ігор"
},
"proxy": {
"title": "Проксі підключено"
}
},
"wanted": {
"name": "Черга бажаних дропів",
"none": "Немає бажаних дропів у черзі..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "{time} :يبدأ",
"ends": "{time} :ينتهي",
"no_campaigns": "لم يتم تحميل أي حملات بعد...",
"claimed_drops": "تم المطالبة"
"claimed_drops": "تم المطالبة",
"filters": {
"active": "نشط",
"not_linked": "غير مرتبط",
"upcoming": "قادم",
"expired": "منتهي",
"finished": "مكتمل",
"item": "عنصر",
"badge": "شارة",
"emote": "رمز تعبيري",
"other": "آخر",
"clear": "مسح المرشحات",
"search_placeholder": "البحث عن الألعاب..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "الإجراءات",
"connection_quality": "جودة الاتصال:",
"minimum_refresh": "الحد الأدنى لفترة التحديث (بالدقائق):",
"reload_campaigns": "إعادة تحميل الحملات"
"reload_campaigns": "إعادة تحميل الحملات",
"mining_benefits": "فوائد التعدين",
"mining_benefits_help": "اختر أنواع الجوائز التي تريد تعدينها."
},
"help": {
"about": "حول Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "يدوي",
"connected": "متصل",
"disconnected": "غير متصل"
},
"footer": {
"version": "النسخة:",
"loading": "جاري التحميل...",
"update_available": "تحديث متوفر:"
},
"badges": {
"manual": {
"title": "الوضع اليدوي نشط - مشاهدة لعبة محددة"
},
"auto": {
"title": "الوضع التلقائي - اتباع أولوية الألعاب"
},
"proxy": {
"title": "اتصال البروكسي نشط"
}
},
"wanted": {
"name": "قائمة انتظار Drops المطلوبة",
"none": "لا توجد drops مطلوبة في قائمة الانتظار..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "開始:{time}",
"ends": "終了:{time}",
"no_campaigns": "まだキャンペーンが読み込まれていません...",
"claimed_drops": "受け取り済み"
"claimed_drops": "受け取り済み",
"filters": {
"active": "アクティブ",
"not_linked": "未連携",
"upcoming": "近日開催",
"expired": "期限切れ",
"finished": "完了",
"item": "アイテム",
"badge": "バッジ",
"emote": "エモート",
"other": "その他",
"clear": "フィルター解除",
"search_placeholder": "ゲームを検索..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "アクション",
"connection_quality": "接続品質:",
"minimum_refresh": "最小更新間隔(分):",
"reload_campaigns": "キャンペーンを再読み込み"
"reload_campaigns": "キャンペーンを再読み込み",
"mining_benefits": "マイニング特典",
"mining_benefits_help": "マイニングしたいドロップの種類を選択してください。"
},
"help": {
"about": "Twitch Drops Minerについて",
@@ -167,6 +182,26 @@
"manual_mode": "手動",
"connected": "接続済み",
"disconnected": "切断"
},
"footer": {
"version": "バージョン:",
"loading": "読み込み中...",
"update_available": "更新利用可能:"
},
"badges": {
"manual": {
"title": "手動モード - 特定のゲームを視聴中"
},
"auto": {
"title": "自動モード - ゲーム優先度に従う"
},
"proxy": {
"title": "プロキシ接続中"
}
},
"wanted": {
"name": "欲しいDropsキュー",
"none": "キューに欲しいDropsはありません..."
}
}
}

View File

@@ -106,7 +106,20 @@
"starts": "开始时间: {time}",
"ends": "结束时间: {time}",
"no_campaigns": "尚未加载任何活动...",
"claimed_drops": "已领取"
"claimed_drops": "已领取",
"filters": {
"active": "活动中",
"not_linked": "未关联",
"upcoming": "即将开始",
"expired": "已过期",
"finished": "已完成",
"item": "物品",
"badge": "徽章",
"emote": "表情",
"other": "其他",
"clear": "清除过滤器",
"search_placeholder": "搜索游戏..."
}
},
"settings": {
"general": {
@@ -127,7 +140,9 @@
"actions": "操作",
"connection_quality": "连接质量:",
"minimum_refresh": "最小刷新间隔(分钟):",
"reload_campaigns": "重新加载活动"
"reload_campaigns": "重新加载活动",
"mining_benefits": "挖掘奖励",
"mining_benefits_help": "选择您想要获取的掉落类型。"
},
"help": {
"about": "关于 Twitch 掉宝矿工",
@@ -164,6 +179,26 @@
"manual_mode": "MANUAL",
"connected": "已连接",
"disconnected": "已断开"
},
"footer": {
"version": "版本:",
"loading": "加载中...",
"update_available": "可用更新:"
},
"badges": {
"manual": {
"title": "手动模式已激活 - 正在观看特定游戏"
},
"auto": {
"title": "自动模式 - 遵循游戏优先级"
},
"proxy": {
"title": "代理连接已激活"
}
},
"wanted": {
"name": "期望掉落队列",
"none": "队列中没有期望的掉落..."
}
}
}

View File

@@ -109,7 +109,20 @@
"starts": "開始時間:{time}",
"ends": "結束時間:{time}",
"no_campaigns": "尚未載入任何活動...",
"claimed_drops": "已領取"
"claimed_drops": "已領取",
"filters": {
"active": "活動中",
"not_linked": "未連結",
"upcoming": "即將開始",
"expired": "已過期",
"finished": "已完成",
"item": "物品",
"badge": "徽章",
"emote": "表情",
"other": "其他",
"clear": "清除過濾器",
"search_placeholder": "搜尋遊戲..."
}
},
"settings": {
"general": {
@@ -130,7 +143,9 @@
"actions": "操作",
"connection_quality": "連線品質:",
"minimum_refresh": "最小重新整理間隔(分鐘):",
"reload_campaigns": "重新載入活動"
"reload_campaigns": "重新載入活動",
"mining_benefits": "挖掘獎勵",
"mining_benefits_help": "選擇您想要獲取的掉落類型。"
},
"help": {
"about": "關於 Twitch Drops Miner",
@@ -167,6 +182,26 @@
"manual_mode": "手動",
"connected": "已連接",
"disconnected": "已中斷連接"
},
"footer": {
"version": "版本:",
"loading": "載入中...",
"update_available": "可用更新:"
},
"badges": {
"manual": {
"title": "手動模式已啟用 - 正在觀看特定遊戲"
},
"auto": {
"title": "自動模式 - 遵循遊戲優先級"
},
"proxy": {
"title": "代理連接已啟用"
}
},
"wanted": {
"name": "期望掉落隊列",
"none": "隊列中沒有期望的掉落..."
}
}
}

View File

@@ -112,6 +112,41 @@ class GUIChannels(TypedDict):
viewers: str
class GUIFooter(TypedDict):
version: str
loading: str
update_available: str
class GUIBadgeItem(TypedDict):
title: str
class GUIBadges(TypedDict):
manual: GUIBadgeItem
auto: GUIBadgeItem
proxy: GUIBadgeItem
class GUIWanted(TypedDict):
name: str
none: str
class GUIInvFilters(TypedDict):
active: str
not_linked: str
upcoming: str
expired: str
finished: str
item: str
badge: str
emote: str
other: str
clear: str
search_placeholder: str
class GUIInvStatus(TypedDict):
active: str
expired: str
@@ -125,6 +160,7 @@ class GUIInventory(TypedDict):
starts: str
ends: str
claimed_drops: str
filters: GUIInvFilters
class GUISettingsGeneral(TypedDict):
@@ -134,6 +170,8 @@ class GUISettingsGeneral(TypedDict):
class GUISettings(TypedDict):
general: GUISettingsGeneral
mining_benefits: str
mining_benefits_help: str
reload: str
reload_campaigns: str
games_to_watch: str
@@ -185,6 +223,9 @@ class GUIMessages(TypedDict):
settings: GUISettings
help: GUIHelp
header: GUIHeader
footer: GUIFooter
badges: GUIBadges
wanted: GUIWanted
class Translation(TypedDict):

View File

@@ -69,8 +69,8 @@
<!-- Current Drop Progress -->
<section class="panel progress-panel">
<h2>Current Drop</h2>
<div id="drop-display">
<h2 id="progress-header">Campaign Progress</h2>
<div id="progress-container">
<div id="no-drop-message">No active drop</div>
<div id="drop-info" style="display: none;">
<div id="manual-mode-controls" class="manual-mode-controls hidden">
@@ -92,19 +92,19 @@
<!-- Console Output -->
<section class="panel console-panel">
<h2>Console Output</h2>
<h2 id="console-header">Logs</h2>
<div id="console-output" class="console-output"></div>
</section>
<!-- Channels List -->
<section class="panel channels-panel">
<h2>Channels</h2>
<h2 id="channels-header">Channels</h2>
<div id="channels-list" class="channels-list"></div>
</section>
<!-- Wanted Items (New Panel) -->
<section class="panel wanted-panel">
<h2>Wanted Drops Queue</h2>
<h2 id="wanted-header">Wanted Drop Queue</h2>
<div id="wanted-items-list" class="wanted-items-list">
<p class="empty-message-small">No wanted drops queued...</p>
</div>
@@ -138,7 +138,8 @@
<span>Finished</span>
</label>
<div class="filter-separator"
style="width: 1px; height: 24px; background: var(--border-color); margin: 0 10px;"></div>
style="width: 1px; height: 24px; background: var(--border-color); margin: 0 10px;">
</div>
<label class="filter-checkbox" title="Direct Entitlement (Game Item)">
<input type="checkbox" id="filter-benefit-item" checked />
<span>Item</span>
@@ -180,7 +181,7 @@
<div id="settings-tab" class="tab-content">
<div class="settings-container">
<section class="settings-section">
<h2>General Settings</h2>
<h2 id="settings-general-header">General</h2>
<label>
<input type="checkbox" id="dark-mode"> Dark Mode
</label>
@@ -195,7 +196,8 @@
<label>
Proxy URL:
<input type="text" id="proxy-url" placeholder="http://user:pass@host:port">
<button id="set-proxy-btn" class="secondary-btn" title="Set and verify proxy">Set Proxy</button>
<button id="set-proxy-btn" class="secondary-btn" title="Set and verify proxy">Set
Proxy</button>
<button id="verify-proxy-btn" class="secondary-btn"
title="Test proxy connection">Verify</button>
</label>
@@ -205,8 +207,9 @@
</section>
<section class="settings-section">
<h2>Mining Benefits</h2>
<p class="help-text">Select which types of drops you want to mine.</p>
<h2 id="settings-benefits-header">Mining Benefits</h2>
<p id="settings-benefits-help" class="help-text">Select which types of drops you want to
mine.</p>
<div class="benefits-filter filter-checkboxes">
<label class="filter-checkbox" title="Detailed description: Direct Entitlement (Game Item)">
<input type="checkbox" id="mining-benefit-item" checked />
@@ -228,8 +231,10 @@
</section>
<section class="settings-section">
<h2>Games to Watch</h2>
<p class="help-text">Select games to watch. Order matters - drag to reorder priority (top = highest
<h2 id="settings-games-header">Games to Watch</h2>
<p id="settings-games-help" class="help-text">Select games to watch. Order matters - drag to
reorder
priority (top = highest
priority).</p>
<div class="games-filter">
@@ -254,8 +259,10 @@
</section>
<section class="settings-section">
<h2>Actions</h2>
<button id="reload-btn" class="action-button">Reload Campaigns</button>
<h2 id="settings-actions-header">Actions</h2>
<div class="settings-actions">
<button id="reload-btn" class="action-button">Reload</button>
</div>
</section>
</div>
</div>
@@ -263,10 +270,10 @@
<!-- Help Tab -->
<div id="help-tab" class="tab-content">
<div class="help-content">
<h2>About Twitch Drops Miner</h2>
<h2 id="help-about-header">About Twitch Drops Miner</h2>
<p>This application automatically mines timed Twitch drops without downloading stream data.</p>
<h3>How to Use</h3>
<h3 id="help-howto-header">How to Use</h3>
<ol>
<li>Login using your Twitch account (OAuth device code flow)</li>
<li>Link your accounts at <a href="https://www.twitch.tv/drops/campaigns"
@@ -276,7 +283,7 @@
<li>Monitor progress in the Main and Inventory tabs</li>
</ol>
<h3>Features</h3>
<h3 id="help-features-header">Features</h3>
<ul>
<li>Stream-less drop mining - saves bandwidth</li>
<li>Game priority and exclusion lists</li>
@@ -285,7 +292,7 @@
<li>Real-time progress tracking</li>
</ul>
<h3>Important Notes</h3>
<h3 id="help-notes-header">Important Notes</h3>
<ul>
<li>Do not watch streams on the same account while mining</li>
<li>Keep your cookies.jar file secure</li>
@@ -293,7 +300,8 @@
</ul>
<div class="help-links">
<a href="https://github.com/rangermix/TwitchDropsMiner" target="_blank">GitHub Repository</a>
<a href="https://github.com/rangermix/TwitchDropsMiner" target="_blank">GitHub
Repository</a>
</div>
</div>
</div>

View File

@@ -28,6 +28,16 @@ async function fetchAndDisplayVersion() {
versionText += ' (latest)';
}
versionElement.textContent = versionText;
// Translate footer version text
const footerVersionText = document.getElementById('footer-version-text');
if (footerVersionText && state.translations.gui?.footer) {
const versionLabel = state.translations.gui.footer.version || 'Version:';
// Preserve the span inside
const span = footerVersionText.querySelector('span');
footerVersionText.textContent = versionLabel + ' ';
footerVersionText.appendChild(span);
}
}
// Display update notification if available
@@ -41,6 +51,17 @@ async function fetchAndDisplayVersion() {
updateLink.href = data.download_url;
updateIndicator.style.display = 'inline-block';
// Translate update message
if (state.translations.gui?.footer) {
const updateLabel = state.translations.gui.footer.update_available || 'Update Available:';
const linkText = document.createTextNode(`${updateLabel} `);
// Clear existing text nodes but keep the span
const span = updateLink.querySelector('span'); // latest-version span
updateLink.textContent = '';
updateLink.appendChild(linkText);
updateLink.appendChild(span);
}
// Log to console
console.log(`Update available: ${data.latest_version} (current: ${data.current_version})`);
}
@@ -49,7 +70,8 @@ async function fetchAndDisplayVersion() {
console.warn('Could not fetch version information:', error);
// Set placeholder text if fetch fails
const versionElement = document.getElementById('current-version');
if (versionElement && versionElement.textContent === 'Loading...') {
const loadingText = state.translations.gui?.footer?.loading || 'Loading...';
if (versionElement && versionElement.textContent === loadingText) {
versionElement.textContent = 'Unknown';
}
}
@@ -1581,7 +1603,8 @@ function applyTranslations(t) {
// Update Progress section
if (mainTab && t.gui?.progress) {
const progressHeader = mainTab.querySelector('.progress-panel h2');
// ID: progress-header
const progressHeader = document.getElementById('progress-header');
if (progressHeader) progressHeader.textContent = t.gui.progress.name;
const noDropMsg = document.getElementById('no-drop-message');
@@ -1593,13 +1616,15 @@ function applyTranslations(t) {
// Update Console section
if (mainTab && t.gui) {
const consoleHeader = mainTab.querySelector('.console-panel h2');
// ID: console-header
const consoleHeader = document.getElementById('console-header');
if (consoleHeader) consoleHeader.textContent = t.gui.output;
}
// Update Channels section
if (mainTab && t.gui?.channels) {
const channelsHeader = mainTab.querySelector('.channels-panel h2');
// ID: channels-header
const channelsHeader = document.getElementById('channels-header');
if (channelsHeader) channelsHeader.textContent = t.gui.channels.name;
// Channel list will re-render with translated empty messages
renderChannels();
@@ -1615,10 +1640,18 @@ function applyTranslations(t) {
// Update Settings tab
const settingsTab = document.getElementById('settings-tab');
if (settingsTab && t.gui?.settings) {
const headers = settingsTab.querySelectorAll('h2');
if (headers[0]) headers[0].textContent = t.gui.settings.general.name;
if (headers[1]) headers[1].textContent = t.gui.settings.games_to_watch;
if (headers[2]) headers[2].textContent = t.gui.settings.actions;
// Use IDs for robust selection
const generalHeader = document.getElementById('settings-general-header');
if (generalHeader) generalHeader.textContent = t.gui.settings.general.name;
const benefitsHeader = document.getElementById('settings-benefits-header');
if (benefitsHeader && t.gui.settings.mining_benefits) benefitsHeader.textContent = t.gui.settings.mining_benefits;
const gamesHeader = document.getElementById('settings-games-header');
if (gamesHeader) gamesHeader.textContent = t.gui.settings.games_to_watch;
const actionsHeader = document.getElementById('settings-actions-header');
if (actionsHeader) actionsHeader.textContent = t.gui.settings.actions;
const darkModeLabel = settingsTab.querySelector('label:has(#dark-mode)');
if (darkModeLabel) {
@@ -1642,8 +1675,11 @@ function applyTranslations(t) {
refreshLabel.appendChild(input);
}
const helpText = settingsTab.querySelector('.help-text');
if (helpText) helpText.textContent = t.gui.settings.games_help;
const benefitsHelp = document.getElementById('settings-benefits-help');
if (benefitsHelp && t.gui.settings.mining_benefits_help) benefitsHelp.textContent = t.gui.settings.mining_benefits_help;
const gamesHelp = document.getElementById('settings-games-help');
if (gamesHelp) gamesHelp.textContent = t.gui.settings.games_help;
const searchInput = document.getElementById('games-filter');
if (searchInput) searchInput.placeholder = t.gui.settings.search_games;
@@ -1670,14 +1706,38 @@ function applyTranslations(t) {
// Update Help tab
const helpTab = document.getElementById('help-tab');
if (helpTab && t.gui?.help) {
// Robust ID selection for Help tab headers
const aboutHeader = document.getElementById('help-about-header');
if (aboutHeader) aboutHeader.textContent = t.gui.help.about || 'About Twitch Drops Miner';
const howtoHeader = document.getElementById('help-howto-header');
if (howtoHeader) howtoHeader.textContent = t.gui.help.how_to_use || 'How to Use';
const featuresHeader = document.getElementById('help-features-header');
if (featuresHeader) featuresHeader.textContent = t.gui.help.features || 'Features';
const notesHeader = document.getElementById('help-notes-header');
if (notesHeader) notesHeader.textContent = t.gui.help.important_notes || 'Important Notes';
// Update list items and links (keeping innerHTML approach for lists as they are dynamic content blocks)
const helpContent = helpTab.querySelector('.help-content');
if (helpContent) {
// Rebuild help content dynamically
// We only need to update the dynamic lists and the github link, preserving the headers we just updated?
// Actually, the previous code was nuke-and-rebuild via innerHTML.
// To keep it consistent with our "ID-based update" philosophy, we should probably avoid nuke-and-rebuild if possible,
// OR just rebuild but include the IDs.
// Since I added IDs to the static HTML, rebuilding via innerHTML will wipe them unless I include them in the template string.
// Strategy: Update the dynamic parts (lists) safely, or just update the whole thing but INCLUDE the IDs.
// Let's update the lists directly if possible, or just re-render properly.
// Actually, the best approach for the lists is to find the UL/OL elements.
// But they don't have IDs. TO be fully robust I should have added IDs to the lists too.
// But for now, let's stick to the previous innerHTML approach but ensuring IDs are preserved in the template string.
helpContent.innerHTML = `
<h2>${t.gui.help.about || 'About Twitch Drops Miner'}</h2>
<h2 id="help-about-header">${t.gui.help.about || 'About Twitch Drops Miner'}</h2>
<p>${t.gui.help.about_text || 'This application automatically mines timed Twitch drops without downloading stream data.'}</p>
<h3>${t.gui.help.how_to_use || 'How to Use'}</h3>
<h3 id="help-howto-header">${t.gui.help.how_to_use || 'How to Use'}</h3>
<ol>
${(t.gui.help.how_to_use_items || [
'Login using your Twitch account (OAuth device code flow)',
@@ -1688,7 +1748,7 @@ function applyTranslations(t) {
]).map(item => `<li>${item}</li>`).join('')}
</ol>
<h3>${t.gui.help.features || 'Features'}</h3>
<h3 id="help-features-header">${t.gui.help.features || 'Features'}</h3>
<ul>
${(t.gui.help.features_items || [
'Stream-less drop mining - saves bandwidth',
@@ -1699,7 +1759,7 @@ function applyTranslations(t) {
]).map(item => `<li>${item}</li>`).join('')}
</ul>
<h3>${t.gui.help.important_notes || 'Important Notes'}</h3>
<h3 id="help-notes-header">${t.gui.help.important_notes || 'Important Notes'}</h3>
<ul>
${(t.gui.help.important_notes_items || [
'Do not watch streams on the same account while mining',
@@ -1715,6 +1775,84 @@ function applyTranslations(t) {
}
}
// Update Footer
if (t.gui?.footer) {
const loadingText = t.gui.footer.loading || 'Loading...';
const currentVersionEl = document.getElementById('current-version');
// Only update if it's the specific "Loading..." text to avoid overwriting the fetched version
if (currentVersionEl && currentVersionEl.textContent === 'Loading...') {
currentVersionEl.textContent = loadingText;
}
const footerVersionText = document.getElementById('footer-version-text');
if (footerVersionText) {
const versionLabel = t.gui.footer.version || 'Version:';
const span = document.getElementById('current-version'); // Need to re-fetch or preserve
footerVersionText.textContent = versionLabel + ' ';
// Re-finding the span because textContent wiped it from parent
if (span) footerVersionText.appendChild(span);
}
}
// Update Badges tooltips
if (t.gui?.badges) {
const manualBadge = document.getElementById('manual-mode-badge');
if (manualBadge && t.gui.badges.manual) manualBadge.title = t.gui.badges.manual.title;
const autoBadge = document.getElementById('auto-mode-badge');
if (autoBadge && t.gui.badges.auto) autoBadge.title = t.gui.badges.auto.title;
const proxyBadge = document.getElementById('proxy-indicator');
if (proxyBadge && t.gui.badges.proxy) proxyBadge.title = t.gui.badges.proxy.title; // Note: append logic in updateSettingsUI overrides this
}
// Update Wanted Drops Panel
if (mainTab && t.gui?.wanted) {
// ID: wanted-header
const wantedHeader = document.getElementById('wanted-header');
if (wantedHeader) wantedHeader.textContent = t.gui.wanted.name;
// Re-render wanted items to update empty message
// Since we don't store wanted items in state globally (only receives them), we rely on updateWantedItems triggering render
}
// Update Inventory Filters (re-using existing inventoryTab variable if available, or just querying)
// Note: inventoryTab was declared above in "Update Inventory Status" section
// But since that might be in a different block or not, let's be safe and just query element directly without const redeclaration if it conflicts.
// However, looking at the code, the previous declaration was likely in the same function scope.
// Simplest fix: use the existing element or re-query without 'const' if needed, but best to just use the one we have.
// Actually, looking at the view_file, there was 'const inventoryTab' around line 1639.
// So I should just reuse that variable or use a different name.
if (inventoryTab && t.gui?.inventory?.filters) {
const f = t.gui.inventory.filters;
const updateLabel = (id, text) => {
const el = document.getElementById(id)?.parentElement.querySelector('span');
if (el) el.textContent = text;
};
updateLabel('filter-active', f.active);
updateLabel('filter-not-linked', f.not_linked);
updateLabel('filter-upcoming', f.upcoming);
updateLabel('filter-expired', f.expired);
updateLabel('filter-finished', f.finished);
updateLabel('filter-benefit-item', f.item);
updateLabel('filter-benefit-badge', f.badge);
updateLabel('filter-benefit-emote', f.emote);
updateLabel('filter-benefit-other', f.other);
const clearBtn = document.getElementById('clear-filters-btn');
if (clearBtn) clearBtn.textContent = f.clear;
const searchInput = document.getElementById('games-filter');
if (searchInput) searchInput.placeholder = f.search_placeholder;
// Update Mining Benefit Labels in Settings (re-using inventory filter keys)
// IDs: mining-benefit-item, mining-benefit-badge, mining-benefit-emote, mining-benefit-unknown
updateLabel('mining-benefit-item', f.item);
updateLabel('mining-benefit-badge', f.badge);
updateLabel('mining-benefit-emote', f.emote);
updateLabel('mining-benefit-unknown', f.other);
}
// Update header elements
if (t.gui?.header) {
const languageLabel = document.querySelector('.language-selector span');
@@ -1880,7 +2018,8 @@ function renderWantedItems(tree) {
container.innerHTML = '';
if (!tree || tree.length === 0) {
container.innerHTML = '<p class="empty-message-small">No wanted drops queued...</p>';
const emptyMsg = state.translations.gui?.wanted?.none || 'No wanted drops queued...';
container.innerHTML = `<p class="empty-message-small">${emptyMsg}</p>`;
return;
}