diff --git a/AGENTS.md b/AGENTS.md index af7072d..e59d75d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,6 +146,7 @@ lang/ # Translation JSON files (19 languages) **src/config/settings.py** - Application settings: - Games to watch list (auto-populated from available campaigns if empty) +- Games can also be added manually from the web settings search box - Connection quality multiplier - Language selection - Proxy support (including verification) diff --git a/README.md b/README.md index d426b37..40cf61f 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Visit 👉 **** 1. Open `http://localhost:8080` 2. Login with your Twitch account (OAuth device flow) 3. The miner auto-fetches available campaigns -4. Select games you want to farm → click **Reload** +4. Select games you want to farm, or type a custom game and click **Add Game** → click **Reload** 5. TDM starts mining drops automatically 🎉 📝 **Tip:** diff --git a/lang/Dansk.json b/lang/Dansk.json index 323d711..8a08114 100644 --- a/lang/Dansk.json +++ b/lang/Dansk.json @@ -130,6 +130,8 @@ "games_to_watch": "Spil at se", "games_help": "Vælg spil at se. Rækkefølgen er vigtig - træk for at ændre prioritet (top = højeste prioritet).", "search_games": "Søg spil...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Vælg alle", "deselect_all": "Fravælg alle", "selected_games": "Valgte spil (træk for at ændre rækkefølge)", diff --git a/lang/Deutsch.json b/lang/Deutsch.json index c248f53..5346ec1 100644 --- a/lang/Deutsch.json +++ b/lang/Deutsch.json @@ -131,6 +131,8 @@ "games_to_watch": "Spiele zum Ansehen", "games_help": "Wählen Sie Spiele zum Ansehen aus. Die Reihenfolge ist wichtig - ziehen Sie, um die Priorität zu ändern (oben = höchste Priorität).", "search_games": "Spiele suchen...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Alle auswählen", "deselect_all": "Alle abwählen", "selected_games": "Ausgewählte Spiele (ziehen zum Neuordnen)", diff --git a/lang/English.json b/lang/English.json index 21621de..26d6339 100644 --- a/lang/English.json +++ b/lang/English.json @@ -109,6 +109,8 @@ "games_to_watch": "Games to Watch", "games_help": "Select games to watch. Order matters - drag to reorder priority (top = highest priority).", "search_games": "Search games...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Select All", "deselect_all": "Deselect All", "selected_games": "Selected Games (drag to reorder)", diff --git a/lang/Español.json b/lang/Español.json index f5de985..437d4d7 100644 --- a/lang/Español.json +++ b/lang/Español.json @@ -131,6 +131,8 @@ "games_to_watch": "Juegos para ver", "games_help": "Selecciona los juegos a ver. El orden importa - arrastra para reordenar la prioridad (arriba = mayor prioridad).", "search_games": "Buscar juegos...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Seleccionar todo", "deselect_all": "Deseleccionar todo", "selected_games": "Juegos seleccionados (arrastra para reordenar)", diff --git a/lang/Français.json b/lang/Français.json index 550e02a..12d32fc 100644 --- a/lang/Français.json +++ b/lang/Français.json @@ -131,6 +131,8 @@ "games_to_watch": "Jeux à regarder", "games_help": "Sélectionnez les jeux à regarder. L'ordre est important - faites glisser pour réorganiser la priorité (haut = priorité la plus élevée).", "search_games": "Rechercher des jeux...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Tout sélectionner", "deselect_all": "Tout désélectionner", "selected_games": "Jeux sélectionnés (glisser pour réorganiser)", diff --git a/lang/Indonesian.json b/lang/Indonesian.json index b884735..83be84d 100644 --- a/lang/Indonesian.json +++ b/lang/Indonesian.json @@ -133,6 +133,8 @@ "games_to_watch": "Game untuk Ditonton", "games_help": "Pilih game untuk ditonton. Urutan penting - seret untuk mengatur ulang prioritas (atas = prioritas tertinggi).", "search_games": "Cari game...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Pilih Semua", "deselect_all": "Batalkan Semua", "selected_games": "Game yang Dipilih (seret untuk mengatur ulang)", diff --git a/lang/Italiano.json b/lang/Italiano.json index ecaccc1..5594bd7 100644 --- a/lang/Italiano.json +++ b/lang/Italiano.json @@ -133,6 +133,8 @@ "games_to_watch": "Giochi da guardare", "games_help": "Seleziona i giochi da guardare. L'ordine è importante - trascina per riordinare la priorità (in alto = priorità massima).", "search_games": "Cerca giochi...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Seleziona tutto", "deselect_all": "Deseleziona tutto", "selected_games": "Giochi selezionati (trascina per riordinare)", diff --git a/lang/Nederlandse.json b/lang/Nederlandse.json index 2eba6a9..84751aa 100644 --- a/lang/Nederlandse.json +++ b/lang/Nederlandse.json @@ -133,6 +133,8 @@ "games_to_watch": "Games om te kijken", "games_help": "Selecteer games om te kijken. Volgorde is belangrijk - sleep om prioriteit te wijzigen (bovenaan = hoogste prioriteit).", "search_games": "Zoek games...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Alles selecteren", "deselect_all": "Alles deselecteren", "selected_games": "Geselecteerde games (sleep om te herschikken)", diff --git a/lang/Polski.json b/lang/Polski.json index 7de230d..5f22e47 100644 --- a/lang/Polski.json +++ b/lang/Polski.json @@ -134,6 +134,8 @@ "games_to_watch": "Gry do oglądania", "games_help": "Wybierz gry do oglądania. Kolejność ma znaczenie - przeciągnij, aby zmienić priorytet (góra = najwyższy priorytet).", "search_games": "Szukaj gier...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Zaznacz wszystko", "deselect_all": "Odznacz wszystko", "selected_games": "Wybrane gry (przeciągnij, aby zmienić kolejność)", diff --git a/lang/Português.json b/lang/Português.json index b81eab1..735dd3b 100644 --- a/lang/Português.json +++ b/lang/Português.json @@ -133,6 +133,8 @@ "games_to_watch": "Jogos para assistir", "games_help": "Selecione os jogos para assistir. A ordem importa - arraste para reordenar a prioridade (topo = maior prioridade).", "search_games": "Procurar jogos...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Selecionar tudo", "deselect_all": "Desmarcar tudo", "selected_games": "Jogos selecionados (arraste para reordenar)", diff --git a/lang/Română.json b/lang/Română.json index 7604bb9..1448129 100644 --- a/lang/Română.json +++ b/lang/Română.json @@ -133,6 +133,8 @@ "games_to_watch": "Jocuri de urmărit", "games_help": "Selectați jocurile de urmărit. Ordinea contează - trageți pentru a reordona prioritatea (sus = prioritate maximă).", "search_games": "Căutați jocuri...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Selectați tot", "deselect_all": "Deselectați tot", "selected_games": "Jocuri selectate (trageți pentru a reordona)", diff --git a/lang/Türkçe.json b/lang/Türkçe.json index 2a57443..8da9834 100644 --- a/lang/Türkçe.json +++ b/lang/Türkçe.json @@ -133,6 +133,8 @@ "games_to_watch": "İzlenecek Oyunlar", "games_help": "İzlenecek oyunları seçin. Sıralama önemlidir - önceliği yeniden sıralamak için sürükleyin (üst = en yüksek öncelik).", "search_games": "Oyun ara...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Tümünü seç", "deselect_all": "Tümünü kaldır", "selected_games": "Seçilen oyunlar (yeniden sıralamak için sürükleyin)", diff --git a/lang/Čeština.json b/lang/Čeština.json index 5bdb431..43d0d43 100644 --- a/lang/Čeština.json +++ b/lang/Čeština.json @@ -133,6 +133,8 @@ "games_to_watch": "Hry ke sledování", "games_help": "Vyberte hry ke sledování. Záleží na pořadí - přetáhněte pro změnu priority (nahoře = nejvyšší priorita).", "search_games": "Hledat hry...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Vybrat vše", "deselect_all": "Zrušit výběr", "selected_games": "Vybrané hry (přetáhněte pro změnu pořadí)", diff --git a/lang/Русский.json b/lang/Русский.json index 133fc3f..5c9defe 100644 --- a/lang/Русский.json +++ b/lang/Русский.json @@ -134,6 +134,8 @@ "games_to_watch": "Игры для просмотра", "games_help": "Выберите игры для просмотра. Порядок имеет значение - перетащите для изменения приоритета (вверху = наивысший приоритет).", "search_games": "Поиск игр...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Выбрать все", "deselect_all": "Снять выбор", "selected_games": "Выбранные игры (перетащите для изменения порядка)", diff --git a/lang/Українська.json b/lang/Українська.json index 9fabc9a..c39159a 100644 --- a/lang/Українська.json +++ b/lang/Українська.json @@ -133,6 +133,8 @@ "games_to_watch": "Ігри для перегляду", "games_help": "Виберіть ігри для перегляду. Порядок має значення - перетягніть для зміни пріоритету (вгорі = найвищий пріоритет).", "search_games": "Пошук ігор...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "Вибрати все", "deselect_all": "Зняти вибір", "selected_games": "Вибрані ігри (перетягніть для зміни порядку)", diff --git a/lang/العربية.json b/lang/العربية.json index 641da5d..71049e3 100644 --- a/lang/العربية.json +++ b/lang/العربية.json @@ -133,6 +133,8 @@ "games_to_watch": "ألعاب للمشاهدة", "games_help": "حدد الألعاب للمشاهدة. الترتيب مهم - اسحب لإعادة ترتيب الأولوية (الأعلى = أعلى أولوية).", "search_games": "البحث عن الألعاب...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "تحديد الكل", "deselect_all": "إلغاء تحديد الكل", "selected_games": "الألعاب المحددة (اسحب لإعادة الترتيب)", diff --git a/lang/日本語.json b/lang/日本語.json index d598a56..471970d 100644 --- a/lang/日本語.json +++ b/lang/日本語.json @@ -133,6 +133,8 @@ "games_to_watch": "視聴するゲーム", "games_help": "視聴するゲームを選択してください。順序が重要です - ドラッグして優先順位を並べ替えます(上 = 最高優先度)。", "search_games": "ゲームを検索...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "すべて選択", "deselect_all": "すべて選択解除", "selected_games": "選択されたゲーム(ドラッグして並べ替え)", diff --git a/lang/简体中文.json b/lang/简体中文.json index 3c1d634..88b33d0 100644 --- a/lang/简体中文.json +++ b/lang/简体中文.json @@ -130,6 +130,8 @@ "games_to_watch": "观看游戏", "games_help": "选择要观看的游戏。顺序很重要 - 拖动以重新排序优先级(顶部 = 最高优先级)。", "search_games": "搜索游戏...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "全选", "deselect_all": "取消全选", "selected_games": "已选游戏(拖动以重新排序)", diff --git a/lang/繁體中文.json b/lang/繁體中文.json index 29952f8..c744273 100644 --- a/lang/繁體中文.json +++ b/lang/繁體中文.json @@ -133,6 +133,8 @@ "games_to_watch": "觀看遊戲", "games_help": "選擇要觀看的遊戲。順序很重要 - 拖曳以重新排序優先級(頂部 = 最高優先級)。", "search_games": "搜尋遊戲...", + "add_game": "Add Game", + "add_game_hint": " Click \"Add Game\" to add it manually.", "select_all": "全選", "deselect_all": "取消全選", "selected_games": "已選遊戲(拖曳以重新排序)", diff --git a/src/i18n/translator.py b/src/i18n/translator.py index 6f7575a..a0c6a6a 100644 --- a/src/i18n/translator.py +++ b/src/i18n/translator.py @@ -177,6 +177,8 @@ class GUISettings(TypedDict): games_to_watch: str games_help: str search_games: str + add_game: str + add_game_hint: str select_all: str deselect_all: str selected_games: str diff --git a/tests/test_benefit_filter.py b/tests/test_benefit_filter.py index 683490f..c21b9ad 100644 --- a/tests/test_benefit_filter.py +++ b/tests/test_benefit_filter.py @@ -1,4 +1,5 @@ import unittest +from unittest.mock import MagicMock from src.models.benefit import Benefit from src.models.campaign import DropsCampaign @@ -99,6 +100,29 @@ class TestBenefitFilter(unittest.TestCase): drop2.has_wanted_unclaimed_benefits.return_value = False self.assertFalse(campaign.has_wanted_unclaimed_benefits(allowed)) + def test_campaign_preserves_account_link_status(self): + campaign_data = { + "id": "campaign-1", + "name": "Test Campaign", + "game": { + "id": "1", + "name": "Test Game", + "displayName": "Test Game", + "boxArtURL": "https://example.test/game-{width}x{height}.jpg", + }, + "self": {"isAccountConnected": False}, + "accountLinkURL": "https://example.test/link", + "startAt": "2026-01-01T00:00:00Z", + "endAt": "2026-01-02T00:00:00Z", + "status": "ACTIVE", + "allow": {"channels": [], "isEnabled": True}, + "timeBasedDrops": [], + } + + campaign = DropsCampaign(MagicMock(), campaign_data, {}) + + self.assertFalse(campaign.linked) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_translations.py b/tests/test_translations.py new file mode 100644 index 0000000..24e7ae0 --- /dev/null +++ b/tests/test_translations.py @@ -0,0 +1,22 @@ +import json + +from src.config import LANG_PATH +from src.i18n.translator import GUISettings + + +def test_all_language_settings_include_gui_settings_schema_keys(): + required_settings_keys = set(GUISettings.__annotations__) + english_settings = json.loads((LANG_PATH / "English.json").read_text(encoding="utf-8"))["gui"][ + "settings" + ] + missing_by_language = {} + + for filepath in LANG_PATH.glob("*.json"): + translation = json.loads(filepath.read_text(encoding="utf-8")) + settings = translation["gui"]["settings"] + missing = sorted(required_settings_keys - set(settings)) + if missing: + missing_by_language[filepath.name] = missing + + assert sorted(set(english_settings) - required_settings_keys) == [] + assert missing_by_language == {} diff --git a/web/index.html b/web/index.html index fa36ed6..f2446b9 100644 --- a/web/index.html +++ b/web/index.html @@ -242,6 +242,7 @@
+
diff --git a/web/static/app.js b/web/static/app.js index 2ce8b96..722ead8 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -1228,7 +1228,8 @@ function renderAvailableGames(games, filterText) { if (games.length === 0) { if (filterText) { const emptyMsg = t.gui?.settings?.no_games_match || 'No games match your search.'; - container.replaceChildren(makeElement('p', { class: 'empty-message' }, emptyMsg)); + const addHint = t.gui?.settings?.add_game_hint || ' Click "Add Game" to add it manually.'; + container.replaceChildren(makeElement('p', { class: 'empty-message' }, `${emptyMsg}${addHint}`)); } else { const emptyMsg = t.gui?.settings?.all_games_selected || 'All games are selected or no games available.'; container.replaceChildren(makeElement('p', { class: 'empty-message' }, emptyMsg)); @@ -1352,6 +1353,37 @@ function deselectAllGames() { saveSettings(); } +function addGameFromSearch() { + const searchInput = document.getElementById('games-filter'); + const gameName = searchInput.value.trim(); + + if (!gameName) { + return; + } + + const games = state.settings.games_to_watch || []; + + // Check if already selected + if (games.includes(gameName)) { + searchInput.value = ''; // Clear input if already added + renderGamesToWatch(); // Just re-render to clear any filtering state if needed + return; + } + + // Add to selected games + games.push(gameName); + state.settings.games_to_watch = games; + + // Add to available games set so it shows up in lists + availableGames.add(gameName); + + // Clear search and update UI + searchInput.value = ''; + renderGamesToWatch(); + renderChannels(); + saveSettings(); +} + function flashTitle() { const originalTitle = document.title; let count = 0; @@ -1697,6 +1729,9 @@ function applyTranslations(t) { const deselectAllBtn = document.getElementById('deselect-all-btn'); if (deselectAllBtn) deselectAllBtn.textContent = t.gui.settings.deselect_all; + const addGameBtn = document.getElementById('add-game-btn'); + if (addGameBtn && t.gui.settings.add_game) addGameBtn.textContent = t.gui.settings.add_game; + const selectedGamesHeader = settingsTab.querySelector('.selected-games h3'); if (selectedGamesHeader) selectedGamesHeader.textContent = t.gui.settings.selected_games; @@ -1940,6 +1975,7 @@ document.addEventListener('DOMContentLoaded', () => { // Games to watch management document.getElementById('select-all-btn').addEventListener('click', selectAllGames); document.getElementById('deselect-all-btn').addEventListener('click', deselectAllGames); + document.getElementById('add-game-btn').addEventListener('click', addGameFromSearch); document.getElementById('games-filter').addEventListener('input', renderGamesToWatch); // Inventory filters diff --git a/web/static/styles.css b/web/static/styles.css index d638015..02e2995 100644 --- a/web/static/styles.css +++ b/web/static/styles.css @@ -1170,7 +1170,7 @@ header h1 { margin-bottom: 20px; } -.games-filter input { +.games-filter input[type="text"] { width: 100%; padding: 10px; border: 1px solid var(--border-color); @@ -1179,6 +1179,7 @@ header h1 { color: var(--text-primary); font-size: 14px; margin-bottom: 10px; + margin-left: 0; } .filter-actions {