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

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

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

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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);
})();