您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks Keno numbers played in Torn City and displays the most frequent "hot" numbers within the tokens div.
当前为
// ==UserScript== // @name Torn Keno Hot Numbers Tracker // @namespace torn.keno.hotnumbers.tracker // @version 1.2 // @description Tracks Keno numbers played in Torn City and displays the most frequent "hot" numbers within the tokens div. // @author eaksquad // @match https://www.torn.com/page.php?sid=keno* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // Included as a fallback if direct URL fetching is needed, though AJAX is prioritized. // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Constants --- const SCRIPT_NAME = "Keno Hot Numbers Tracker"; const SCRIPT_VERSION = "1.2"; // Updated internal version const KENO_NUMBERS_STORAGE_KEY = 'kenoNumberFrequencies'; // Key for storing frequencies in GM_getValue const DISPLAY_CONTAINER_ID = 'keno-hot-numbers-content'; // ID for the new div INSIDE alertZoneInner const TARGET_PARENT_CONTAINER_ID = 'alertZoneInner'; // The div where our display will be placed const MAX_HOT_NUMBERS_TO_SHOW = 10; // How many of the hottest numbers to display const KENO_NUMBER_RANGE_MIN = 1; // Minimum valid Keno number const KENO_NUMBER_RANGE_MAX = 80; // Maximum valid Keno number console.log(`[${SCRIPT_NAME} v${SCRIPT_VERSION}] Script loading.`); // --- State Management --- // This object will store the counts of each Keno number played. // Example: { 5: 12, 18: 8, 77: 15 } let kenoNumberFrequencies = {}; // --- Utility Functions --- /** * Loads saved Keno number frequencies from storage. * Initializes kenoNumberFrequencies with stored data or an empty object if none exists. */ function loadFrequencies() { try { const storedData = GM_getValue(KENO_NUMBERS_STORAGE_KEY, '{}'); kenoNumberFrequencies = JSON.parse(storedData); // Ensure the loaded data is a valid object. If not (e.g., corrupted data), reset to an empty object. if (typeof kenoNumberFrequencies !== 'object' || kenoNumberFrequencies === null) { console.warn(`[${SCRIPT_NAME}] Stored frequencies data was invalid. Resetting.`); kenoNumberFrequencies = {}; } console.log(`[${SCRIPT_NAME}] Loaded ${Object.keys(kenoNumberFrequencies).length} number frequencies.`); } catch (error) { console.error(`[${SCRIPT_NAME}] Error loading frequencies:`, error); kenoNumberFrequencies = {}; // Fallback to empty object on any error. } } /** * Saves the current Keno number frequencies to storage. */ function saveFrequencies() { try { GM_setValue(KENO_NUMBERS_STORAGE_KEY, JSON.stringify(kenoNumberFrequencies)); } catch (error) { console.error(`[${SCRIPT_NAME}] Error saving frequencies:`, error); } } /** * Updates the frequencies count for each number drawn in a Keno game. * This function is called when Keno result data is successfully captured. * @param {number[]} drawnNumbers - An array of numbers drawn in the latest game session. */ function updateFrequencies(drawnNumbers) { if (!Array.isArray(drawnNumbers) || drawnNumbers.length === 0) { console.warn(`[${SCRIPT_NAME}] No valid drawn numbers provided for frequency update.`); return; } let changesMade = false; // Flag to track if any frequencies were actually updated. drawnNumbers.forEach(num => { // Validate that the number is within the expected Keno range (1-80). if (num >= KENO_NUMBER_RANGE_MIN && num <= KENO_NUMBER_RANGE_MAX) { kenoNumberFrequencies[num] = (kenoNumberFrequencies[num] || 0) + 1; // Increment count, or initialize to 1 if first time seen. changesMade = true; } else { console.warn(`[${SCRIPT_NAME}] Encountered out-of-range number (${num}). Ignoring.`); } }); if (changesMade) { saveFrequencies(); // Save the updated frequencies to persistent storage. updateDisplay(); // Refresh the display to show the latest "hot" numbers. console.log(`[${SCRIPT_NAME}] Frequencies updated. Total unique numbers tracked: ${Object.keys(kenoNumberFrequencies).length}`); } } /** * Creates or updates the display element that shows the hottest Keno numbers. * This function is responsible for querying the current frequencies, sorting them, * and rendering them in the designated div inside alertZoneInner. */ function updateDisplay() { // Target the specific child div for Keno numbers within alertZoneInner. const displayContainer = document.getElementById(DISPLAY_CONTAINER_ID); if (!displayContainer) { console.warn(`[${SCRIPT_NAME}] Display container #${DISPLAY_CONTAINER_ID} not found! Cannot update.`); // Attempt to re-initialize if it was missed or destroyed. initializeDisplay(); // This might fix it if the parent container exists but the child doesn't. return; } // Convert the frequencies object into an array of [number, count] pairs. // Then sort this array based on the count in descending order to get the hottest numbers first. const sortedNumbers = Object.entries(kenoNumberFrequencies) .sort(([, countA], [, countB]) => countB - countA) // Sort by count (descending) .slice(0, MAX_HOT_NUMBERS_TO_SHOW); // Limit to the top N hottest numbers // If no numbers have been tracked yet, display a placeholder message. if (sortedNumbers.length === 0) { displayContainer.innerHTML = '<em>No Keno data tracked yet. Play a game!</em>'; return; } // Format the sorted numbers for display. Example: "68 (15) | 53 (12) | ..." // Wrap numbers and counts in spans for potential styling. const displayHtml = sortedNumbers.map(([num, count]) => `<span class="keno-number-entry">${num}</span> <span class="keno-number-count">(${count})</span>` ).join(' | '); // Join entries with a separator. // Update the container's HTML with the formatted string. displayContainer.innerHTML = `<strong>Hottest Keno Numbers:</strong> ${displayHtml}`; } /** * Initializes the display element in the DOM by finding the parent container * and creating the child div for Keno numbers. */ function initializeDisplay() { // Find the target parent container specified by the user. const parentContainer = document.getElementById(TARGET_PARENT_CONTAINER_ID); if (!parentContainer) { console.warn(`[${SCRIPT_NAME}] Target parent container #${TARGET_PARENT_CONTAINER_ID} not found. Keno tracker display will not be initialized.`); return; // Exit if the parent container doesn't exist. } // Inject CSS styles for the Keno numbers display within the alertZoneInner. // NOTE: The 'Unexpected token ;' error often stems from CSS syntax issues within GM_addStyle template literals. // Using double quotes for the font name is a common fix for such parsing ambiguities. GM_addStyle(` /* Styles for the Keno numbers display div */ #${DISPLAY_CONTAINER_ID} { color: #e0e0e0; /* Light text color */ padding: 5px 0 5px 0; /* Top/bottom padding, no left/right needed as it's inside another div */ font-size: 13px; /* Slightly smaller font for better fit */ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Modern font stack - Changed to double quotes for 'Segoe UI' */ display: block; /* Ensure it takes its own line */ white-space: normal; /* Allow text content to wrap naturally */ text-align: center; /* Center the content */ margin-top: 5px; /* Space from the "tokens: 11" text above */ word-break: break-word; /* Break long words/numbers */ } #${DISPLAY_CONTAINER_ID} strong { color: #00e5ff; /* Bright cyan for the title */ font-weight: 600; margin-right: 5px; /* Space between title and numbers */ } #${DISPLAY_CONTAINER_ID} .keno-number-entry { font-weight: bold; color: #00e5ff; /* Bright cyan for the numbers */ margin-right: 3px; /* Small space after number */ } #${DISPLAY_CONTAINER_ID} .keno-number-count { font-size: 0.85em; color: #bdbdbd; /* Lighter grey for counts */ } `); // Create the Keno numbers display div if it doesn't already exist. if (!document.getElementById(DISPLAY_CONTAINER_ID)) { const div = document.createElement('div'); div.id = DISPLAY_CONTAINER_ID; // Initial placeholder text. div.innerHTML = '<em>Waiting for Keno results...</em>'; parentContainer.appendChild(div); // Append it to the target parent container. console.log(`[${SCRIPT_NAME}] Keno display container #${DISPLAY_CONTAINER_ID} created.`); } // Update the display with the current frequencies once the element is ready. updateDisplay(); } // --- AJAX Interception Logic --- // This function intercepts network requests made by the browser to capture Keno result data. // It relies on heuristics to identify relevant responses. const originalFetch = window.fetch; // Store the original fetch function. // Override window.fetch to intercept requests. window.fetch = function(...args) { // Call the original fetch function and get its promise. return originalFetch.apply(this, args).then(response => { // Important: Clone the response. This allows the original response to be used elsewhere // (like by Torn's own scripts) while we read from the clone. const clonedResponse = response.clone(); // Attempt to parse the cloned response as JSON. // This will only succeed if the response body is valid JSON. clonedResponse.json().then(data => { // Heuristic check: Does this JSON data look like Keno results? // We check for the presence of `randomNumbers` (an array) and `slotturns` (a number), // which are key indicators from the example data. if (data && typeof data === 'object' && Array.isArray(data.randomNumbers) && data.hasOwnProperty('slotturns') && data.hasOwnProperty('winnings')) { console.log(`[${SCRIPT_NAME}] Detected Keno result data.`); // If it looks like Keno results, update our frequencies and display. updateFrequencies(data.randomNumbers); } }).catch(err => { // If parsing fails (e.g., response is HTML, not JSON, or not valid JSON), // this catch block will execute. We can ignore these errors as they are expected // for most network requests that aren't Keno results. // console.warn(`[${SCRIPT_NAME}] Response was not JSON or not Keno results:`, err); }); return response; // Always return the original response object so the fetch chain continues normally. }); }; // --- Direct URL Fetching (Fallback/Alternative - Less Likely to be needed) --- // The user mentioned a URL like `page.php?rfcv=...` appearing. If Keno results // are *not* loaded via AJAX but by navigating to such a URL, we'd need GM_xmlhttpRequest. // This is less common for dynamic game results. // The following is a placeholder for how it might be implemented if AJAX fails. /* let lastProcessedRfcv = null; // To avoid re-processing the same result URL. function checkForRfcvResult() { const currentUrl = window.location.href; const urlParams = new URLSearchParams(currentUrl.split('?')[1] || ''); const rfcvParam = urlParams.get('rfcv'); // Check if we are on a result page (has rfcv) and if it's a new one we haven't processed. if (rfcvParam && rfcvParam !== lastProcessedRfcv) { lastProcessedRfcv = rfcvParam; // Mark this RFVC as processed. // Construct the full URL, assuming it's on the same origin. const resultUrl = `${window.location.origin}/page.php?rfcv=${rfcvParam}`; console.log(`[${SCRIPT_NAME}] Detected new rfcv parameter. Attempting to fetch Keno results from: ${resultUrl}`); GM_xmlhttpRequest({ method: "GET", url: resultUrl, headers: { "User-Agent": "Mozilla/5.0" }, // Standard browser User-Agent onload: function(response) { try { const htmlContent = response.responseText; const jsonMatch = htmlContent.match(/<script[^>]*>\s*({.*?})\s*<\/script>/s); if (jsonMatch && jsonMatch[1]) { const jsonData = JSON.parse(jsonMatch[1]); if (jsonData && typeof jsonData === 'object' && Array.isArray(jsonData.randomNumbers) && jsonData.hasOwnProperty('slotturns')) { console.log(`[${SCRIPT_NAME}] Successfully fetched and parsed Keno data from rfcv URL.`); updateFrequencies(jsonData.randomNumbers); } else { console.warn(`[${SCRIPT_NAME}] Parsed JSON from rfcv URL did not match expected Keno structure.`); } } else { console.warn(`[${SCRIPT_NAME}] Could not find embedded JSON data in the rfcv URL response.`); } } catch (e) { console.error(`[${SCRIPT_NAME}] Error processing rfcv URL response:`, e); } }, onerror: function(err) { console.error(`[${SCRIPT_NAME}] Failed to fetch rfcv URL ${resultUrl}:`, err); } }); } } */ // --- Initial Setup --- loadFrequencies(); // Load existing data when the script starts. initializeDisplay(); // Create and populate the display element within alertZoneInner. // The fetch interception handles AJAX calls automatically. // The checkForRfcvResult function is commented out, as AJAX interception is the primary method. // If AJAX fails, you would uncomment the line below and potentially adapt the JSON extraction logic within it. // checkForRfcvResult(); // MutationObserver to detect changes in the DOM that might indicate new Keno results are loaded (e.g. if page reloads dynamically without a full navigation change, or if the AJAX logic fails) // This is a potential fallback if the fetch interception is somehow missed. // NOTE: The MutationObserver is a fallback and might be less efficient or trigger more often than necessary. // The primary Keno data detection is via the window.fetch override. const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { // Element node // Look for elements that are likely to contain Keno result data or trigger result updates. // This is heuristic and might need tuning. // A broad check for JSON-like content might be too noisy. // A more specific check could look for specific classes if known. // For now, we'll assume that if `alertZoneInner` (or its child display div) is updated, it might be from a Keno result. // This is less direct than fetch interception. if (node.id === TARGET_PARENT_CONTAINER_ID || (node.querySelector && node.querySelector(`#${DISPLAY_CONTAINER_ID}`))) { console.log(`[${SCRIPT_NAME}] DOM mutation detected potentially related to Keno results.`); // Re-update display in case the structure changed or data was missed. updateDisplay(); } } }); } }); }); // Observe the body for added nodes that might contain Keno results or update our display target. observer.observe(document.body, { childList: true, subtree: true }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址