Greasy Fork is available in English.
直接拦截Blob URL创建,强制进行图片拼接(无拼接标记)
// ==UserScript==
// @name Blob拦截图片拼接脚本(纯净版)——实现为豆包ai生成的所有图片去水印
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 直接拦截Blob URL创建,强制进行图片拼接(无拼接标记)
// @author 小张 | 个人博客:https://blog.z-l.top | 公众号“爱吃馍” | 设计导航站 :https://dh.z-l.top | quicker账号昵称:星河城野❤
// @license GPL-3.0
// @match https://www.doubao.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
console.log('=====================================');
console.log('Blob拦截图片拼接脚本(纯净版)已加载');
console.log('=====================================');
// 配置
const CONFIG = {
DEBUG_MODE: true,
SPLIT_RATIO: 0.5, // 分割比例
TIMEOUT: 20000, // 超时时间
FORCE_SPLICING: true // 强制拼接模式
};
// 全局状态
const state = {
originalCreateObjectURL: null,
originalRevokeObjectURL: null,
pendingBlobs: new Map(), // 待处理的Blob
processedBlobs: new Map(), // 已处理的Blob
inPaintingImageData: null, // in_painting_picture图片数据
downloadTriggered: false, // 是否触发了下载
lastSplicedUrl: null // 最后拼接的URL
};
// 初始化 - 在document-start阶段立即执行
function initialize() {
log('开始初始化...');
// 保存原始方法
state.originalCreateObjectURL = URL.createObjectURL;
state.originalRevokeObjectURL = URL.revokeObjectURL;
// 重写Blob URL方法
overrideBlobMethods();
// 设置其他监听器
setupEventListeners();
// 开始查找in_painting_picture图片
startLookingForInPaintingPicture();
log('初始化完成!');
}
// 重写Blob URL相关方法
function overrideBlobMethods() {
log('重写Blob URL方法...');
// 重写createObjectURL
URL.createObjectURL = function(blob) {
const timestamp = new Date().toLocaleTimeString();
log(`[${timestamp}] 拦截到Blob创建:`, {
type: blob.type,
size: blob.size,
isImage: blob.type.startsWith('image/')
});
// 如果是图片类型且触发了下载,进行拼接
if (blob.type.startsWith('image/') && state.downloadTriggered) {
log('检测到图片Blob且下载已触发,准备拼接...');
// 存储原始Blob
const blobId = generateBlobId(blob);
state.pendingBlobs.set(blobId, blob);
// 处理拼接
processBlobSplicing(blobId, blob);
// 返回临时URL,稍后会被替换
const tempUrl = state.originalCreateObjectURL.call(URL, new Blob(['processing'], {type: 'text/plain'}));
state.pendingBlobs.set(tempUrl, blobId);
return tempUrl;
}
// 正常处理非图片Blob或未触发下载的情况
return state.originalCreateObjectURL.call(URL, blob);
};
// 重写revokeObjectURL
URL.revokeObjectURL = function(url) {
const timestamp = new Date().toLocaleTimeString();
log(`[${timestamp}] 拦截到Blob释放:`, url);
// 如果是我们的临时URL,不释放
if (state.pendingBlobs.has(url)) {
log('检测到临时URL,跳过释放');
return;
}
return state.originalRevokeObjectURL.call(URL, url);
};
}
// 设置事件监听器
function setupEventListeners() {
log('设置事件监听器...');
// 监听所有点击事件
document.addEventListener('click', handleGlobalClick, true);
// 监听页面变化
const observer = new MutationObserver(handlePageChanges);
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src', 'class', 'data-testid']
});
// 页面卸载清理
window.addEventListener('beforeunload', cleanup);
}
// 处理全局点击
function handleGlobalClick(e) {
if (!e.target) return;
// 检查是否点击了下载按钮
const downloadButton = findDownloadButton(e.target);
if (downloadButton) {
log('检测到下载按钮点击!');
// 标记下载已触发
state.downloadTriggered = true;
// 设置超时,防止无限等待
setTimeout(() => {
state.downloadTriggered = false;
log('下载触发状态已重置(超时)');
}, CONFIG.TIMEOUT);
// 如果是链接,阻止默认行为
if (downloadButton.tagName === 'A') {
e.preventDefault();
e.stopPropagation();
// 延迟执行,给其他事件处理器时间
setTimeout(() => {
simulateDownloadClick(downloadButton);
}, 100);
}
}
}
// 查找下载按钮
function findDownloadButton(element) {
let current = element;
while (current && current !== document) {
if (isDownloadButton(current)) {
return current;
}
current = current.parentElement;
}
return null;
}
// 判断是否为下载按钮
function isDownloadButton(element) {
if (!element) return false;
const testId = element.getAttribute('data-testid');
const className = element.className;
const text = (element.textContent || element.getAttribute('title') || '').toLowerCase();
const href = element.tagName === 'A' ? element.href : '';
const isDownload = (
testId === 'edit_image_download_button' ||
className.includes('hover-DQYLdi') ||
className.includes('imagelink-nowatermark') ||
text.includes('下载') ||
text.includes('download') ||
text.includes('原图') ||
(href && (href.includes('download') || href.includes('original')))
);
if (isDownload) {
log('识别到下载按钮:', {
tagName: element.tagName,
testId: testId,
className: className,
text: text,
href: href
});
}
return isDownload;
}
// 模拟下载点击
function simulateDownloadClick(button) {
log('模拟下载点击...');
try {
// 创建按钮克隆
const clone = button.cloneNode(true);
clone.style.display = 'none';
clone.style.opacity = '0';
clone.style.pointerEvents = 'none';
document.body.appendChild(clone);
// 移除所有事件监听器
clone.onclick = null;
// 触发点击
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
ctrlKey: false,
shiftKey: false,
altKey: false,
metaKey: false
});
clone.dispatchEvent(event);
log('模拟点击已触发');
setTimeout(() => {
document.body.removeChild(clone);
}, 1000);
} catch (error) {
log('模拟点击失败:', error);
}
}
// 开始查找in_painting_picture图片
function startLookingForInPaintingPicture() {
log('开始查找in_painting_picture图片...');
// 立即查找
checkForInPaintingPicture();
// 定时查找
setInterval(checkForInPaintingPicture, 2000);
}
// 检查in_painting_picture图片
function checkForInPaintingPicture() {
if (state.inPaintingImageData) {
return; // 已经找到了
}
const images = document.querySelectorAll('[data-testid="in_painting_picture"]');
if (images.length > 0) {
const targetImage = images[0];
log('找到in_painting_picture图片:', targetImage.src);
// 下载图片数据
downloadInPaintingImage(targetImage.src);
// 监听图片变化
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.attributeName === 'src') {
log('in_painting_picture图片src已更新:', targetImage.src);
downloadInPaintingImage(targetImage.src);
}
});
});
observer.observe(targetImage, { attributes: true });
} else {
log('未找到in_painting_picture图片,继续等待...');
}
}
// 下载in_painting_picture图片
async function downloadInPaintingImage(url) {
log('下载in_painting_picture图片:', url);
try {
const response = await fetch(url, {
mode: 'cors',
headers: {
'User-Agent': navigator.userAgent,
'Accept': 'image/*',
'Referer': window.location.href
},
cache: 'no-cache'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const blob = await response.blob();
const reader = new FileReader();
reader.onload = function(e) {
state.inPaintingImageData = e.target.result;
log('in_painting_picture图片下载完成,大小:', blob.size, 'bytes');
};
reader.readAsDataURL(blob);
} catch (error) {
log('下载in_painting_picture图片失败:', error);
}
}
// 处理Blob拼接
async function processBlobSplicing(blobId, originalBlob) {
log('开始处理Blob拼接...');
try {
// 等待in_painting_picture图片下载完成
if (!state.inPaintingImageData) {
log('等待in_painting_picture图片下载完成...');
await waitForInPaintingImage();
}
log('开始拼接图片...');
// 转换原始Blob为DataURL
const originalData = await blobToDataURL(originalBlob);
// 拼接图片(无标记版本)
const splicedBlob = await spliceImages(originalData, state.inPaintingImageData);
// 生成新的Blob URL
const splicedUrl = state.originalCreateObjectURL.call(URL, splicedBlob);
state.processedBlobs.set(blobId, splicedUrl);
state.lastSplicedUrl = splicedUrl;
log('图片拼接完成,生成新Blob URL:', splicedUrl);
// 触发下载
triggerDownload(splicedUrl, 'spliced_image.png');
// 重置下载状态
state.downloadTriggered = false;
} catch (error) {
log('Blob拼接失败:', error);
// 失败时使用原始Blob
const originalUrl = state.originalCreateObjectURL.call(URL, originalBlob);
triggerDownload(originalUrl, 'original_image.png');
state.downloadTriggered = false;
}
}
// 等待in_painting_picture图片
function waitForInPaintingImage() {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const checkInterval = setInterval(() => {
if (state.inPaintingImageData) {
clearInterval(checkInterval);
resolve();
} else if (Date.now() - startTime > CONFIG.TIMEOUT) {
clearInterval(checkInterval);
reject(new Error('等待in_painting_picture图片超时'));
}
}, 100);
});
}
// Blob转换为DataURL
function blobToDataURL(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.readAsDataURL(blob);
});
}
// 拼接图片(无标记版本)
function spliceImages(originalData, editData) {
log('执行图片拼接...');
return new Promise((resolve, reject) => {
const originalImg = new Image();
const editImg = new Image();
let originalLoaded = false;
let editLoaded = false;
originalImg.onload = function() {
log('原始图片加载完成:', originalImg.width, '×', originalImg.height);
originalLoaded = true;
checkReady();
};
editImg.onload = function() {
log('编辑图片加载完成:', editImg.width, '×', editImg.height);
editLoaded = true;
checkReady();
};
originalImg.onerror = function() {
reject(new Error('原始图片加载失败'));
};
editImg.onerror = function() {
reject(new Error('编辑图片加载失败'));
};
originalImg.src = originalData;
editImg.src = editData;
function checkReady() {
if (originalLoaded && editLoaded) {
try {
// 创建Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 确定目标尺寸
const targetWidth = Math.max(originalImg.width, editImg.width);
const targetHeight = Math.max(originalImg.height, editImg.height);
canvas.width = targetWidth;
canvas.height = targetHeight;
log('拼接画布尺寸:', targetWidth, '×', targetHeight);
// 计算分割线位置
const splitX = Math.floor(targetWidth * CONFIG.SPLIT_RATIO);
log('分割线位置:', splitX);
// 绘制原始图片左半部分
ctx.drawImage(
originalImg,
0, 0, splitX, originalImg.height, // 源区域
0, 0, splitX, targetHeight // 目标区域
);
// 绘制编辑图片右半部分
const editSplitX = Math.floor(editImg.width * CONFIG.SPLIT_RATIO);
ctx.drawImage(
editImg,
editSplitX, 0, editImg.width - editSplitX, editImg.height, // 源区域
splitX, 0, targetWidth - splitX, targetHeight // 目标区域
);
// 转换为Blob(不添加任何标记)
canvas.toBlob((blob) => {
log('图片拼接成功,生成Blob大小:', blob.size, 'bytes');
resolve(blob);
}, 'image/png', 1.0);
} catch (error) {
reject(new Error('图片拼接失败:' + error.message));
}
}
}
});
}
// 触发下载
function triggerDownload(blobUrl, filename) {
log('触发下载:', filename);
try {
const a = document.createElement('a');
a.href = blobUrl;
a.download = filename;
a.style.cssText = `
position: fixed !important;
left: -9999px !important;
top: -9999px !important;
display: block !important;
opacity: 0 !important;
pointer-events: none !important;
`;
document.body.appendChild(a);
// 创建鼠标事件
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
detail: 1,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
ctrlKey: false,
shiftKey: false,
altKey: false,
metaKey: false,
button: 0,
buttons: 1
});
a.dispatchEvent(event);
log('下载事件已触发');
setTimeout(() => {
if (document.body.contains(a)) {
document.body.removeChild(a);
}
// 延迟释放资源
setTimeout(() => {
if (blobUrl !== state.lastSplicedUrl) {
state.originalRevokeObjectURL.call(URL, blobUrl);
log('已释放Blob URL资源:', blobUrl);
}
}, 30000);
}, 100);
} catch (error) {
log('触发下载失败:', error);
throw error;
}
}
// 处理页面变化
function handlePageChanges(mutations) {
mutations.forEach(mutation => {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
// 检查新添加的元素中是否有in_painting_picture图片
const images = node.querySelectorAll('[data-testid="in_painting_picture"]');
if (images.length > 0 && !state.inPaintingImageData) {
log('页面变化中发现in_painting_picture图片');
checkForInPaintingPicture();
}
}
});
}
});
}
// 生成Blob ID
function generateBlobId(blob) {
return `blob_${Date.now()}_${blob.size}_${blob.type}`;
}
// 清理资源
function cleanup() {
log('清理脚本资源...');
// 恢复原始方法
if (state.originalCreateObjectURL) {
URL.createObjectURL = state.originalCreateObjectURL;
}
if (state.originalRevokeObjectURL) {
URL.revokeObjectURL = state.originalRevokeObjectURL;
}
// 释放所有Blob URL
state.processedBlobs.forEach((url) => {
try {
state.originalRevokeObjectURL.call(URL, url);
} catch (e) {
// 忽略已释放的URL
}
});
state.pendingBlobs.clear();
state.processedBlobs.clear();
log('脚本资源清理完成');
}
// 日志函数
function log(...args) {
if (CONFIG.DEBUG_MODE) {
console.log('[Blob拦截拼接脚本]', new Date().toLocaleTimeString(), ...args);
}
}
// 立即启动脚本
initialize();
})();