您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button that links to the Plonk It page for that respective country, after the round ends
// ==UserScript== // @name GeoGuessr PlonkIt Button // @description Adds a button that links to the Plonk It page for that respective country, after the round ends // @version 1.0 // @author ArunSomasundaram // @match *://*.geoguessr.com/* // @icon https://www.google.com/s2/favicons?domain=geoguessr.com // @grant GM_openInTab // @run-at document-start // @namespace https://gf.qytechs.cn/users/1484321 // ==/UserScript== (function() { 'use strict'; // ============================================================================ // CONSTANTS AND CONFIGURATION // ============================================================================ /** * Mapping of ISO 3166-1 alpha-2 country codes to PlonkIt URL slugs */ const COUNTRY_DICT = { 'ad': 'andorra', 'ae': 'united-arab-emirates', 'af': 'afghanistan', 'ag': 'antigua-and-barbuda', 'ai': 'anguilla', 'al': 'albania', 'am': 'armenia', 'ao': 'angola', 'aq': 'antarctica', 'ar': 'argentina', 'as': 'american-samoa', 'at': 'austria', 'au': 'australia', 'aw': 'aruba', 'ax': 'aland-islands', 'az': 'azerbaijan', 'ba': 'bosnia-and-herzegovina', 'bb': 'barbados', 'bd': 'bangladesh', 'be': 'belgium', 'bf': 'burkina-faso', 'bg': 'bulgaria', 'bh': 'bahrain', 'bi': 'burundi', 'bj': 'benin', 'bl': 'saint-barthelemy', 'bm': 'bermuda', 'bn': 'brunei', 'bo': 'bolivia', 'bq': 'caribbean-netherlands', 'br': 'brazil', 'bs': 'bahamas', 'bt': 'bhutan', 'bv': 'bouvet-island', 'bw': 'botswana', 'by': 'belarus', 'bz': 'belize', 'ca': 'canada', 'cc': 'cocos-keeling-islands', 'cd': 'democratic-republic-of-the-congo', 'cf': 'central-african-republic', 'cg': 'republic-of-the-congo', 'ch': 'switzerland', 'ci': 'ivory-coast', 'ck': 'cook-islands', 'cl': 'chile', 'cm': 'cameroon', 'cn': 'china', 'co': 'colombia', 'cr': 'costa-rica', 'cu': 'cuba', 'cv': 'cape-verde', 'cw': 'curacao', 'cx': 'christmas-island', 'cy': 'cyprus', 'cz': 'czech-republic', 'de': 'germany', 'dj': 'djibouti', 'dk': 'denmark', 'dm': 'dominica', 'do': 'dominican-republic', 'dz': 'algeria', 'ec': 'ecuador', 'ee': 'estonia', 'eg': 'egypt', 'eh': 'western-sahara', 'er': 'eritrea', 'es': 'spain', 'et': 'ethiopia', 'fi': 'finland', 'fj': 'fiji', 'fk': 'falkland-islands', 'fm': 'micronesia', 'fo': 'faroe-islands', 'fr': 'france', 'ga': 'gabon', 'gb': 'united-kingdom', 'gd': 'grenada', 'ge': 'georgia', 'gf': 'french-guiana', 'gg': 'guernsey', 'gh': 'ghana', 'gi': 'gibraltar', 'gl': 'greenland', 'gm': 'gambia', 'gn': 'guinea', 'gp': 'guadeloupe', 'gq': 'equatorial-guinea', 'gr': 'greece', 'gs': 'south-georgia-and-south-sandwich-islands', 'gt': 'guatemala', 'gu': 'guam', 'gw': 'guinea-bissau', 'gy': 'guyana', 'hk': 'hong-kong', 'hm': 'heard-island-and-mcdonald-islands', 'hn': 'honduras', 'hr': 'croatia', 'ht': 'haiti', 'hu': 'hungary', 'id': 'indonesia', 'ie': 'ireland', 'il': 'israel', 'im': 'isle-of-man', 'in': 'india', 'io': 'british-indian-ocean-territory', 'iq': 'iraq', 'ir': 'iran', 'is': 'iceland', 'it': 'italy', 'je': 'jersey', 'jm': 'jamaica', 'jo': 'jordan', 'jp': 'japan', 'ke': 'kenya', 'kg': 'kyrgyzstan', 'kh': 'cambodia', 'ki': 'kiribati', 'km': 'comoros', 'kn': 'saint-kitts-and-nevis', 'kp': 'north-korea', 'kr': 'south-korea', 'kw': 'kuwait', 'ky': 'cayman-islands', 'kz': 'kazakhstan', 'la': 'laos', 'lb': 'lebanon', 'lc': 'saint-lucia', 'li': 'liechtenstein', 'lk': 'sri-lanka', 'lr': 'liberia', 'ls': 'lesotho', 'lt': 'lithuania', 'lu': 'luxembourg', 'lv': 'latvia', 'ly': 'libya', 'ma': 'morocco', 'mc': 'monaco', 'md': 'moldova', 'me': 'montenegro', 'mf': 'saint-martin', 'mg': 'madagascar', 'mh': 'marshall-islands', 'mk': 'north-macedonia', 'ml': 'mali', 'mm': 'myanmar', 'mn': 'mongolia', 'mo': 'macau', 'mp': 'northern-mariana-islands', 'mq': 'martinique', 'mr': 'mauritania', 'ms': 'montserrat', 'mt': 'malta', 'mu': 'mauritius', 'mv': 'maldives', 'mw': 'malawi', 'mx': 'mexico', 'my': 'malaysia', 'mz': 'mozambique', 'na': 'namibia', 'nc': 'new-caledonia', 'ne': 'niger', 'nf': 'norfolk-island', 'ng': 'nigeria', 'ni': 'nicaragua', 'nl': 'netherlands', 'no': 'norway', 'np': 'nepal', 'nr': 'nauru', 'nu': 'niue', 'nz': 'new-zealand', 'om': 'oman', 'pa': 'panama', 'pe': 'peru', 'pf': 'french-polynesia', 'pg': 'papua-new-guinea', 'ph': 'philippines', 'pk': 'pakistan', 'pl': 'poland', 'pm': 'saint-pierre-and-miquelon', 'pn': 'pitcairn-islands', 'pr': 'puerto-rico', 'ps': 'palestine', 'pt': 'portugal', 'pw': 'palau', 'py': 'paraguay', 'qa': 'qatar', 're': 'reunion', 'ro': 'romania', 'rs': 'serbia', 'ru': 'russia', 'rw': 'rwanda', 'sa': 'saudi-arabia', 'sb': 'solomon-islands', 'sc': 'seychelles', 'sd': 'sudan', 'se': 'sweden', 'sg': 'singapore', 'sh': 'saint-helena', 'si': 'slovenia', 'sj': 'svalbard-and-jan-mayen', 'sk': 'slovakia', 'sl': 'sierra-leone', 'sm': 'san-marino', 'sn': 'senegal', 'so': 'somalia', 'sr': 'suriname', 'ss': 'south-sudan', 'st': 'sao-tome-and-principe', 'sv': 'el-salvador', 'sx': 'sint-maarten', 'sy': 'syria', 'sz': 'eswatini', 'tc': 'turks-and-caicos-islands', 'td': 'chad', 'tf': 'french-southern-and-antarctic-lands', 'tg': 'togo', 'th': 'thailand', 'tj': 'tajikistan', 'tk': 'tokelau', 'tl': 'east-timor', 'tm': 'turkmenistan', 'tn': 'tunisia', 'to': 'tonga', 'tr': 'turkey', 'tt': 'trinidad-and-tobago', 'tv': 'tuvalu', 'tw': 'taiwan', 'tz': 'tanzania', 'ua': 'ukraine', 'ug': 'uganda', 'um': 'united-states-minor-outlying-islands', 'us': 'united-states', 'uy': 'uruguay', 'uz': 'uzbekistan', 'va': 'vatican-city', 'vc': 'saint-vincent-and-the-grenadines', 've': 'venezuela', 'vg': 'british-virgin-islands', 'vi': 'united-states-virgin-islands', 'vn': 'vietnam', 'vu': 'vanuatu', 'wf': 'wallis-and-futuna', 'ws': 'samoa', 'ye': 'yemen', 'yt': 'mayotte', 'za': 'south-africa', 'zm': 'zambia', 'zw': 'zimbabwe' }; /** * Configuration constants */ const CONFIG = { BUTTON_ID: 'plonkit-button', POLLING_INTERVAL: 1500, PLONKIT_BASE_URL: 'https://plonkit.net', FLAG_CDN_URL: 'https://flagcdn.com/24x18' }; /** * CSS selectors for DOM elements */ const SELECTORS = { RESULT_LAYOUT: '[class*="result-layout_root"]', PLAY_AGAIN_BUTTON: 'button[data-qa="play-again-button"]', GAME_FINISHED: 'div[class*="game-finished_root"]', GAME_SUMMARY: 'div[class*="game-summary_root"]' }; // ============================================================================ // STATE MANAGEMENT // ============================================================================ /** * Set to track processed rounds to prevent duplicate button creation */ const processedRounds = new Set(); /** * Track the last URL to detect navigation changes */ let lastUrl = location.href; // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * Generates a flag image URL for the given country code * @param {string} countryCode - Two-letter ISO country code * @returns {string|null} Flag image URL or null if invalid code */ function getFlagImageUrl(countryCode) { if (!countryCode || countryCode.length !== 2) { return null; } return `${CONFIG.FLAG_CDN_URL}/${countryCode.toLowerCase()}.png`; } /** * Checks if the current page is in game or challenge mode * @returns {boolean} True if in game/challenge mode */ function isGameMode() { return /\/(game|challenge)\//.test(location.pathname); } /** * Extracts country code from game round data * @param {Object} roundData - Round data from API response * @returns {string|null} Country code or null if not found */ function extractCountryCode(roundData) { if (!roundData) return null; const code = ( roundData.streakLocationCode || roundData.locationCode || roundData.countryCode || roundData.country )?.toLowerCase(); return code; } /** * Generates PlonkIt URL for a given country code * @param {string} countryCode - Two-letter ISO country code * @returns {string|null} PlonkIt URL or null if country not supported */ function getPlonkItUrl(countryCode) { const slug = COUNTRY_DICT[countryCode.toLowerCase()]; return slug ? `${CONFIG.PLONKIT_BASE_URL}/${slug}` : null; } // ============================================================================ // BUTTON MANAGEMENT // ============================================================================ /** * Creates and displays the PlonkIt button with country flag * @param {string} url - PlonkIt URL to open * @param {string} countryCode - Two-letter ISO country code */ function createPlonkItButton(url, countryCode) { removeButton(); const button = document.createElement('button'); button.id = CONFIG.BUTTON_ID; const flagImageUrl = getFlagImageUrl(countryCode); console.log('Creating PlonkIt button for country:', countryCode, 'with flag URL:', flagImageUrl); // Set button content with flag image or fallback emoji if (flagImageUrl) { button.innerHTML = ` <img src="${flagImageUrl}" alt="${countryCode.toUpperCase()} flag" style="width: 20px; height: 15px; margin-right: 8px; border-radius: 2px; object-fit: cover;" onerror="this.style.display='none'"> <span>PlonkIt</span> `; } else { button.innerHTML = '<span>🌍 PlonkIt</span>'; } button.classList.add('plonkit-btn'); button.onclick = () => window.open(url, '_blank'); // Apply styles applyButtonStyles(button); document.body.appendChild(button); } /** * Applies CSS styles to the PlonkIt button * @param {HTMLElement} button - Button element to style */ function applyButtonStyles(button) { // Inject CSS styles if not already present if (!document.getElementById('plonkit-styles')) { const style = document.createElement('style'); style.id = 'plonkit-styles'; style.textContent = ` .plonkit-btn { position: relative; transition: transform 0.2s ease, box-shadow 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; display: flex; align-items: center; justify-content: center; } .plonkit-btn img { flex-shrink: 0; vertical-align: middle; } .plonkit-btn:hover { transform: scale(1.05); box-shadow: 0 4px 10px rgba(0, 255, 123, 0.4); } .plonkit-btn::after { position: absolute; bottom: 120%; left: 50%; transform: translateX(-50%); background: #333; color: white; padding: 6px 10px; border-radius: 4px; white-space: nowrap; font-size: 12px; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; z-index: 9999; } .plonkit-btn:hover::after { opacity: 1; } `; document.head.appendChild(style); } // Apply inline styles button.style.cssText = ` position: fixed; bottom: 20px; left: 20px; z-index: 9999; background-color: #4caf50; color: white; border: none; border-radius: 8px; padding: 10px 16px; font-size: 14px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 8px rgba(0,0,0,0.3); transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; `; // Add hover effects button.onmouseover = () => button.style.transform = 'scale(1.05)'; button.onmouseout = () => button.style.transform = 'scale(1)'; } /** * Removes the PlonkIt button from the page */ function removeButton() { const button = document.getElementById(CONFIG.BUTTON_ID); if (button) { button.remove(); } } // ============================================================================ // GAME DATA HANDLING // ============================================================================ /** * Fetches game data from GeoGuessr API and creates button if appropriate */ async function fetchGameData() { if (!isGameMode()) return; try { const token = location.pathname.split('/').pop().split('?')[0]; const isChallenge = location.pathname.includes('/challenge/'); const apiUrl = isChallenge ? `https://www.geoguessr.com/api/v3/challenges/${token}/game` : `https://www.geoguessr.com/api/v3/games/${token}`; const response = await fetch(apiUrl); if (!response.ok) return; const data = await response.json(); const round = data.player?.guesses?.length || 0; const roundData = data.rounds?.[round - 1]; if (!roundData) return; const countryCode = extractCountryCode(roundData); if (!countryCode) return; // Check if this round has already been processed const gameId = data.token || token; const roundKey = `${gameId}-${round}`; if (processedRounds.has(roundKey)) return; // Create button if country is supported const plonkItUrl = getPlonkItUrl(countryCode); if (plonkItUrl) { createPlonkItButton(plonkItUrl, countryCode); processedRounds.add(roundKey); } } catch (error) { console.error('Error fetching GeoGuessr game data:', error); } } // ============================================================================ // EVENT HANDLING AND INITIALIZATION // ============================================================================ /** * Handles URL changes and page state updates */ function handlePageUpdate() { if (!isGameMode()) { removeButton(); return; } // Handle URL changes if (location.href !== lastUrl) { lastUrl = location.href; processedRounds.clear(); removeButton(); } // Remove button if result overlay disappears (new round or screen change) const resultElement = document.querySelector(SELECTORS.RESULT_LAYOUT); if (!resultElement) { removeButton(); } // Remove button on final screen after all rounds completed const finalScreenSelectors = [ SELECTORS.PLAY_AGAIN_BUTTON, SELECTORS.GAME_FINISHED, SELECTORS.GAME_SUMMARY ]; const finalSummary = finalScreenSelectors.some(selector => document.querySelector(selector) ); if (finalSummary) { removeButton(); return; } // Fetch game data and potentially create button fetchGameData(); } /** * Initialize the script */ function initialize() { console.log('✅ GeoGuessr PlonkIt Button script loaded'); // Set up DOM mutation observer const observer = new MutationObserver(handlePageUpdate); observer.observe(document.body, { childList: true, subtree: true }); // Set up fallback polling mechanism setInterval(handlePageUpdate, CONFIG.POLLING_INTERVAL); } // ============================================================================ // SCRIPT EXECUTION // ============================================================================ // Initialize the script when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); } else { initialize(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址