mirror of
https://github.com/rangermix/TwitchDropsMiner.git
synced 2026-05-26 07:08:04 +00:00
translation update (#24)
* update agent instructions * - update translations - add id to translation anchors to ensure correct update
This commit is contained in:
434
AGENTS.md
Normal file
434
AGENTS.md
Normal 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
123
CLAUDE.md
@@ -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
117
GEMINI.md
@@ -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)`
|
||||
|
||||
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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ă..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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ě..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "Нет желаемых дропов в очереди..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "Немає бажаних дропів у черзі..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 مطلوبة في قائمة الانتظار..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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はありません..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "队列中没有期望的掉落..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "隊列中沒有期望的掉落..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user