PollEv Geolocation Spoofer

Spoofs Location on PollEv to do quizzes anywhere.

// ==UserScript==
// @name         PollEv Geolocation Spoofer
// @namespace    https://github.com/rastr1sr
// @version      1.0
// @description  Spoofs Location on PollEv to do quizzes anywhere.
// @author       Rastrisr
// @compatible   firefox
// @compatible   chrome
// @compatible   edge
// @match        *://*.pollev.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @grant        GM_notification
// @run-at       document-start
// @icon         https://upload.wikimedia.org/wikipedia/commons/5/55/WMA_button2b.png
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const SCRIPT_PREFIX = 'pollev_geo_spoofer_';
    const IP_WARNING_DISMISSED_KEY = SCRIPT_PREFIX + 'ip_warning_dismissed_v1';

    let spoofEnabled = true;
    let currentLat = GM_getValue(SCRIPT_PREFIX + 'latitude', 40.7580);
    let currentLon = GM_getValue(SCRIPT_PREFIX + 'longitude', -73.9855);
    let currentAccuracy = GM_getValue(SCRIPT_PREFIX + 'accuracy', 20);

    if (navigator.geolocation && spoofEnabled) {

        const originalGetCurrentPosition = navigator.geolocation.getCurrentPosition.bind(navigator.geolocation);
        const originalWatchPosition = navigator.geolocation.watchPosition.bind(navigator.geolocation);
        const originalClearWatch = navigator.geolocation.clearWatch.bind(navigator.geolocation);

        const createPositionObject = () => ({
            coords: {
                latitude: currentLat,
                longitude: currentLon,
                accuracy: currentAccuracy,
                altitude: null,
                altitudeAccuracy: null,
                heading: null,
                speed: null,
            },
            timestamp: Date.now(),
        });

        const spoofedGetCurrentPosition = (successCallback, errorCallback, options) => {
            console.log('[PollEv Spoofer] Spoofing getCurrentPosition');
            if (successCallback) {
                setTimeout(() => {
                    try {
                         successCallback(createPositionObject());
                    } catch (e) {
                        console.error("[PollEv Spoofer] Error in user's successCallback for getCurrentPosition:", e);
                        if(errorCallback) {
                             errorCallback({ code: 2, message: "Spoofed location callback failed." });
                        }
                    }
                }, 100 + Math.random() * 300);
            } else {
                 console.warn('[PollEv Spoofer] getCurrentPosition called without a successCallback.');
            }
        };

        const spoofedWatchPosition = (successCallback, errorCallback, options) => {
            console.log('[PollEv Spoofer] Spoofing watchPosition');
            const watchId = Math.floor(Math.random() * 1000000);
             if (successCallback) {
                 setTimeout(() => {
                    try {
                        successCallback(createPositionObject());
                    } catch (e) {
                        console.error("[PollEv Spoofer] Error in user's successCallback for watchPosition:", e);
                         if(errorCallback) {
                             errorCallback({ code: 2, message: "Spoofed watch callback failed." });
                         }
                    }
                 }, 150 + Math.random() * 300);
             } else {
                 console.warn('[PollEv Spoofer] watchPosition called without a successCallback.');
             }
            return watchId;
        };

        const spoofedClearWatch = (watchId) => {
            console.log('[PollEv Spoofer] Spoofing clearWatch for ID:', watchId);
        };

        try {
            Object.defineProperties(unsafeWindow.navigator.geolocation, {
                getCurrentPosition: {
                    value: spoofedGetCurrentPosition,
                    writable: false,
                    configurable: true
                },
                watchPosition: {
                    value: spoofedWatchPosition,
                    writable: false,
                    configurable: true
                },
                clearWatch: {
                    value: spoofedClearWatch,
                    writable: false,
                    configurable: true
                }
            });
            console.log('[PollEv Spoofer] Geolocation API successfully overridden.');
        } catch (e) {
            console.error('[PollEv Spoofer] Failed to override geolocation API:', e);
             console.warn('[PollEv Spoofer] Location spoofing may not work correctly.');
        }

    } else if (!navigator.geolocation) {
        console.warn('[PollEv Spoofer] navigator.geolocation API not found. Cannot spoof.');
    } else {
        console.log('[PollEv Spoofer] Spoofing is disabled.');
    }

    let uiVisible = false;
    let panel = null;

    function createUIPanel() {
        panel = document.createElement('div');
        panel.id = SCRIPT_PREFIX + 'panel';
        panel.innerHTML = `
            <div class="${SCRIPT_PREFIX}header">
                <span>PollEv Geo Spoofer</span>
                <button class="${SCRIPT_PREFIX}close-btn" title="Close">×</button>
            </div>
            <div class="${SCRIPT_PREFIX}content">
                <div class="${SCRIPT_PREFIX}input-group">
                    <label for="${SCRIPT_PREFIX}lat">Latitude:</label>
                    <input type="number" id="${SCRIPT_PREFIX}lat" step="any" min="-90" max="90" placeholder="e.g., 40.7580">
                </div>
                <div class="${SCRIPT_PREFIX}input-group">
                    <label for="${SCRIPT_PREFIX}lon">Longitude:</label>
                    <input type="number" id="${SCRIPT_PREFIX}lon" step="any" min="-180" max="180" placeholder="e.g., -73.9855">
                </div>
                 <div class="${SCRIPT_PREFIX}input-group">
                    <label for="${SCRIPT_PREFIX}acc">Accuracy (m):</label>
                    <input type="number" id="${SCRIPT_PREFIX}acc" step="1" min="1" placeholder="e.g., 20">
                </div>
                <div class="${SCRIPT_PREFIX}button-group">
                    <button id="${SCRIPT_PREFIX}save-btn">Save & Apply</button>
                     <span id="${SCRIPT_PREFIX}status" class="${SCRIPT_PREFIX}status-msg"></span>
                </div>
                 <div class="${SCRIPT_PREFIX}info">
                    <small>Changes apply immediately to new location requests. A page reload might ensure consistency.</small>
                 </div>
                 <div class="${SCRIPT_PREFIX}ip-warning-info">
                    <hr class="${SCRIPT_PREFIX}divider">
                    <p><strong>Important:</strong> This script only spoofs browser geolocation.</p>
                    <p>If Poll Everywhere uses <strong>IP address range filtering</strong> (e.g., specific Wi-Fi networks), this script <strong>cannot</strong> bypass that. You may still be blocked even with spoofing enabled.</p>
                    <p>In such cases, you might need to use a <strong>VPN or Proxy</strong> located within the allowed network/region.</p>
                 </div>
            </div>
        `;
        document.body.appendChild(panel);

        const latInput = panel.querySelector(`#${SCRIPT_PREFIX}lat`);
        const lonInput = panel.querySelector(`#${SCRIPT_PREFIX}lon`);
        const accInput = panel.querySelector(`#${SCRIPT_PREFIX}acc`);
        const saveBtn = panel.querySelector(`#${SCRIPT_PREFIX}save-btn`);
        const closeBtn = panel.querySelector(`.${SCRIPT_PREFIX}close-btn`);
        const statusMsg = panel.querySelector(`#${SCRIPT_PREFIX}status`);

        latInput.value = currentLat;
        lonInput.value = currentLon;
        accInput.value = currentAccuracy;

        saveBtn.addEventListener('click', () => {
            const newLat = parseFloat(latInput.value);
            const newLon = parseFloat(lonInput.value);
            const newAcc = parseInt(accInput.value, 10);

            let isValid = true;
            statusMsg.textContent = '';
            statusMsg.style.color = 'var(--pollev-spoofer-error)';

            if (isNaN(newLat) || newLat < -90 || newLat > 90) {
                statusMsg.textContent = 'Invalid Latitude (-90 to 90).';
                latInput.focus();
                isValid = false;
            } else if (isNaN(newLon) || newLon < -180 || newLon > 180) {
                statusMsg.textContent = 'Invalid Longitude (-180 to 180).';
                lonInput.focus();
                isValid = false;
            } else if (isNaN(newAcc) || newAcc < 1) {
                statusMsg.textContent = 'Invalid Accuracy (>= 1).';
                accInput.focus();
                isValid = false;
            }

            if (!isValid) return;


            currentLat = newLat;
            currentLon = newLon;
            currentAccuracy = newAcc;

            GM_setValue(SCRIPT_PREFIX + 'latitude', currentLat);
            GM_setValue(SCRIPT_PREFIX + 'longitude', currentLon);
            GM_setValue(SCRIPT_PREFIX + 'accuracy', currentAccuracy);

            statusMsg.textContent = 'Saved!';
            statusMsg.style.color = 'var(--pollev-spoofer-success)';

            setTimeout(() => {
                if (statusMsg.textContent === 'Saved!') {
                   statusMsg.textContent = '';
                }

            }, 1500);
        });

        closeBtn.addEventListener('click', () => toggleUIPanel(false));

        panel.style.display = uiVisible ? 'flex' : 'none';
    }

    function toggleUIPanel(forceState) {
        if (!panel && (forceState === true || (forceState === undefined && !uiVisible))) {
            if (document.body) {
                 createUIPanel();
            } else {
                document.addEventListener('DOMContentLoaded', createUIPanel, { once: true });
                return;
            }
        }

        uiVisible = typeof forceState === 'boolean' ? forceState : !uiVisible;

        if (panel) {
             panel.style.display = uiVisible ? 'flex' : 'none';
             if (uiVisible) {
                 panel.querySelector(`#${SCRIPT_PREFIX}lat`).value = currentLat;
                 panel.querySelector(`#${SCRIPT_PREFIX}lon`).value = currentLon;
                 panel.querySelector(`#${SCRIPT_PREFIX}acc`).value = currentAccuracy;
                 panel.querySelector(`#${SCRIPT_PREFIX}status`).textContent = '';
             }
        }
    }

    GM_registerMenuCommand('Configure PollEv Geo Spoofer', () => toggleUIPanel());


    function showIpWarningNotification() {
        if (GM_getValue(IP_WARNING_DISMISSED_KEY, false)) {
            return;
        }

        if (!document.body) {
            setTimeout(showIpWarningNotification, 500);
            return;
        }

        const notificationDiv = document.createElement('div');
        notificationDiv.id = SCRIPT_PREFIX + 'ip-warning-notification';
        notificationDiv.innerHTML = `
            <div class="${SCRIPT_PREFIX}notification-content">
                📌 <strong>PollEv Geo Spoofer Info:</strong> This script spoofs browser location, but PollEv might <i>also</i> check your IP address (e.g., Wi-Fi). If you're still blocked, you may need a VPN or Proxy on the allowed network.
            </div>
            <button class="${SCRIPT_PREFIX}notification-dismiss" title="Dismiss permanently">×</button>
        `;
        document.body.appendChild(notificationDiv);

        notificationDiv.querySelector(`.${SCRIPT_PREFIX}notification-dismiss`).addEventListener('click', () => {
            notificationDiv.style.display = 'none';
            GM_setValue(IP_WARNING_DISMISSED_KEY, true);
             try {
                 notificationDiv.remove();
             } catch (e) {}
        });

         setTimeout(() => {
             if (notificationDiv && notificationDiv.style.display !== 'none') {
                 notificationDiv.style.opacity = '0';
                 setTimeout(() => {
                     if (notificationDiv && notificationDiv.style.display !== 'none') {
                        notificationDiv.style.display = 'none';
                        GM_setValue(IP_WARNING_DISMISSED_KEY, true);
                         try { notificationDiv.remove(); } catch (e) {}
                     }
                 }, 500);
             }
         }, 20000);

    }

    GM_addStyle(`
        :root {
            --pollev-spoofer-bg: #ffffff;
            --pollev-spoofer-text: #333333;
            --pollev-spoofer-border: #cccccc;
            --pollev-spoofer-shadow: #00000033;
            --pollev-spoofer-header-bg: #f0f0f0;
            --pollev-spoofer-button-bg: #3498db;
            --pollev-spoofer-button-hover-bg: #2980b9;
            --pollev-spoofer-button-text: #ffffff;
            --pollev-spoofer-close-hover-bg: #e74c3c;
            --pollev-spoofer-success: #2ecc71;
            --pollev-spoofer-error: #e74c3c;
            --pollev-spoofer-info-text: #7f8c8d;
            --pollev-spoofer-warning-bg: #fffbea;
            --pollev-spoofer-warning-border: #fddc71;
            --pollev-spoofer-warning-text: #5f4c0a;
        }

        #${SCRIPT_PREFIX}panel {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 99999;
            background-color: var(--pollev-spoofer-bg);
            border: 1px solid var(--pollev-spoofer-border);
            border-radius: 8px;
            box-shadow: 0 4px 15px var(--pollev-spoofer-shadow);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
            font-size: 14px;
            color: var(--pollev-spoofer-text);
            width: 320px; /* Slightly wider for new text */
            display: none; /* Initially hidden */
            flex-direction: column;
            overflow: hidden; /* Ensures border-radius clips content */
        }

        .${SCRIPT_PREFIX}header {
            background-color: var(--pollev-spoofer-header-bg);
            padding: 8px 12px;
            border-bottom: 1px solid var(--pollev-spoofer-border);
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: bold;
        }

        .${SCRIPT_PREFIX}close-btn {
            background: none;
            border: none;
            font-size: 20px;
            line-height: 1;
            cursor: pointer;
            color: #999;
            padding: 2px 5px;
            border-radius: 4px;
        }
        .${SCRIPT_PREFIX}close-btn:hover {
            background-color: var(--pollev-spoofer-close-hover-bg);
            color: var(--pollev-spoofer-button-text);
        }

        .${SCRIPT_PREFIX}content {
            padding: 15px;
            display: flex;
            flex-direction: column;
            gap: 12px; /* Spacing between elements */
        }

        .${SCRIPT_PREFIX}input-group {
            display: flex;
            flex-direction: column; /* Stack label and input */
            gap: 4px; /* Space between label and input */
        }

        .${SCRIPT_PREFIX}input-group label {
            font-weight: 500;
            font-size: 0.9em;
            color: #555;
        }

        .${SCRIPT_PREFIX}input-group input[type="number"] {
            padding: 8px 10px;
            border: 1px solid var(--pollev-spoofer-border);
            border-radius: 4px;
            font-size: 1em;
            width: 100%; /* Take full width */
            box-sizing: border-box; /* Include padding and border in width */
        }
         .${SCRIPT_PREFIX}input-group input[type="number"]:focus {
             border-color: var(--pollev-spoofer-button-bg);
             outline: none;
             box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
         }


        .${SCRIPT_PREFIX}button-group {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-top: 5px;
        }

        #${SCRIPT_PREFIX}save-btn {
            background-color: var(--pollev-spoofer-button-bg);
            color: var(--pollev-spoofer-button-text);
            border: none;
            border-radius: 5px;
            padding: 10px 15px;
            cursor: pointer;
            font-size: 0.95em;
            font-weight: 500;
            transition: background-color 0.2s ease;
            flex-grow: 1; /* Take available space if needed */
        }
        #${SCRIPT_PREFIX}save-btn:hover {
            background-color: var(--pollev-spoofer-button-hover-bg);
        }

        .${SCRIPT_PREFIX}status-msg {
             font-size: 0.9em;
             font-weight: 500;
        }
         .${SCRIPT_PREFIX}info {
             margin-top: 5px;
             font-size: 0.85em;
             color: var(--pollev-spoofer-info-text);
             text-align: center;
             line-height: 1.3;
         }

         .${SCRIPT_PREFIX}divider {
            border: none;
            border-top: 1px solid #eee;
            margin: 15px 0 10px 0;
         }

         .${SCRIPT_PREFIX}ip-warning-info {
             background-color: var(--pollev-spoofer-warning-bg);
             border: 1px solid var(--pollev-spoofer-warning-border);
             color: var(--pollev-spoofer-warning-text);
             padding: 10px;
             border-radius: 4px;
             font-size: 0.9em;
             line-height: 1.4;
             margin-top: 10px;
         }
          .${SCRIPT_PREFIX}ip-warning-info p {
              margin: 0 0 5px 0;
          }
         .${SCRIPT_PREFIX}ip-warning-info p:last-child {
              margin-bottom: 0;
          }
         .${SCRIPT_PREFIX}ip-warning-info strong {
            color: inherit; /* Ensure strong tag uses warning text color */
         }

         /* Notification Bar Styles */
         #${SCRIPT_PREFIX}ip-warning-notification {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
            background-color: var(--pollev-spoofer-warning-bg);
            border-top: 2px solid var(--pollev-spoofer-warning-border);
            color: var(--pollev-spoofer-warning-text);
            z-index: 100000; /* High z-index */
            padding: 10px 40px 10px 20px; /* Space for close button */
            box-sizing: border-box;
            font-size: 14px;
            line-height: 1.4;
            display: flex;
            align-items: center;
            justify-content: space-between;
            box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
            transition: opacity 0.5s ease-out;
            opacity: 1;
         }
         .${SCRIPT_PREFIX}notification-content {
            flex-grow: 1;
         }
        .${SCRIPT_PREFIX}notification-content strong,
        .${SCRIPT_PREFIX}notification-content i {
             color: inherit;
         }

         .${SCRIPT_PREFIX}notification-dismiss {
            position: absolute;
            top: 50%;
            right: 10px;
            transform: translateY(-50%);
            background: none;
            border: none;
            font-size: 24px;
            line-height: 1;
            color: var(--pollev-spoofer-warning-text);
            cursor: pointer;
            padding: 5px;
            opacity: 0.7;
         }
         .${SCRIPT_PREFIX}notification-dismiss:hover {
             opacity: 1;
         }
    `);

    function initialize() {
        setTimeout(showIpWarningNotification, 1500);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址