通过hook掉JSON.parse实现豆包AI生图下载原图去水印!
// ==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);
}
});
})();