Greasy Fork is available in English.

Battery Status API Spoofer

Either fully removes navigator.getBattery() or replaces it with a fixed 100%-charged state, to prevent fingerprinting/tracking via battery level and charging status

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Battery Status API Spoofer
// @namespace    https://violentmonkey.github.io/
// @version      1.0
// @description  Either fully removes navigator.getBattery() or replaces it with a fixed 100%-charged state, to prevent fingerprinting/tracking via battery level and charging status
// @author       Streampunk
// @icon         data:image/png;base64,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
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // ===================== CONFIG =====================
  // 'block' - completely removes getBattery()/navigator.battery, so calls
  //           throw/are undefined, exactly as if the API never existed
  //           (indistinguishable from browsers that dropped it, e.g.
  //           current Firefox/Safari).
  // 'fixed' - keeps the API present but always returns a fixed, plausible
  //           100%-charged / plugged-in state instead of real battery data.
  const MODE = 'block'; // <-- change to 'fixed' if you prefer a fake value
  // ====================================================

  const hasGetBattery = typeof navigator.getBattery === 'function';
  const legacyProps = ['battery', 'mozBattery', 'webkitBattery'].filter((p) => p in navigator);

  if (!hasGetBattery && legacyProps.length === 0) {
    return; // nothing to do, API not present at all
  }

  if (MODE === 'block') {
    // --- Fully remove the API ---

    if (hasGetBattery) {
      try {
        Object.defineProperty(navigator, 'getBattery', {
          configurable: true,
          value: undefined,
        });
      } catch (e) {
        try {
          const proto = Object.getPrototypeOf(navigator);
          Object.defineProperty(proto, 'getBattery', {
            configurable: true,
            value: undefined,
          });
        } catch (e2) {
          console.warn('[block-battery] failed to remove getBattery', e2);
        }
      }
    }

    legacyProps.forEach((prop) => {
      try {
        Object.defineProperty(navigator, prop, {
          configurable: true,
          get() { return undefined; },
        });
      } catch (e) {
        console.warn(`[block-battery] failed to remove navigator.${prop}`, e);
      }
    });

    return;
  }

  if (MODE === 'fixed') {
    // --- Return a fixed, plausible battery state ---

    // Fully charged and plugged in: the most common/least informative
    // state (doesn't reveal usage patterns, discharge rate, or battery
    // health, which were previously used for cross-session tracking).
    const FAKE_BATTERY_STATE = {
      charging: true,
      chargingTime: 0,
      dischargingTime: Infinity,
      level: 1.0,
    };

    function createFakeBatteryManager() {
      const listeners = {
        chargingchange: [],
        chargingtimechange: [],
        dischargingtimechange: [],
        levelchange: [],
      };

      const state = { ...FAKE_BATTERY_STATE };

      // Minimal EventTarget-like object exposing the same interface as the
      // real BatteryManager (properties + on*change handlers + addEventListener)
      const battery = {
        get charging() { return state.charging; },
        get chargingTime() { return state.chargingTime; },
        get dischargingTime() { return state.dischargingTime; },
        get level() { return state.level; },

        onchargingchange: null,
        onchargingtimechange: null,
        ondischargingtimechange: null,
        onlevelchange: null,

        addEventListener(type, listener) {
          if (listeners[type] && typeof listener === 'function') {
            listeners[type].push(listener);
          }
        },
        removeEventListener(type, listener) {
          if (listeners[type]) {
            const idx = listeners[type].indexOf(listener);
            if (idx !== -1) listeners[type].splice(idx, 1);
          }
        },
        dispatchEvent(event) {
          // Never actually triggered since state never changes, but kept
          // for interface completeness in case a site calls it directly.
          const type = event && event.type;
          if (listeners[type]) {
            listeners[type].forEach((fn) => {
              try { fn.call(battery, event); } catch (e) {}
            });
          }
          const handlerName = 'on' + type;
          if (typeof battery[handlerName] === 'function') {
            try { battery[handlerName].call(battery, event); } catch (e) {}
          }
          return true;
        },
      };

      return battery;
    }

    const fakeBattery = createFakeBatteryManager();
    const fakeBatteryPromise = Promise.resolve(fakeBattery);

    if (hasGetBattery) {
      try {
        Object.defineProperty(navigator, 'getBattery', {
          configurable: true,
          writable: true,
          value: function () {
            return fakeBatteryPromise;
          },
        });
      } catch (e) {
        try {
          const proto = Object.getPrototypeOf(navigator);
          Object.defineProperty(proto, 'getBattery', {
            configurable: true,
            writable: true,
            value: function () {
              return fakeBatteryPromise;
            },
          });
        } catch (e2) {
          console.warn('[spoof-battery] failed to override getBattery', e2);
        }
      }
    }

    legacyProps.forEach((prop) => {
      try {
        Object.defineProperty(navigator, prop, {
          configurable: true,
          get() { return fakeBattery; },
        });
      } catch (e) {
        console.warn(`[spoof-battery] failed to override navigator.${prop}`, e);
      }
    });

    return;
  }

  console.warn('[battery-privacy-script] unknown MODE value:', MODE);
})();