Greasy Fork

Greasy Fork is available in English.

豆包去水印

通过hook掉JSON.parse实现豆包AI生图下载原图去水印!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         豆包去水印
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  通过hook掉JSON.parse实现豆包AI生图下载原图去水印!
// @author       核心脚本作者: https://github.com/LauZzL/doubao-downloader  |  二次开发:小张 | 个人博客:https://blog.z-l.top | 公众号“爱吃馍” | 设计导航站 :https://dh.z-l.top | quicker账号昵称:星河城野❤
// @license      GPL-3.0
// @match        https://www.doubao.com/*
// @icon         https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/static/image/logo-icon-white-bg.72df0b1a.png
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  function findAllKeysInJson(obj, key) {
    const results = [];
    function search(current) {
      if (current && typeof current === 'object') {
        if (!Array.isArray(current) && Object.prototype.hasOwnProperty.call(current, key)) {
          results.push(current[key]);
        }
        const items = Array.isArray(current) ? current : Object.values(current);
        for (const item of items) {
          search(item);
        }
      }
    }
    search(obj);
    return results;
  }

  let _parse = JSON.parse;
  JSON.parse = function (data) {
    let jsonData = _parse(data);
    if (!data.match('creations')) return jsonData;
    let creations = findAllKeysInJson(jsonData, 'creations');
    if (creations.length > 0) {
      creations.forEach((creaetion) => {
        creaetion.map((item) => {
          // 原始图片url
          const rawUrl = item.image.image_ori_raw.url;
          // 下载原图时的去水印选项. 绑定到 下载去水印复选框
          // 根据设置决定是否替换URL
          const downloadEnabled = typeof GM_getValue !== 'undefined' ? GM_getValue('download-watermark', true) : true;
          const largeEnabled = typeof GM_getValue !== 'undefined' ? GM_getValue('large-watermark', true) : true;
          const previewEnabled = typeof GM_getValue !== 'undefined' ? GM_getValue('preview-watermark', true) : true;

          // 应用去水印设置到图片URL
          applyWatermarkSettings(item.image, rawUrl);
          return item;
        });
      })
    }
    return jsonData;
  }
  // 注册菜单命令打开设置界面
  GM_registerMenuCommand('设置首选项', function () {
    // 触发设置按钮点击事件以显示模态框
    document.querySelector('#watermark-settings-button').click();
  });

  // 创建设置按钮
  function createSettingsButton() {
    GM_addStyle(`
  .settings-btn {
    position: fixed;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    z-index: 9999;
    padding: 0;
    background: #0057ff0f;
    border: 1px solid #0057ff26;
    border-radius: 26px 0 0 26px;
    cursor: move;
    transition: all 0.3s ease;
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .settings-btn img {
    width: 32px;
    height: 32px;
    object-contain;
    pointer-events: none; /* 确保图片不阻止拖拽事件 */
  }
`);

    const button = document.createElement('button');
    button.id = 'watermark-settings-button';
    button.className = 'settings-btn';
    button.innerHTML = '<img src="https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/static/image/logo-icon-white-bg.72df0b1a.png" alt="设置">';
    let isDragging = false;
    let offsetX, offsetY;

    let longPressTimer;
    const LONG_PRESS_DELAY = 100; // 缩短长按时间,提高拖拽灵敏度

    button.addEventListener('mousedown', (e) => {
      // 阻止事件冒泡,确保整个按钮区域都能触发拖拽
      e.stopPropagation();
      const rect = button.getBoundingClientRect();
      offsetX = e.clientX - rect.left;
      offsetY = e.clientY - rect.top;
      isDragging = false;
      button.style.transition = 'none';

      // 启动长按定时器
      longPressTimer = setTimeout(() => {
        // 只有长按后才允许拖拽
        isDragging = true;
        button.style.cursor = 'grabbing';
      }, LONG_PRESS_DELAY);
    });

    document.addEventListener('mousemove', (e) => {
      // 只有长按定时器触发后才允许拖拽
      if (!isDragging) return;

      // 放宽鼠标范围检查,提高拖拽容错性
      const rect = button.getBoundingClientRect();
      // 扩大检测范围10px
      if (e.clientX < rect.left - 10 || e.clientX > rect.right + 10 || e.clientY < rect.top - 10 || e.clientY > rect.bottom + 10) {
        isDragging = false;
        return;
      }

      if (isDragging) {
        // 检查鼠标是否仍在按钮范围内
        const rect = button.getBoundingClientRect();
        if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) {
          isDragging = false;
          return;
        }

        e.preventDefault();

        const newTop = e.clientY - offsetY;
        const windowHeight = window.innerHeight;
        const buttonHeight = button.offsetHeight;
        const maxTop = windowHeight - buttonHeight;
        const constrainedTop = Math.max(0, Math.min(newTop, maxTop));

        button.style.top = `${constrainedTop}px`;
        button.style.transform = 'translateY(0)';
      }
    });

    document.addEventListener('mouseup', () => {
      // 清除长按定时器
      clearTimeout(longPressTimer);
      isDragging = false;
      button.style.transition = 'all 0.3s ease';
      button.style.cursor = 'move';
    });

    button.addEventListener('mouseleave', () => {
      // 清除长按定时器
      clearTimeout(longPressTimer);
      isDragging = false;
      button.style.transition = 'all 0.3s ease';
      button.style.cursor = 'move';
    });

    button.addEventListener('click', (e) => {
      // 如果是拖拽状态,完全阻止点击事件
      if (isDragging) {
        isDragging = false;
        e.stopPropagation();
        e.preventDefault();
        return;
      }
      e.stopPropagation();
      e.preventDefault();
      // 创建外部页面弹窗容器,类似花瓣去水印脚本的弹窗方式
      const popupContainer = document.createElement('div');
      popupContainer.id = 'settings-popup';
      popupContainer.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 999999;
    backdrop-filter: blur(5px);
      `;

      // 创建弹窗内容容器
      const popupContent = document.createElement('div');
      popupContent.style.cssText = `
        width: 583px;
        height: 631px;
        background: white;
        border-radius: 28px;
        overflow: hidden;
        position: relative;
      `;

      // 创建关闭按钮
      const closeButton = document.createElement('button');
      closeButton.textContent = '×';
      closeButton.style.cssText = `
        position: absolute;
    top: 14px;
    right: 14px;
    background: rgba(0, 0, 0, 0.2);
    color: white;
    border: none;
    border-radius: 50%;
    width: 32px;
    height: 32px;
    font-size: 22px;
    cursor: pointer;
    display: flex
;
    align-items: center;
    justify-content: center;
        z-index: 10;
      `;

      // 创建iframe加载外部设置页面
      const settingsIframe = document.createElement('iframe');
      // 恢复为本地服务器URL,确保本地服务器已启动
      // 使用GitHub Pages上的设置界面URL,提高可访问性
      settingsIframe.src = 'https://xiaolongmr.github.io/tampermonkey-scripts/%E8%B1%86%E5%8C%85%E5%8E%BB%E6%B0%B4%E5%8D%B0/svg%20to%20html/%E8%AE%BE%E7%BD%AE%E9%A6%96%E9%80%89%E9%A1%B9.html';
      // 添加错误处理
      settingsIframe.onerror = function () {
        alert('设置页面加载失败,请确保本地服务器已启动且文件路径正确');
        closePopup();
      };
      settingsIframe.onload = function () {
        console.log('设置页面加载成功');
        // 发送当前设置状态到iframe
        const currentSettings = {
          'download-watermark': GM_getValue('download-watermark', true),
          'large-watermark': GM_getValue('large-watermark', true),
          'preview-watermark': GM_getValue('preview-watermark', true)
        };
        try {
          settingsIframe.contentWindow.postMessage({
            type: 'INITIAL_SETTINGS',
            settings: currentSettings
          }, 'https://xiaolongmr.github.io');
        } catch (e) {
          console.error('Failed to send initial settings:', e);
        }
      };
      settingsIframe.style.cssText = `
        width: 100%;
        height: 100%;
        border: none;
      `;

      // 组装弹窗
      popupContent.appendChild(closeButton);
      popupContent.appendChild(settingsIframe);
      popupContainer.appendChild(popupContent);
      document.body.appendChild(popupContainer);

      // 关闭弹窗功能
      const closePopup = () => popupContainer.remove();
      closeButton.addEventListener('click', closePopup);
      popupContainer.addEventListener('click', (e) => {
        if (e.target === popupContainer) closePopup();
      });
    });
    document.body.appendChild(button);
  }

  // 应用去水印设置到图片对象
  function applyWatermarkSettings(imageObj, rawUrl) {
    const downloadEnabled = GM_getValue('download-watermark', true);
    const largeEnabled = GM_getValue('large-watermark', true);
    const previewEnabled = GM_getValue('preview-watermark', true);

    if (downloadEnabled) imageObj.image_ori.url = rawUrl;
    if (largeEnabled) imageObj.image_preview.url = rawUrl;
    if (previewEnabled) imageObj.image_thumb.url = rawUrl;
  }

  // 实时更新页面上的图片URL
  function updatePageImages() {
    // 获取当前设置
    const downloadEnabled = GM_getValue('download-watermark', true);
    const largeEnabled = GM_getValue('large-watermark', true);
    const previewEnabled = GM_getValue('preview-watermark', true);

    // 查找所有可能的图片元素并更新
    document.querySelectorAll('img[src*="image_ori"], img[src*="image_preview"], img[src*="image_thumb"]').forEach(img => {
      // 提取原始URL(移除水印参数)
      const rawUrl = img.src.split('?')[0];

      // 根据图片类型应用对应设置
      if (img.src.includes('image_ori') && downloadEnabled) {
        img.src = rawUrl;
      } else if (img.src.includes('image_preview') && largeEnabled) {
        img.src = rawUrl;
      } else if (img.src.includes('image_thumb') && previewEnabled) {
        img.src = rawUrl;
      }
    });
  }

  function saveSetting(id, checked) {
    GM_setValue(id, checked);
    localStorage.setItem(`watermark-${id}`, JSON.stringify(checked));

    // 通知iframe更新设置
    const iframe = document.getElementById('settings-iframe');
    if (iframe && iframe.contentWindow) {
      try {
        iframe.contentWindow.postMessage({ type: 'SETTING_UPDATED', id, checked }, 'https://xiaolongmr.github.io');
      } catch (e) {
        console.error('Failed to send setting update:', e);
      }
    }

    // 设置更改后立即更新页面图片
    updatePageImages();
  }

  // 在页面加载完成后创建按钮
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', createSettingsButton);
  } else {
    createSettingsButton();
  }

  // 添加跨域消息通信
  // 支持GitHub Pages域名的跨域消息通信
  window.addEventListener('message', function (e) {
    // 允许来自GitHub Pages和本地服务器的消息,便于开发和生产环境切换
    if (e.origin !== 'https://xiaolongmr.github.io' && e.origin !== 'http://127.0.0.1:5501') return;

    if (e.data.type === 'SETTING_CHANGED') {
      const { id, checked } = e.data;
      saveSetting(id, checked);
    }
  });

})();