您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Random game picker with provider filter, scan with live count update, yes/maybe/no prompt showing game name, random favorite picker, and UI enhancements on Betfred All Games page with SPA navigation detection.
当前为
// ==UserScript== // @name Betfred All Games Random + Provider Filter + Random Favorite // @namespace http://tampermonkey.net/ // @version 1.0.4 // @description Random game picker with provider filter, scan with live count update, yes/maybe/no prompt showing game name, random favorite picker, and UI enhancements on Betfred All Games page with SPA navigation detection. // @author The Devil // @match *://www.betfred.com/* // @match *://betfred.com/* // @run-at document-idle // @license MIT // @grant none // ==/UserScript== (function () { 'use strict'; // --- Constants and Selectors --- const GAME_LINK_SELECTOR = 'a._19pd3t9s[href^="/games/play/"]'; const INFO_BTN_SELECTOR = 'img._zdxht7[alt="More Info"]'; const CLOSE_OVERLAY_SELECTOR = 'span._1ye7m8b[data-actionable][role="button"]'; const PLAYED_GAMES_KEY = 'betfred_played_games'; const MAPPING_KEY = 'betfred_game_to_provider'; const YES_NO_MAYBE_KEY = 'betfred_play_again_feedback'; const SCAN_PROGRESS_KEY = 'betfred_scan_progress'; // --- Provider Aliases --- const providerAliases = { '1x2 Gaming': '1x2 Gaming', '4ThePlayer': '4ThePlayer', 'AGS': 'AGS', 'Alchemy Games': 'Alchemy Gaming', 'Alchemy Gaming': 'Alchemy Gaming', 'All For One Studios': 'All For One Studios', 'Area Vegas': 'Area Vegas', 'Aurum Signature Studios': 'Aurum Signature Studios', 'BTG': 'Big Time Gaming', 'Bang Bang': 'Bang Bang', 'Big Time Gaming': 'Big Time Gaming', 'Blue Ring Studios': 'Blue Ring Studios', 'Blueprint': 'Blueprint', 'Boomerang': 'Boomerang', 'Buck Stakes Entertainment': 'Buck Stakes Entertainment', 'BulletProof': 'BulletProof', 'Bulletproof': 'BulletProof', 'Chance Interactive': 'Chance Interactive', 'Circular Arrow': 'Circular Arrow', 'Coin Machine Gaming': 'Coin Machine Gaming', 'Crazy Tooth Studio': 'Crazy Tooth Studios', 'Crazy Tooth Studios': 'Crazy Tooth Studios', 'DWG': 'DWG', 'ELK Studio': 'ELK Studios', 'ELK Studios': 'ELK Studios', 'Fortune Factory': 'Fortune Factory Studios', 'Fortune Factory Studios': 'Fortune Factory Studios', 'Foxium Studios': 'Foxium Studios', 'G Games': 'G Games', 'G Gaming': 'G Games', 'Game Evolution': 'Game Evolution', 'GameBurger Studios': 'Gameburger Studios', 'Gameburger Studios': 'Gameburger Studios', 'Games Global': 'Games Global', 'Gold Coin Studios': 'Gold Coin Studios', 'Golden Rock Studios': 'Golden Rock Studios', 'Hacksaw Gaming': 'Hacksaw Gaming', 'Hammertime Games': 'Hammertime Games', 'High Limit Studio': 'High Limit Studio', 'Hungry Bear Gaming': 'Hungry Bear Gaming', 'IGT': 'IGT', 'INO Games': 'INO Games', 'Infinity Dragon': 'Infinity Dragon Studios', 'Infinity Dragon Studios': 'Infinity Dragon Studios', 'Inspired': 'Inspired', 'Jelly': 'Jelly', 'Just For The Win': 'Just For The Win', 'Light & Wonder': 'Light & Wonder', 'Lightning Box': 'Lightning Box', 'NYX - Pragmatic': 'Pragmatic Play', 'Nailed It Games': 'Nailed It! Games', 'Nailed It! Games': 'Nailed It! Games', 'Nailed it! Games': 'Nailed It! Games', 'Neon Valley Studios': 'Neon Valley Studios', 'NetEnt': 'NetEnt', 'Netent': 'NetEnt', 'NoLimit City': 'NoLimit City', 'Nolimit City': 'NoLimit City', 'Northern Lights': 'Northern Lights Gaming', 'Northern Lights Gaming': 'Northern Lights Gaming', 'Old Skool Studios': 'Old Skool Studios', 'Oros Gaming': 'Oros Gaming', 'Pear Fiction Studios': 'Pear Fiction Studios', 'Peter & Sons': 'Peter & Sons', "Play'n Go": "Play'n Go", 'Playtech': 'Playtech', 'Pragmatic': 'Pragmatic Play', 'Pragmatic Play': 'Pragmatic Play', 'Prospect Gaming': 'Prospect Gaming', 'Realistic': 'Realistic', 'Red TIger': 'Red Tiger', 'Red Tiger': 'Red Tiger', 'RedTiger': 'Red Tiger', 'Reel Paly': 'Reel Play', 'Reel Play': 'Reel Play', 'ReelPlay': 'Reel Play', 'Reflex Gaming': 'Reflex Gaming', 'Scientific Games': 'Scientific Games', 'Slingshot Studios': 'Slingshot Studios', 'Snowborn Games': 'Snowborn Studios', 'Snowborn Studios': 'Snowborn Studios', 'Spin On': 'Spin Play Games', 'Spin Play Games': 'Spin Play Games', 'SpinPlay Games': 'Spin Play Games', 'Stormcraft Studios': 'Stormcraft Studios', 'Switch Studios': 'Switch Studios', 'Thunderkick': 'Thunderkick', 'Triple Edge Studios': 'Triple Edge Studios', 'Wishbone Games': 'Wishbone Games', 'Wizard Games': 'Wizard Games', 'Yggdrasil': 'Yggdrasil', 'iSoftBet': 'iSoftBet' }; // --- Variables --- let playedGames = JSON.parse(localStorage.getItem(PLAYED_GAMES_KEY) || '{}'); let providerData = JSON.parse(localStorage.getItem(MAPPING_KEY) || '{}'); let playAgainFeedback = JSON.parse(localStorage.getItem(YES_NO_MAYBE_KEY) || '{}'); let scanProgress = JSON.parse(localStorage.getItem(SCAN_PROGRESS_KEY) || '{"index":0,"completed":false}'); let selectedProvider = ''; let container, optionsPanel; let randomBtn, randomFavoriteBtn, providerFilterSelect, scanBtn, resetBtn, cancelScanBtn, scanToggleBtn, scanButtonsContainer; let scanProgressText = null; let initialized = false; let scanning = false; let scanCancelRequested = false; let gameListObserver = null; let allGamesLinkObserver = null; let addOptionsButtonScheduled = false; // --- Utility --- function normalizeProvider(name) { return providerAliases[name.trim()] || name.trim(); } function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Wait for element with timeout function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const interval = 100; let elapsed = 0; const check = () => { const el = document.querySelector(selector); if (el) return resolve(el); elapsed += interval; if (elapsed >= timeout) reject(`Element ${selector} not found`); else setTimeout(check, interval); }; check(); }); } // Get all game container divs containing the game links function getAllGameElements() { return [...document.querySelectorAll(GAME_LINK_SELECTOR)].map(a => a.closest('div')); } // Normalize path from href for mapping keys function getGamePath(href) { try { return new URL(href, location.origin).pathname; } catch { return ''; } } // Save data to localStorage function saveData() { localStorage.setItem(PLAYED_GAMES_KEY, JSON.stringify(playedGames)); localStorage.setItem(MAPPING_KEY, JSON.stringify(providerData)); localStorage.setItem(YES_NO_MAYBE_KEY, JSON.stringify(playAgainFeedback)); localStorage.setItem(SCAN_PROGRESS_KEY, JSON.stringify(scanProgress)); } // --- SPA URL Change Detection --- function onSPAUrlChange(callback) { let lastUrl = location.href; const pushState = history.pushState; history.pushState = function () { pushState.apply(this, arguments); callback(location.href); }; const replaceState = history.replaceState; history.replaceState = function () { replaceState.apply(this, arguments); callback(location.href); }; window.addEventListener('popstate', () => { callback(location.href); }); setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; callback(location.href); } }, 500); } // --- Title Prettifier --- function prettifyTitle(title) { return title .replace(/[_\-]+/g, ' ') // Replace underscores/dashes with space .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase .replace(/(\D)(\d)/g, '$1 $2') // Space before numbers .replace(/(\d)([A-Za-z])/g, '$1 $2') // Space after numbers .replace(/\b(\d+)k\b/gi, (_, num) => `${num}K`) // 4k → 4K .replace(/\b(\d{4,})\b/g, n => Number(n).toLocaleString()) // 4000 → 4,000 .replace(/([a-z])([A-Z])/g, '$1 $2') // Again handle PascalCase .replace(/\s+/g, ' ') // Collapse extra spaces .trim() .replace(/\b\w/g, c => c.toUpperCase()); // Capitalize first letter of each word } async function initialize() { if (initialized) return; initialized = true; // Reload saved data from localStorage playedGames = JSON.parse(localStorage.getItem(PLAYED_GAMES_KEY) || '{}'); providerData = JSON.parse(localStorage.getItem(MAPPING_KEY) || '{}'); playAgainFeedback = JSON.parse(localStorage.getItem(YES_NO_MAYBE_KEY) || '{}'); scanProgress = JSON.parse(localStorage.getItem(SCAN_PROGRESS_KEY) || '{"index":0,"completed":false}'); createOptionsPanel(); await addOptionsButton(); observeAllGamesLink(); filterGamesByProvider(); updateRandomButtonText(); updateScanButtonText(); } // Initialize on first load if on All Games page if (location.pathname === '/games/category/all-games') { waitForElement(GAME_LINK_SELECTOR, 15000) .then(() => { initialize(); observeGameListChanges(); }) .catch(e => { console.warn('Games did not load on initial page load:', e); }); } onSPAUrlChange(async (newUrl) => { const urlPath = new URL(newUrl).pathname; if (urlPath === '/games/category/all-games') { try { await waitForElement(GAME_LINK_SELECTOR, 15000); await waitForElement('a._1rwiby3._mdg8s6x[href="/games/category/all-games"]', 10000); observeAllGamesLink(); if (!initialized) { playedGames = JSON.parse(localStorage.getItem(PLAYED_GAMES_KEY) || '{}'); providerData = JSON.parse(localStorage.getItem(MAPPING_KEY) || '{}'); playAgainFeedback = JSON.parse(localStorage.getItem(YES_NO_MAYBE_KEY) || '{}'); scanProgress = JSON.parse(localStorage.getItem(SCAN_PROGRESS_KEY) || '{"index":0,"completed":false}'); await initialize(); observeGameListChanges(); } else { if (optionsPanel) optionsPanel.style.display = 'block'; if (container) container.style.display = 'inline-block'; filterGamesByProvider(); updateProviderDropdownCounts(); updateRandomButtonText(); updateScanButtonText(); } } catch (err) { initialized = false; if (optionsPanel) optionsPanel.style.display = 'none'; if (container) container.style.display = 'none'; disconnectGameListObserver(); console.warn('Game elements did not load on SPA navigation:', err); } } else { if (initialized) { initialized = false; if (optionsPanel) optionsPanel.style.display = 'none'; if (container) container.style.display = 'none'; disconnectGameListObserver(); } } }); // --- UI Creation --- // Create the floating options panel with controls function createOptionsPanel() { optionsPanel = document.createElement('div'); optionsPanel.style.position = 'absolute'; optionsPanel.style.backgroundColor = '#0a5bab'; optionsPanel.style.color = '#fff'; optionsPanel.style.padding = '10px'; optionsPanel.style.borderRadius = '5px'; optionsPanel.style.display = 'none'; optionsPanel.style.zIndex = '10000'; optionsPanel.style.width = '340px'; optionsPanel.style.fontFamily = 'Arial, sans-serif'; optionsPanel.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)'; optionsPanel.style.userSelect = 'none'; // Random Game Button randomBtn = document.createElement('button'); randomBtn.textContent = selectedProvider ? `🎲 Random ${selectedProvider} 🎲` : '🎲 Random Game 🎲'; randomBtn.style.width = '100%'; randomBtn.style.marginBottom = '10px'; styleButton(randomBtn, '#1877f2', '#005bb5'); randomBtn.onclick = pickRandomGame; optionsPanel.appendChild(randomBtn); // Random Favorite Button (games marked "yes") randomFavoriteBtn = document.createElement('button'); randomFavoriteBtn.textContent = selectedProvider ? `🎯 Random Favorite ${selectedProvider} 🎯` : '🎯 Random Favorite 🎯'; randomFavoriteBtn.style.width = '100%'; randomFavoriteBtn.style.marginBottom = '10px'; styleButton(randomFavoriteBtn, '#28a745', '#1e7e34'); randomFavoriteBtn.onclick = pickRandomFavoriteGame; optionsPanel.appendChild(randomFavoriteBtn); // Provider filter dropdown providerFilterSelect = document.createElement('select'); providerFilterSelect.style.width = '100%'; providerFilterSelect.style.margin = '10px 0 15px 0'; providerFilterSelect.style.padding = '6px'; providerFilterSelect.style.borderRadius = '3px'; providerFilterSelect.style.fontSize = '14px'; providerFilterSelect.style.cursor = 'pointer'; providerFilterSelect.style.color = '#000'; // Add default option showing total count of all games const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = `Select Provider (All Games: ${getAllGameElements().length})`; providerFilterSelect.appendChild(defaultOption); // Add all unique providers sorted alphabetically const uniqueProviders = [...new Set(Object.values(providerAliases))].sort((a, b) => a.localeCompare(b)); uniqueProviders.forEach(provider => { const option = document.createElement('option'); option.value = provider; option.textContent = provider + ' (0)'; providerFilterSelect.appendChild(option); }); providerFilterSelect.onchange = () => { selectedProvider = providerFilterSelect.value; filterGamesByProvider(); updateRandomButtonText(); updateScanButtonText(); }; optionsPanel.appendChild(providerFilterSelect); // Create container to hold scan and reset buttons, hidden by default scanButtonsContainer = document.createElement('div'); scanButtonsContainer.style.display = 'none'; scanButtonsContainer.style.justifyContent = 'space-between'; scanButtonsContainer.style.gap = '10px'; scanButtonsContainer.style.marginBottom = '10px'; // Scan Button scanBtn = document.createElement('button'); updateScanButtonText(); styleButton(scanBtn, '#e03e2f', '#b52a1f'); scanBtn.style.flexGrow = '1'; scanBtn.onclick = () => { if (scanning) return; scanCancelRequested = false; const visibleGames = getAllGameElements().filter(div => div.style.display !== 'none'); const totalVisible = visibleGames.length; const totalKnown = Object.keys(providerData).length; if (totalVisible === 0) { // No games visible: full scan needed, reset progress scanProgress = { index: 0, completed: false }; saveData(); scanProviders(); } else if (totalVisible !== totalKnown) { // Game list changed, update scan (resume or start new) scanProviders(); } else { // No changes, nothing to scan alert('No new games to scan. Provider data is up to date.'); } }; scanButtonsContainer.appendChild(scanBtn); // Cancel Scan Button (hidden initially) cancelScanBtn = document.createElement('button'); cancelScanBtn.textContent = 'Cancel'; styleButton(cancelScanBtn, '#555', '#333'); cancelScanBtn.style.flexGrow = '1'; cancelScanBtn.style.display = 'none'; cancelScanBtn.onclick = () => { scanCancelRequested = true; }; scanButtonsContainer.appendChild(cancelScanBtn); // Reset Button resetBtn = document.createElement('button'); resetBtn.textContent = 'Reset Data'; resetBtn.style.flexGrow = '1'; styleButton(resetBtn, '#888', '#555'); resetBtn.onclick = () => { showResetOptionsPrompt(); }; scanButtonsContainer.appendChild(resetBtn); function showResetOptionsPrompt() { if (document.getElementById('resetOptionsPrompt')) return; // Prevent multiple prompts const overlay = document.createElement('div'); overlay.id = 'resetOptionsPrompt'; overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.right = '0'; overlay.style.bottom = '0'; overlay.style.backgroundColor = 'rgba(0,0,0,0.7)'; overlay.style.zIndex = '12000'; overlay.style.display = 'flex'; overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; const panel = document.createElement('div'); panel.style.backgroundColor = '#0a5bab'; panel.style.color = '#fff'; panel.style.padding = '20px 30px'; panel.style.borderRadius = '10px'; panel.style.textAlign = 'center'; panel.style.maxWidth = '400px'; panel.style.fontFamily = 'Arial, sans-serif'; const message = document.createElement('p'); message.textContent = 'What would you like to reset? Remember to always refresh the page after.'; message.style.marginBottom = '20px'; panel.appendChild(message); const buttonsDiv = document.createElement('div'); buttonsDiv.style.display = 'flex'; buttonsDiv.style.justifyContent = 'space-around'; // Reset Everything button const resetAllBtn = document.createElement('button'); resetAllBtn.textContent = 'Reset Everything'; styleButton(resetAllBtn, '#dc3545', '#a71d2a'); resetAllBtn.onclick = () => { playedGames = {}; providerData = {}; playAgainFeedback = {}; scanProgress = { index: 0, completed: false }; saveData(); updateProviderDropdownCounts(); filterGamesByProvider(); closePrompt(); alert('All data has been reset.'); }; buttonsDiv.appendChild(resetAllBtn); // Reset Feedback Only button const resetFeedbackBtn = document.createElement('button'); resetFeedbackBtn.textContent = 'Reset Feedback Only'; styleButton(resetFeedbackBtn, '#ffc107', '#d39e00'); resetFeedbackBtn.onclick = () => { playAgainFeedback = {}; saveData(); updateProviderDropdownCounts(); filterGamesByProvider(); closePrompt(); alert('Game feedback has been reset please refresh page for it to update.'); }; buttonsDiv.appendChild(resetFeedbackBtn); // Cancel button const cancelBtn = document.createElement('button'); cancelBtn.textContent = 'Cancel'; styleButton(cancelBtn, '#888', '#555'); cancelBtn.onclick = () => { closePrompt(); }; buttonsDiv.appendChild(cancelBtn); panel.appendChild(buttonsDiv); overlay.appendChild(panel); document.body.appendChild(overlay); function closePrompt() { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); } } scanButtonsContainer.appendChild(resetBtn); optionsPanel.appendChild(scanButtonsContainer); // Scan Progress Text scanProgressText = document.createElement('div'); scanProgressText.style.color = '#fff'; scanProgressText.style.fontSize = '14px'; scanProgressText.style.marginBottom = '8px'; scanProgressText.textContent = ''; optionsPanel.appendChild(scanProgressText); // Add toggle arrow to show/hide scan/reset buttons, centered const scanToggleContainer = document.createElement('div'); scanToggleContainer.style.width = '100%'; scanToggleContainer.style.display = 'flex'; scanToggleContainer.style.justifyContent = 'center'; scanToggleContainer.style.marginBottom = '10px'; scanToggleBtn = document.createElement('span'); scanToggleBtn.textContent = '▼'; scanToggleBtn.style.cursor = 'pointer'; scanToggleBtn.style.userSelect = 'none'; scanToggleBtn.style.fontWeight = 'bold'; scanToggleBtn.style.fontSize = '18px'; scanToggleBtn.style.lineHeight = '1'; scanToggleBtn.onclick = () => { if (scanButtonsContainer.style.display === 'none') { scanButtonsContainer.style.display = 'flex'; scanToggleBtn.textContent = '▲'; } else { scanButtonsContainer.style.display = 'none'; scanToggleBtn.textContent = '▼'; } }; scanToggleContainer.appendChild(scanToggleBtn); optionsPanel.appendChild(scanToggleContainer); document.body.appendChild(optionsPanel); } // Button styling helper function styleButton(button, bgColor, hoverColor) { button.style.cursor = 'pointer'; button.style.fontWeight = 'bold'; button.style.padding = '8px'; button.style.border = 'none'; button.style.borderRadius = '3px'; button.style.backgroundColor = bgColor; button.style.color = '#fff'; button.style.transition = 'background-color 0.3s ease'; button.onmouseenter = () => (button.style.backgroundColor = hoverColor); button.onmouseleave = () => (button.style.backgroundColor = bgColor); } // Add the "Random Game Options" button next to "All Games" link async function addOptionsButton() { const selector = 'a._1rwiby3._mdg8s6x[href="/games/category/all-games"]'; try { const allGamesLink = await waitForElement(selector, 10000); // wait up to 10s if (!allGamesLink) return; if (container && document.body.contains(container)) return; container = document.createElement('div'); container.style.display = 'inline-block'; container.style.marginLeft = '10px'; const btn = document.createElement('button'); btn.textContent = 'Random Game Options'; btn.style.cursor = 'pointer'; btn.style.fontWeight = 'bold'; btn.style.padding = '6px 12px'; btn.style.borderRadius = '4px'; btn.style.border = 'none'; btn.style.backgroundColor = '#1877f2'; btn.style.color = '#fff'; btn.style.transition = 'background-color 0.3s ease'; btn.onmouseenter = () => (btn.style.backgroundColor = '#005bb5'); btn.onmouseleave = () => (btn.style.backgroundColor = '#1877f2'); container.appendChild(btn); allGamesLink.parentNode.insertBefore(container, allGamesLink.nextSibling); btn.addEventListener('click', () => { if (!optionsPanel) return; if (optionsPanel.style.display === 'none') { const rect = btn.getBoundingClientRect(); optionsPanel.style.top = rect.bottom + window.scrollY + 5 + 'px'; optionsPanel.style.left = rect.left + window.scrollX + 'px'; optionsPanel.style.display = 'block'; updateProviderDropdownCounts(); updateRandomButtonText(); updateScanButtonText(); } else { optionsPanel.style.display = 'none'; } }); } catch (e) { console.warn('Failed to add options button:', e); } } function observeGameListChanges() { const gameListContainer = document.querySelector('div._dmn8hc[data-actionable="GamesCategoryPage.all-games.GameList"]'); if (!gameListContainer) return; if (gameListObserver) { gameListObserver.disconnect(); } gameListObserver = new MutationObserver(() => { // When games list changes, refresh UI and counts filterGamesByProvider(); updateProviderDropdownCounts(); updateRandomButtonText(); updateScanButtonText(); }); gameListObserver.observe(gameListContainer, { childList: true, subtree: true, }); } function disconnectGameListObserver() { if (gameListObserver) { gameListObserver.disconnect(); gameListObserver = null; } } function observeAllGamesLink() { const parent = document.querySelector('nav') || document.body; if (!parent) return; if (allGamesLinkObserver) allGamesLinkObserver.disconnect(); allGamesLinkObserver = new MutationObserver(() => { if (!addOptionsButtonScheduled) { addOptionsButtonScheduled = true; setTimeout(async () => { addOptionsButtonScheduled = false; if (!container || !document.body.contains(container)) { const allGamesLink = document.querySelector('a._1rwiby3._mdg8s6x[href="/games/category/all-games"]'); if (allGamesLink) { await addOptionsButton(); } } }, 200); } }); allGamesLinkObserver.observe(parent, { childList: true, subtree: true }); } // --- Filtering Games by Provider --- function filterGamesByProvider() { const games = getAllGameElements(); games.forEach(gameDiv => { const gameLink = gameDiv.querySelector(GAME_LINK_SELECTOR); if (!gameLink) return; const path = getGamePath(gameLink.href); const provider = (providerData[path] && providerData[path].provider) || ''; if (selectedProvider === '' || provider === selectedProvider) { gameDiv.style.display = ''; } else { gameDiv.style.display = 'none'; } }); } // --- Update provider dropdown counts --- function updateProviderDropdownCounts() { const games = getAllGameElements(); const counts = {}; games.forEach(gameDiv => { const gameLink = gameDiv.querySelector(GAME_LINK_SELECTOR); if (!gameLink) return; const path = getGamePath(gameLink.href); const provider = (providerData[path] && providerData[path].provider) || 'Unknown'; counts[provider] = (counts[provider] || 0) + 1; }); // Update default option with total games count if (providerFilterSelect && providerFilterSelect.options.length) { providerFilterSelect.options[0].textContent = `Select Provider (All Games: ${games.length})`; for (let i = 1; i < providerFilterSelect.options.length; i++) { const opt = providerFilterSelect.options[i]; const provider = opt.value; const count = counts[provider] || 0; opt.textContent = `${provider} (${count})`; } } } // --- Update Random Button Text --- function updateRandomButtonText() { if (!randomBtn || !randomFavoriteBtn) return; const totalGames = getAllGameElements().filter(gameDiv => { if (gameDiv.style.display === 'none') return false; return true; }).length; const favoriteGamesCount = Object.values(playAgainFeedback) .filter(val => val === 'yes') .reduce((acc, val) => acc + 1, 0); if (selectedProvider) { const providerGamesCount = getAllGameElements().filter(gameDiv => { if (gameDiv.style.display === 'none') return false; return true; }).length; // Favorite count for selected provider: const favCountForProvider = Object.entries(playAgainFeedback).filter(([gameKey, val]) => { if (val !== 'yes') return false; if (!providerData[gameKey]) return false; return providerData[gameKey] && providerData[gameKey].provider === selectedProvider; }).length; randomBtn.textContent = `Random Game (${selectedProvider}) (${providerGamesCount})`; randomFavoriteBtn.textContent = `Random Favorite (${selectedProvider}) (${favCountForProvider})`; } else { randomBtn.textContent = `Random Game (All) (${totalGames})`; randomFavoriteBtn.textContent = `Random Favorite (All) (${favoriteGamesCount})`; } } function updateScanButtonText() { if (!scanBtn) return; const totalGames = getAllGameElements().filter(div => div.style.display !== 'none').length; const savedGamesCount = Object.keys(providerData).length; if (savedGamesCount === 0) { scanBtn.textContent = 'Full Scan'; } else if (savedGamesCount < totalGames) { scanBtn.textContent = 'Update Required'; } else { scanBtn.textContent = 'Up to Date'; } } // --- Pick Random Game --- function pickRandomGame() { const games = getAllGameElements().filter(gameDiv => { if (gameDiv.style.display === 'none') return false; const link = gameDiv.querySelector(GAME_LINK_SELECTOR); if (!link) return false; const path = getGamePath(link.href); if (playedGames[path]) return false; // Skip played return true; }); if (games.length === 0) { alert('No available games to pick.'); return; } const choice = games[Math.floor(Math.random() * games.length)]; const gameLink = choice.querySelector(GAME_LINK_SELECTOR); if (gameLink) { window.open(gameLink.href, '_blank'); const path = getGamePath(gameLink.href); playedGames[path] = true; saveData(); filterGamesByProvider(); updateRandomButtonText(); updateScanButtonText(); const savedTitle = (providerData[path] && providerData[path].title) || ''; showPlayAgainPrompt(path, prettifyTitle(savedTitle)); } } // --- Pick Random Favorite Game (only "yes" feedback) --- function pickRandomFavoriteGame() { // Filter all games user marked "yes" const yesGames = Object.entries(playAgainFeedback) .filter(([gameKey, val]) => val === 'yes') .filter(([gameKey]) => { if (selectedProvider) { if (!providerData[gameKey]) return false; return providerData[gameKey]?.provider === selectedProvider; } return true; }); if (yesGames.length === 0) { alert('No favorite games available to pick.'); return; } const [gameKey] = yesGames[Math.floor(Math.random() * yesGames.length)]; // Find the link element for this gameKey const gameDiv = getAllGameElements().find(gameDiv => { const link = gameDiv.querySelector(GAME_LINK_SELECTOR); if (!link) return false; const path = getGamePath(link.href); return path === gameKey; }); if (!gameDiv) { alert('Game link not found for the selected favorite game.'); return; } const link = gameDiv.querySelector(GAME_LINK_SELECTOR); if (link) { window.open(link.href, '_blank'); playedGames[gameKey] = true; saveData(); filterGamesByProvider(); updateRandomButtonText(); updateScanButtonText(); const savedTitle = (providerData[gameKey] && providerData[gameKey].title) || ''; } } // --- Show Play Again Prompt --- function showPlayAgainPrompt(gameKey, gameTitle) { // If already answered "yes" before, don't show prompt again if (playAgainFeedback[gameKey] === 'yes') return; if (document.getElementById('playAgainPrompt')) return; // Already shown const overlay = document.createElement('div'); overlay.id = 'playAgainPrompt'; overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.right = '0'; overlay.style.bottom = '0'; overlay.style.backgroundColor = 'rgba(0,0,0,0.8)'; overlay.style.zIndex = '20000'; overlay.style.display = 'flex'; overlay.style.alignItems = 'center'; overlay.style.justifyContent = 'center'; const panel = document.createElement('div'); panel.style.backgroundColor = '#333'; panel.style.color = '#fff'; panel.style.padding = '20px 30px'; panel.style.borderRadius = '8px'; panel.style.textAlign = 'center'; panel.style.maxWidth = '400px'; panel.style.fontFamily = 'Arial, sans-serif'; const title = document.createElement('h2'); title.textContent = 'Would you play this game again?'; panel.appendChild(title); const nameEl = document.createElement('p'); nameEl.style.marginTop = '10px'; nameEl.style.fontWeight = 'bold'; nameEl.textContent = gameTitle || 'Unknown Game'; panel.appendChild(nameEl); // Buttons container const buttonsDiv = document.createElement('div'); buttonsDiv.style.marginTop = '20px'; buttonsDiv.style.display = 'flex'; buttonsDiv.style.justifyContent = 'space-around'; // Yes button const yesBtn = document.createElement('button'); yesBtn.textContent = 'Yes'; styleButton(yesBtn, '#28a745', '#1e7e34'); yesBtn.onclick = () => { playAgainFeedback[gameKey] = 'yes'; saveData(); closePrompt(); }; buttonsDiv.appendChild(yesBtn); // Maybe button const maybeBtn = document.createElement('button'); maybeBtn.textContent = 'Maybe'; styleButton(maybeBtn, '#ffc107', '#d39e00'); maybeBtn.onclick = () => { playAgainFeedback[gameKey] = 'maybe'; saveData(); closePrompt(); }; buttonsDiv.appendChild(maybeBtn); // No button const noBtn = document.createElement('button'); noBtn.textContent = 'No'; styleButton(noBtn, '#dc3545', '#a71d2a'); noBtn.onclick = () => { playAgainFeedback[gameKey] = 'no'; saveData(); closePrompt(); }; buttonsDiv.appendChild(noBtn); panel.appendChild(buttonsDiv); overlay.appendChild(panel); document.body.appendChild(overlay); function closePrompt() { document.body.removeChild(overlay); updateRandomButtonText(); updateScanButtonText(); } } function updateScanButtonText() { if (!scanBtn) return; const visibleGamesCount = getAllGameElements().filter(div => div.style.display !== 'none').length; const savedGamesCount = Object.keys(providerData).length; if (savedGamesCount === 0) { scanBtn.textContent = 'Full Scan'; } else if (savedGamesCount < visibleGamesCount) { scanBtn.textContent = 'Update Required'; } else { scanBtn.textContent = 'Scan (Up to Date)'; } } // --- Scan Providers --- async function scanProviders() { if (scanning) return; scanning = true; scanCancelRequested = false; scanBtn.style.display = 'none'; cancelScanBtn.style.display = ''; scanProgressText.textContent = 'Starting scan...'; try { const gameDivs = getAllGameElements(); const total = gameDivs.length; for (let i = scanProgress.index || 0; i < total; i++) { if (scanCancelRequested) { scanProgress.index = i; scanProgress.completed = false; saveData(); scanProgressText.textContent = 'Scan cancelled.'; scanning = false; scanBtn.style.display = ''; cancelScanBtn.style.display = 'none'; return; } const gameDiv = gameDivs[i]; const infoBtn = gameDiv.querySelector(INFO_BTN_SELECTOR); if (!infoBtn) { // Fallback: skip if no info button scanProgressText.textContent = `Skipping game ${i + 1} (no info icon)`; await wait(300); continue; } scanProgressText.textContent = `Scanning game ${i + 1} / ${total}...`; infoBtn.click(); // Wait for overlay with title h4._1dujhhk try { const titleEl = await waitForElement('h4._1dujhhk', 5000); await wait(100); // Additional wait for provider to appear // Provider usually in a <li> containing "Game Provider - <provider>" let providerName = ''; const lis = [...document.querySelectorAll('li')]; for (const li of lis) { if (li.textContent.trim().startsWith('Game Provider -')) { providerName = li.textContent.replace('Game Provider -', '').trim(); break; } } if (providerName) { const normalized = normalizeProvider(providerName); const gameLink = gameDiv.querySelector(GAME_LINK_SELECTOR); const titleEl = document.querySelector('h4._1dujhhk'); const gameTitle = titleEl ? titleEl.textContent.trim() : ''; if (gameLink) { const path = getGamePath(gameLink.href); providerData[path] = { provider: normalized, title: gameTitle }; } } } catch { // Timeout or missing elements, skip } // Close overlay const closeBtn = document.querySelector(CLOSE_OVERLAY_SELECTOR); if (closeBtn) closeBtn.click(); await wait(100); scanProgress.index = i + 1; saveData(); } scanProgress.completed = true; scanProgress.index = 0; saveData(); scanProgressText.textContent = 'Scan completed refresh page.'; updateProviderDropdownCounts(); filterGamesByProvider(); updateRandomButtonText(); updateScanButtonText(); } catch (e) { scanProgressText.textContent = 'Scan error: ' + e.message; } finally { scanning = false; scanBtn.style.display = ''; cancelScanBtn.style.display = 'none'; } } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址