Greasy Fork

Greasy Fork is available in English.

Google AI Studio 图片去水印

自动阻止 Google AI Studio 加载 watermark_v4.png,让生成图片无水印

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
  // @name         Google AI Studio 图片去水印
  // @namespace    https://aistudio.google.com/
  // @version      1.0.3
  // @description  自动阻止 Google AI Studio 加载 watermark_v4.png,让生成图片无水印
  // @match        https://aistudio.google.com/*
  // @run-at       document-start
  // @grant        none
  // @auther       hepingfly(公众号:和平本记)
  // @noframes
  // @license      GNU AGPLv3
  // ==/UserScript==

  (function () {
    'use strict';

    const pattern = /watermark_v4\.png/i;
    const logPrefix = '[AI Studio Watermark Blocker]';
    const log = (...args) => console.log(logPrefix, ...args);

    const shouldBlock = (candidate) => {
      if (!candidate) return false;
      const value = String(candidate);
      if (pattern.test(value)) return true;
      try {
        const absolute = new URL(value, location.href).href;
        return pattern.test(absolute);
      } catch {
        return false;
      }
    };

    // fetch()
    if (window.fetch) {
      const originalFetch = window.fetch;
      window.fetch = function (...args) {
        const input = args[0];
        const url = typeof input === 'string' ? input : input?.url;
        if (shouldBlock(url)) {
          log('Blocked fetch:', url);
          return Promise.reject(
            new DOMException('Blocked watermark request', 'AbortError'),
          );
        }
        return originalFetch.apply(this, args);
      };
    }

    // XMLHttpRequest
    const xhrProto = XMLHttpRequest.prototype;
    const originalOpen = xhrProto.open;
    const originalSend = xhrProto.send;

    xhrProto.open = function (method, url, ...rest) {
      this.__watermarkBlocked = shouldBlock(url);
      if (this.__watermarkBlocked) {
        log('Blocked XHR:', url);
      }
      return originalOpen.call(this, method, url, ...rest);
    };

    xhrProto.send = function (...args) {
      if (this.__watermarkBlocked) {
        this.abort();
        return;
      }
      return originalSend.apply(this, args);
    };

    // <img src="...">
    const imageProto = HTMLImageElement.prototype;
    const srcDescriptor = Object.getOwnPropertyDescriptor(imageProto, 'src');
    if (srcDescriptor?.set && srcDescriptor?.get) {
      Object.defineProperty(imageProto, 'src', {
        configurable: true,
        enumerable: srcDescriptor.enumerable,
        get() {
          return srcDescriptor.get.call(this);
        },
        set(value) {
          if (shouldBlock(value)) {
            log('Blocked <img> src:', value);
            return;
          }
          return srcDescriptor.set.call(this, value);
        },
      });
    }

    // setAttribute(覆盖 src/style 时防止回退路径触发)
    const originalSetAttribute = Element.prototype.setAttribute;
    Element.prototype.setAttribute = function (name, value) {
      const lower = String(name).toLowerCase();
      const strValue = String(value);

      if (lower === 'src' && shouldBlock(strValue)) {
        log('Blocked setAttribute src:', strValue);
        return;
      }

      if (lower === 'style' && shouldBlock(strValue)) {
        log('Sanitized inline style:', strValue);
        const sanitized = strValue.replace(pattern, 'none');
        return originalSetAttribute.call(this, name, sanitized);
      }

      return originalSetAttribute.call(this, name, value);
    };

    // style.setProperty / style.backgroundImage
    const styleProto = CSSStyleDeclaration.prototype;
    const originalSetProperty = styleProto.setProperty;
    styleProto.setProperty = function (property, value, priority) {
      if (/background-image/i.test(property) && shouldBlock(value)) {
        log('Blocked background-image via setProperty:', value);
        return;
      }
      return originalSetProperty.call(this, property, value, priority);
    };

    const bgDescriptor = Object.getOwnPropertyDescriptor(
      styleProto,
      'backgroundImage',
    );
    if (bgDescriptor?.set && bgDescriptor?.get) {
      Object.defineProperty(styleProto, 'backgroundImage', {
        configurable: true,
        enumerable: bgDescriptor.enumerable,
        get() {
          return bgDescriptor.get.call(this);
        },
        set(value) {
          if (shouldBlock(value)) {
            log('Blocked backgroundImage setter:', value);
            return;
          }
          return bgDescriptor.set.call(this, value);
        },
      });
    }

    log('Initialized');
  })();