豆包去水印

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

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

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

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

您需要先安装一个扩展,例如 篡改猴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);
    }
  });

})();