// ==UserScript==
// @name 豆包&即梦AI下载无水印图片视频
// @namespace http://tampermonkey.net/
// @version 1.51
// @description 实现豆包&即梦生成的图片视频免费无水印下载
// @author 微信11208596
// @license UNLICENSED
// @match https://www.doubao.com/*
// @match https://jimeng.jianying.com/ai-tool/*
// @grant GM_download
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function () {
'use strict';
// 创建确认对话框样式
const style = document.createElement('style');
style.textContent = `
.download-confirm-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 10000;
min-width: 300px;
text-align: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.download-confirm-dialog h3 {
margin: 0 0 15px 0;
color: #333;
}
.download-confirm-dialog .buttons {
margin-top: 20px;
}
.download-confirm-dialog button {
margin: 0 10px;
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.download-confirm-dialog .confirm-btn {
background: #1890ff;
color: white;
}
.download-confirm-dialog .confirm-btn:disabled {
background: #b4b4b4;
cursor: not-allowed;
}
.download-confirm-dialog .cancel-btn {
background: #f5f5f5;
color: #333;
}
.download-confirm-dialog button:hover {
opacity: 0.8;
}
.download-confirm-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 9999;
}
`;
document.head.appendChild(style);
// 获取当前网站域名
const currentDomain = window.location.hostname;
// 创建确认对话框
function createConfirmDialog(mediaUrl, mediaType, downloadFunction) {
const overlay = document.createElement('div');
overlay.className = 'download-confirm-overlay';
const dialog = document.createElement('div');
dialog.className = 'download-confirm-dialog';
// 获取当前日期时间
const now = new Date();
const dateStr = `${now.getFullYear()}${(now.getMonth()+1).toString().padStart(2,'0')}${now.getDate().toString().padStart(2,'0')}`;
// 获取提示词
let promptText = '';
// 根据不同类型使用不同的选择器
let promptElement;
if (mediaType === 'video') {
// 视频页面的提示词选择器
promptElement = document.querySelector('span.lv-typography[class*="promptText-sTGKI"]');
} else {
// 图片页面的提示词选择器 - 添加豆包新的选择器
promptElement = document.querySelector('span.lv-typography[class*="promptText-"]') ||
document.querySelector('.message-text-aF_36u[data-testid="message_text_content"]');
}
if (promptElement) {
// 获取完整的提示词文本
let text = promptElement.textContent.trim();
// 如果是豆包的提示词,移除"帮我生成图片:"前缀
if (text.startsWith('帮我生成图片:')) {
text = text.replace('帮我生成图片:', '');
}
promptText = text
// 移除多余的空格和换行
.replace(/\s+/g, ' ')
// 移除引号和破折号
.replace(/^[""]-|[""]$/g, '')
// 移除文件名中的非法字符
.replace(/[\\/:*?"<>|]/g, '_')
// 移除比例信息
.replace(/比例「\d+:\d+」/, '')
.trim()
.substring(0, 100); // 限制长度
console.log(`获取到的${mediaType}提示词:`, promptText); // 调试用
} else {
console.log(`未找到${mediaType}提示词元素`); // 调试用
}
// 默认文件名使用提示词,如果没有提示词则使用日期
const defaultFileName = promptText || dateStr;
dialog.innerHTML = `
<h3>确认下载</h3>
<p>是否下载此${mediaType === 'video' ? '视频' : '图片'}?</p>
<div style="margin: 15px 0;">
<label for="fileName">文件名:</label>
<input type="text" id="fileName" value="${defaultFileName}" style="width: 80%; padding: 5px; margin-top: 5px;">
</div>
<div class="buttons">
<button class="confirm-btn">确认</button>
<button class="cancel-btn">取消</button>
</div>
`;
document.body.appendChild(overlay);
document.body.appendChild(dialog);
const confirmBtn = dialog.querySelector('.confirm-btn');
const cancelBtn = dialog.querySelector('.cancel-btn');
const fileNameInput = dialog.querySelector('#fileName');
function closeDialog() {
document.body.removeChild(overlay);
document.body.removeChild(dialog);
}
confirmBtn.addEventListener('click', () => {
confirmBtn.disabled = true;
confirmBtn.textContent = '下载中...';
const customFileName = fileNameInput.value.trim();
downloadFunction(mediaUrl, closeDialog, customFileName);
});
cancelBtn.addEventListener('click', closeDialog);
}
// 处理视频URL,移除水印
function processVideoUrl(url) {
try {
if (url.includes('vlabvod.com')) {
const urlObj = new URL(url);
const paramsToRemove = [
'lr', 'watermark', 'display_watermark_busi_user',
'cd', 'cs', 'ds', 'ft', 'btag', 'dy_q', 'feature_id'
];
paramsToRemove.forEach(param => {
urlObj.searchParams.delete(param);
});
if (urlObj.searchParams.has('br')) {
const br = parseInt(urlObj.searchParams.get('br'));
urlObj.searchParams.set('br', Math.max(br, 6000).toString());
urlObj.searchParams.set('bt', Math.max(br, 6000).toString());
}
urlObj.searchParams.delete('l');
return urlObj.toString();
}
return url;
} catch (e) {
console.error('处理视频URL时出错:', e);
return url;
}
}
// 获取真实视频URL
async function getRealVideoUrl(videoElement) {
let videoUrl = videoElement.src;
if (!videoUrl) {
const sourceElement = videoElement.querySelector('source');
if (sourceElement) {
videoUrl = sourceElement.src;
}
}
if (!videoUrl) {
videoUrl = videoElement.getAttribute('data-src');
}
return videoUrl;
}
// 获取文件扩展名
function getFileExtension(url) {
const extension = url.split('?')[0].match(/\.(jpg|jpeg|png|gif|mp4|webm)$/i);
return extension ? extension[0] : '.jpg';
}
// 下载图片的函数
function downloadImage(imageUrl, callback, customFileName) {
const fileExtension = getFileExtension(imageUrl);
const fileName = customFileName ? `${customFileName}${fileExtension}` : getFileNameFromUrl(imageUrl);
GM_xmlhttpRequest({
method: 'GET',
url: imageUrl,
responseType: 'blob',
headers: {
'Accept': 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': currentDomain.includes('doubao') ?
'https://www.doubao.com/' :
'https://jimeng.jianying.com/',
'Origin': currentDomain.includes('doubao') ?
'https://www.doubao.com' :
'https://jimeng.jianying.com',
'User-Agent': navigator.userAgent
},
onload: function(response) {
if (response.status === 200) {
const blob = response.response;
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => URL.revokeObjectURL(url), 100);
if (callback) callback();
} else {
console.error('下载失败:', response.status);
if (callback) callback();
}
},
onerror: function(error) {
console.error('请求失败:', error);
if (callback) callback();
}
});
}
// 下载视频的函数
function downloadVideo(videoUrl, callback, customFileName) {
const processedUrl = processVideoUrl(videoUrl);
const fileExtension = '.mp4';
const fileName = customFileName ? `${customFileName}${fileExtension}` : getFileNameFromUrl(processedUrl);
let progressDialog = document.querySelector('.download-confirm-dialog');
let progressBtn = progressDialog?.querySelector('.confirm-btn');
GM_xmlhttpRequest({
method: 'GET',
url: processedUrl,
responseType: 'blob',
headers: {
'Accept': 'video/mp4,video/*;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Range': 'bytes=0-',
'Referer': currentDomain.includes('doubao') ?
'https://www.doubao.com/' :
'https://jimeng.jianying.com/',
'Origin': currentDomain.includes('doubao') ?
'https://www.doubao.com' :
'https://jimeng.jianying.com',
'User-Agent': navigator.userAgent
},
onprogress: function(progress) {
if (progress.lengthComputable && progressBtn) {
const percent = Math.round((progress.loaded / progress.total) * 100);
progressBtn.textContent = `下载中 ${percent}%`;
}
},
onload: function(response) {
if (response.status === 200 || response.status === 206) {
const blob = response.response;
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => URL.revokeObjectURL(url), 100);
if (callback) callback();
} else {
console.error('下载失败:', response.status);
alert('下载失败,请检查控制台获取详细信息');
if (callback) callback();
}
},
onerror: function(error) {
console.error('请求失败:', error);
alert('下载失败,请检查控制台获取详细信息');
if (callback) callback();
}
});
}
// 从 URL 中提取文件名
function getFileNameFromUrl(url) {
url = url.split('?')[0];
const urlParts = url.split('/');
let fileName = urlParts[urlParts.length - 1];
if (fileName.includes('~')) {
fileName = fileName.split('~')[0];
}
if (!fileName.match(/\.(mp4|webm|jpg|jpeg|png)$/i)) {
fileName += url.includes('video') ? '.mp4' : '.jpeg';
}
return fileName;
}
// 监听右键点击事件
document.addEventListener('contextmenu', async function (event) {
const target = event.target;
if (target.tagName.toLowerCase() === 'img') {
const imageUrl = target.src;
if (imageUrl) {
createConfirmDialog(imageUrl, 'image', downloadImage);
event.preventDefault();
}
}
else if (target.tagName.toLowerCase() === 'video' ||
target.closest('video')) {
const videoElement = target.tagName.toLowerCase() === 'video' ?
target : target.closest('video');
if (videoElement) {
const videoUrl = await getRealVideoUrl(videoElement);
if (videoUrl) {
createConfirmDialog(videoUrl, 'video', downloadVideo);
event.preventDefault();
}
}
}
}, true);
})();