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

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu budete musieť nainštalovať rozšírenie, ako je napríklad Tampermonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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