登录

屏蔽烧饼游戏自动全屏的油猴脚本

717
  • 楼主 编辑于

    不知道什么时候加的自动全屏,有点难受

    油猴脚本
    // ==UserScript==
    // @name         烧饼游戏 · 阻止自动网页全屏
    // @namespace    sb-matching-tool
    // @version      1.0.0
    // @description  阻止所有烧饼游戏自动网页全屏,兼容以后沿用共用实现的新游戏,保留手动全屏。
    // @match        https://sb.sb/games/*
    // @run-at       document-start
    // @grant        none
    // @sandbox      raw
    // @noframes
    // @license      MIT
    // ==/UserScript==
    
    (function preventAutoWebFullscreen() {
      'use strict';
      if (location.origin !== 'https://sb.sb' || !location.pathname.startsWith('/games/')) return;
      const installed = Symbol.for('sb.no-auto-webfullscreen.v1');
      if (window[installed]) return;
      Object.defineProperty(window, installed, { value: true });
    
      const panelSelector = '[data-wheel-panel], [data-t3d-panel], #kt-app';
      const controls = '[data-game-webfs], [data-game-fs], #kt-webfs, #kt-fullscreen, [data-act="webfs"], [data-act="fs"]';
      const panels = new Map();
      const games = new WeakSet();
      const renderers = new WeakSet();
      const mounts = new WeakMap();
      const block = function () {};
    
      // Observe assignments synchronously: game modules often cache bbsGame immediately
      // after games.js initializes it. Polling or DOMContentLoaded would be too late.
      function watchValue(owner, key, transform) {
        const descriptor = Object.getOwnPropertyDescriptor(owner, key);
        if (descriptor && !descriptor.configurable) {
          const next = transform(owner[key]);
          if (descriptor.writable) owner[key] = next;
          return;
        }
        let value = transform(owner[key]);
        Object.defineProperty(owner, key, {
          configurable: true,
          enumerable: descriptor ? descriptor.enumerable : true,
          get() { return descriptor && descriptor.get ? transform(descriptor.get.call(this)) : value; },
          set(next) {
            if (descriptor && descriptor.set) {
              descriptor.set.call(this, next);
              value = transform(descriptor.get ? descriptor.get.call(this) : next);
            } else value = transform(next);
          }
        });
      }
    
      function isObject(value) {
        return value !== null && (typeof value === 'object' || typeof value === 'function');
      }
    
      watchValue(window, 'bbsGame', game => {
        if (isObject(game) && !games.has(game)) {
          games.add(game);
          watchValue(game, 'playFS', () => block);
        }
        return game;
      });
    
      function nativeFullscreen() {
        return document.fullscreenElement || document.webkitFullscreenElement || null;
      }
    
      function expanded(record) {
        const list = record.element.classList;
        return record.kind === 'temple' ? list.contains('kt-webfs') : list.contains('is-fs') || list.contains('is-webfs');
      }
    
      function remember(element) {
        if (!element || !element.matches(panelSelector)) return null;
        if (!panels.has(element)) {
          panels.set(element, {
            element,
            kind: element.id === 'kt-app' ? 'temple' : element.hasAttribute('data-t3d-panel') ? 'thunder3d' : 'common',
            manual: false,
            pending: false,
            active: false,
            timer: null
          });
        }
        return panels.get(element);
      }
    
      function forgetIntent(record) {
        window.clearTimeout(record.timer);
        record.timer = null;
        record.manual = record.pending = false;
      }
    
      function manualIntent(record, mode) {
        if (!record) return;
        const exiting = mode === 'web' ? expanded(record) : nativeFullscreen() === record.element;
        forgetIntent(record);
        if (exiting) return;
        record.manual = record.pending = true;
        // Keep permission through asynchronous native <-> web fullscreen transitions.
        // A failed/unused button must not authorize a later automatic expansion.
        record.timer = window.setTimeout(() => {
          record.timer = null;
          record.pending = false;
          if (!expanded(record)) record.manual = false;
        }, 5000);
      }
    
      // The 3D game's fullscreen controls also work via its keyboard/gamepad menu.
      // Its start/practice/resume callbacks deliberately receive no permission.
      watchValue(window, 'bbsThunder3DGame', renderer => {
        if (isObject(renderer) && !renderers.has(renderer)) {
          renderers.add(renderer);
          watchValue(renderer, 'mount', original => {
            if (typeof original !== 'function') return original;
            if (mounts.has(original)) return mounts.get(original);
            const wrapped = function (options, ...rest) {
              if (!options || typeof options.onMenu !== 'function') return Reflect.apply(original, this, [options, ...rest]);
              const onMenu = options.onMenu;
              const next = { ...options, onMenu: function (action, ...args) {
                if (action === 'fs' || action === 'webfs') {
                  const panel = options.root && options.root.closest('[data-t3d-panel]');
                  manualIntent(remember(panel || document.querySelector('[data-t3d-panel]')), action === 'fs' ? 'native' : 'web');
                }
                return Reflect.apply(onMenu, this, [action, ...args]);
              } };
              return Reflect.apply(original, this, [next, ...rest]);
            };
            mounts.set(original, wrapped);
            mounts.set(wrapped, wrapped);
            return wrapped;
          });
        }
        return renderer;
      });
    
      function unlockPage() {
        if (nativeFullscreen()) return;
        for (const record of panels.values()) {
          if (record.kind !== 'temple' && record.element.isConnected && expanded(record)) {
            document.documentElement?.classList.add('game-fs-lock');
            return;
          }
        }
        document.documentElement?.classList.remove('game-fs-lock');
      }
    
      function restore(record) {
        const panel = record.element;
        if (record.kind === 'temple') {
          // The site's handler restores its private qa flag, saved overflow and scroll
          // position. Merely removing kt-webfs leaves those out of sync.
          const button = panel.querySelector('#kt-webfs');
          if (button && typeof button.onclick === 'function') button.click();
          return;
        }
        if (record.kind === 'common') {
          const button = panel.querySelector(panel.classList.contains('is-webfs') ? '[data-game-webfs]' : '[data-game-fs]');
          if (button) button.click();
        }
        if (record.kind === 'thunder3d' || expanded(record)) {
          panel.classList.remove('is-fs', 'is-webfs');
          for (const [selector, label, title] of [
            ['[data-game-webfs]', '网页全屏', '网页全屏(Esc 退出)'],
            ['[data-game-fs]', '全屏', '全屏']
          ]) {
            const button = panel.querySelector(selector);
            if (button) {
              button.classList.remove('is-on');
              button.setAttribute('aria-label', label);
              button.title = title;
            }
          }
          const renderer = window.bbsThunder3DGame;
          if (record.kind === 'thunder3d' && renderer && typeof renderer.fsLabels === 'function') renderer.fsLabels(false, false);
          window.dispatchEvent(new Event('resize'));
        }
        unlockPage();
      }
    
      function check(record) {
        if (nativeFullscreen() === record.element) {
          record.active = false;
          forgetIntent(record);
        } else if (expanded(record)) {
          if (record.manual) {
            record.active = true;
            record.pending = false;
            window.clearTimeout(record.timer);
            record.timer = null;
          } else restore(record);
        } else if (record.active) {
          record.active = false;
          if (!record.pending) forgetIntent(record);
        }
      }
    
      function discover(root, changed) {
        if (root.nodeType === 1 && root.matches(panelSelector)) changed.add(remember(root));
        if (root.querySelectorAll) root.querySelectorAll(panelSelector).forEach(element => changed.add(remember(element)));
      }
    
      const observer = new MutationObserver(mutations => {
        const changed = new Set();
        for (const mutation of mutations) {
          if (mutation.type === 'childList') mutation.addedNodes.forEach(node => discover(node, changed));
          else if (panels.has(mutation.target)) changed.add(panels.get(mutation.target));
        }
        for (const [element, record] of panels) {
          if (!element.isConnected) {
            if (expanded(record)) restore(record);
            forgetIntent(record);
            panels.delete(element);
          }
        }
        changed.forEach(check);
      });
      observer.observe(document, { subtree: true, childList: true, attributes: true, attributeFilter: ['class'] });
    
      document.addEventListener('click', event => {
        if (!event.isTrusted) return;
        const target = event.target && event.target.closest ? event.target : event.target?.parentElement;
        const button = target && target.closest(controls);
        const panel = button && button.closest(panelSelector);
        if (panel) {
          const web = button.matches('[data-game-webfs], #kt-webfs, [data-act="webfs"]');
          manualIntent(remember(panel), web ? 'web' : 'native');
        } else {
          for (const record of panels.values()) {
            if (record.pending && !expanded(record)) forgetIntent(record);
          }
        }
      }, true);
    
      for (const eventName of ['fullscreenchange', 'webkitfullscreenchange']) {
        document.addEventListener(eventName, () => panels.forEach(check));
      }
      const initial = new Set();
      discover(document, initial);
      initial.forEach(check);
    })();
    
    

    GreasyFork发布地址

    收藏支持反对打赏
    个性签名

    即使是坏掉的钟,一天也有两次是准的。

7 条回复

发表回复