Anti-Fingerprinting Shield Plus

Floating UI enhancements: draggable gear icon, saved position, toggling panel, hover tooltip, and adaptive placement. Improved usability in Safari and Chrome.

// ==UserScript==
// @name         Anti-Fingerprinting Shield Plus
// @namespace    https://365devnet.eu/userscripts
// @version      4.9
// @description  Floating UI enhancements: draggable gear icon, saved position, toggling panel, hover tooltip, and adaptive placement. Improved usability in Safari and Chrome.
// @author       Richard B
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @license      MIT
// ==/UserScript==

(() => {
  const DEBUG = true;
  const settingsKey = '__afs_user_settings';
  const positionKey = '__afs_ui_position';
  const sessionExpiryKey = '__afs_last_seen';
  const spoofDefaults = {
    userAgent: true,
    platform: true,
    language: true,
    screen: true,
    hardwareConcurrency: true,
    timezone: true,
    canvas: true,
    webgl: true,
    audio: true,
    plugins: true,
    mediaDevices: true,
    storageEstimate: true,
    matchMedia: true,
    sharedArrayBuffer: true
  };

  const spoofSettings = loadSettings();
  const now = Date.now();
  const SESSION_TIMEOUT_MINUTES = 30;
  const browser = detectBrowser();

  if (isExpired()) {
    clearStoredValues();
    log(`Session expired. Values cleared.`);
  }
  localStorage.setItem(sessionExpiryKey, now.toString());
  const sessionId = getOrCreatePersistent('__afs_session_id', () => Math.random().toString(36).substring(2, 10));

  function log(...args) {
    if (DEBUG) console.log('[AFS+]', ...args);
  }

  function loadSettings() {
    const saved = localStorage.getItem(settingsKey);
    return saved ? JSON.parse(saved) : { ...spoofDefaults };
  }

  function saveSettings(settings) {
    localStorage.setItem(settingsKey, JSON.stringify(settings));
  }

  function clearStoredValues() {
    Object.keys(localStorage).forEach(key => {
      if (key.startsWith('__afs_')) localStorage.removeItem(key);
    });
  }

  function isExpired() {
    const lastSeen = parseInt(localStorage.getItem(sessionExpiryKey), 10);
    return isNaN(lastSeen) || now - lastSeen > SESSION_TIMEOUT_MINUTES * 60 * 1000;
  }

  function getOrCreatePersistent(key, generator) {
    const fullKey = '__afs_' + key;
    let value = localStorage.getItem(fullKey);
    if (!value) {
      value = generator();
      localStorage.setItem(fullKey, value);
    }
    return value;
  }

  function detectBrowser() {
    const ua = navigator.userAgent;
    if (/Safari/.test(ua) && !/Chrome/.test(ua)) return 'Safari';
    if (/Edg\//.test(ua)) return 'Edge';
    if (/Chrome/.test(ua)) return 'Chrome';
    return 'Other';
  }

  function pick(arr) {
    return arr[Math.floor(Math.random() * arr.length)];
  }

  function spoof(obj, prop, valueFn) {
    try {
      Object.defineProperty(obj, prop, {
        get: valueFn,
        configurable: true
      });
    } catch (e) {
      log('Spoof failed:', prop, e);
    }
  }

  const spoofed = {
    userAgent: getOrCreatePersistent('ua', () => pick([
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15',
      'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
    ])),
    platform: getOrCreatePersistent('platform', () => pick(['MacIntel', 'Win32', 'Linux x86_64'])),
    language: getOrCreatePersistent('lang', () => pick(['en-US', 'nl-NL', 'de-DE'])),
    screenWidth: parseInt(getOrCreatePersistent('sw', () => pick([1920, 1366, 1440]))),
    screenHeight: parseInt(getOrCreatePersistent('sh', () => pick([1080, 900, 768]))),
    cores: parseInt(getOrCreatePersistent('cores', () => pick([2, 4, 8]))),
    memory: parseInt(getOrCreatePersistent('mem', () => pick([2, 4, 8]))),
    timezone: getOrCreatePersistent('tz', () => pick(['UTC', 'Europe/Amsterdam', 'America/New_York']))
  };

  if (spoofSettings.userAgent) spoof(navigator, 'userAgent', () => spoofed.userAgent);
  if (spoofSettings.platform) spoof(navigator, 'platform', () => spoofed.platform);
  if (spoofSettings.language) {
    spoof(navigator, 'language', () => spoofed.language);
    spoof(navigator, 'languages', () => [spoofed.language, 'en']);
  }
  if (spoofSettings.screen) {
    spoof(window.screen, 'width', () => spoofed.screenWidth);
    spoof(window.screen, 'height', () => spoofed.screenHeight);
    spoof(window, 'innerWidth', () => spoofed.screenWidth);
    spoof(window, 'innerHeight', () => spoofed.screenHeight - 40);
  }
  if (spoofSettings.hardwareConcurrency) {
    spoof(navigator, 'hardwareConcurrency', () => spoofed.cores);
    spoof(navigator, 'deviceMemory', () => spoofed.memory);
  }
  if (spoofSettings.timezone && typeof Intl !== 'undefined') {
    const orig = Intl.DateTimeFormat.prototype.resolvedOptions;
    Intl.DateTimeFormat.prototype.resolvedOptions = function () {
      const options = orig.call(this);
      options.timeZone = spoofed.timezone;
      return options;
    };
  }

  if (spoofSettings.canvas && CanvasRenderingContext2D) {
    const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
    CanvasRenderingContext2D.prototype.getImageData = function (x, y, w, h) {
      const data = originalGetImageData.call(this, x, y, w, h);
      for (let i = 0; i < data.data.length; i += 4) {
        data.data[i] += Math.floor(Math.random() * 3);
        data.data[i + 1] += Math.floor(Math.random() * 3);
        data.data[i + 2] += Math.floor(Math.random() * 3);
      }
      return data;
    };
  }

  if (spoofSettings.webgl && WebGLRenderingContext) {
    const originalGL = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function (param) {
      const spoofMap = { 37445: 'FakeVendor Inc.', 37446: 'Virtual GPU Renderer', 3379: 4096, 35661: 8 };
      return spoofMap[param] || originalGL.call(this, param);
    };
  }

  if (spoofSettings.audio && window.AudioContext) {
    const ctx = window.AudioContext.prototype;
    spoof(ctx, 'sampleRate', () => 44100);
    if (AnalyserNode.prototype.getFloatFrequencyData) {
      const original = AnalyserNode.prototype.getFloatFrequencyData;
      AnalyserNode.prototype.getFloatFrequencyData = function (arr) {
        original.call(this, arr);
        for (let i = 0; i < arr.length; i++) {
          arr[i] += Math.random() * 0.1;
        }
      };
    }
  }

  if (spoofSettings.mediaDevices) {
    spoof(navigator, 'mediaDevices', () => ({
      enumerateDevices: () => Promise.resolve([])
    }));
  }

  if (spoofSettings.plugins) {
    spoof(navigator, 'plugins', () => []);
    spoof(navigator, 'mimeTypes', () => ({ length: 0 }));
  }

  if (spoofSettings.storageEstimate) {
    navigator.storage.estimate = () => Promise.resolve({
      usage: 5242880,
      quota: 1073741824
    });
  }

  if (spoofSettings.matchMedia) {
    const originalMatchMedia = window.matchMedia;
    window.matchMedia = function (query) {
      if (query.includes('color-scheme') || query.includes('forced-colors')) {
        return { matches: Math.random() > 0.5, media: query };
      }
      return originalMatchMedia.call(this, query);
    };
  }

  if (spoofSettings.sharedArrayBuffer) {
    spoof(window, 'SharedArrayBuffer', () => undefined);
  }

  function createUI() {
    if (!DEBUG) return;

    const savedPos = JSON.parse(localStorage.getItem(positionKey) || '{"top":10,"left":10}');
    const panel = document.createElement('div');
    panel.style.cssText = 'position:absolute;background:#1b1b1b;color:#fff;padding:10px;border-radius:8px;z-index:99999;font-family:sans-serif;font-size:13px;';
    panel.style.display = 'none';

    panel.innerHTML = '<strong>Anti-Fingerprinting Shield Settings</strong><br>';

    Object.keys(spoofDefaults).forEach(key => {
      const line = document.createElement('div');
      line.innerHTML = `<label><input type="checkbox" ${spoofSettings[key] ? 'checked' : ''} /> ${key}</label>`;
      line.querySelector('input').addEventListener('change', () => {
        spoofSettings[key] = !spoofSettings[key];
        saveSettings(spoofSettings);
        location.reload();
      });
      panel.appendChild(line);
    });

    const icon = document.createElement('div');
    icon.textContent = '⚙️';
    icon.title = 'Anti-Fingerprinting Shield Plus';
    icon.style.cssText = `
      position:fixed;
      top:${savedPos.top}px;
      left:${savedPos.left}px;
      cursor:move;
      z-index:99999;
      font-size:20px;
      user-select:none;
    `;

    icon.addEventListener('click', () => {
      panel.style.display = (panel.style.display === 'none') ? 'block' : 'none';
      const iconBox = icon.getBoundingClientRect();
      panel.style.top = `${iconBox.top + 25}px`;
      panel.style.left = `${iconBox.left}px`;
    });

    let isDragging = false;
    let offsetX = 0;
    let offsetY = 0;

    icon.addEventListener('mousedown', e => {
      isDragging = true;
      offsetX = e.clientX - icon.offsetLeft;
      offsetY = e.clientY - icon.offsetTop;
    });

    document.addEventListener('mousemove', e => {
      if (isDragging) {
        icon.style.left = `${e.clientX - offsetX}px`;
        icon.style.top = `${e.clientY - offsetY}px`;
      }
    });

    document.addEventListener('mouseup', () => {
      if (isDragging) {
        isDragging = false;
        localStorage.setItem(positionKey, JSON.stringify({
          top: icon.offsetTop,
          left: icon.offsetLeft
        }));
      }
    });

    document.addEventListener('fullscreenchange', () => {
      const isFullscreen = !!document.fullscreenElement;
      icon.style.display = isFullscreen ? 'none' : 'block';
    });

    document.addEventListener('keydown', e => {
      if (e.ctrlKey && e.shiftKey && e.key === 'F') {
        if (!document.body.contains(panel)) document.body.appendChild(panel);
        panel.style.display = 'block';
      }
    });

    document.addEventListener('DOMContentLoaded', () => {
      document.body.appendChild(icon);
      document.body.appendChild(panel);
    });
  }

  createUI();
})();