// ==UserScript==
// @name 豆包&即梦AI下载无水印图片视频
// @namespace http://tampermonkey.net/
// @version 3.0
// @description 实现豆包&即梦生成的图片视频免费无水印下载
// @author 微信11208596
// @license UNLICENSED
// @match https://www.doubao.com/*
// @match https://jimeng.jianying.com/ai-tool/*
// @grant GM_download
// @grant GM_xmlhttpRequest
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
// 添加激活码相关功能
const ACTIVATION_KEY = 'doubao_activation_status';
const SECRET_KEY = 'db94xy20240322'; // 使用一个固定的密钥值
const VALID_DAYS = 30; // 激活码有效期(天)
// 生成激活码
function generateActivationCode(deviceId) {
// 添加时间戳
const timestamp = Math.floor(Date.now() / 1000);
const expireTime = timestamp + (VALID_DAYS * 24 * 60 * 60);
// 混合设备ID和时间戳
const data = `${deviceId}|${expireTime}`;
// 使用更复杂的加密算法
const encrypted = encryptData(data);
// 添加校验位
const checksum = generateChecksum(encrypted);
// 组合最终激活码
return `${encrypted}-${checksum}-${expireTime.toString(36)}`;
}
// 验证激活码
function verifyActivationCode(deviceId, inputCode) {
try {
// 检查激活码是否在黑名单中
const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
if (blacklist.includes(inputCode)) {
console.log('激活码已被取消');
return false;
}
console.log('开始验证:', {deviceId, inputCode});
const [encrypted, checksum, expireTimeStr] = inputCode.split('-');
console.log('解析激活码:', {encrypted, checksum, expireTimeStr});
const validChecksum = generateChecksum(encrypted);
console.log('校验和:', {valid: validChecksum, input: checksum});
if (generateChecksum(encrypted) !== checksum) {
console.log('校验和不匹配');
return false;
}
const expireTime = parseInt(expireTimeStr, 36);
const now = Math.floor(Date.now() / 1000);
console.log('时间验证:', {expireTime, now, valid: now <= expireTime});
if (now > expireTime) {
console.log('激活码已过期');
return false;
}
const decrypted = decryptData(encrypted);
console.log('解密结果:', decrypted);
const [originalDeviceId, originalExpireTime] = decrypted.split('|');
console.log('解析数据:', {originalDeviceId, originalExpireTime});
const isValid = originalDeviceId === deviceId;
console.log('设备ID匹配结果:', isValid);
if (isValid) {
// 保存激活码以便后续验证过期时间
localStorage.setItem('activation_code', inputCode);
}
return isValid;
} catch (e) {
console.error('验证过程出错:', e);
return false;
}
}
// 加密函数
function encryptData(data) {
let result = '';
const key = SECRET_KEY;
// 使用简单的异或加密
for (let i = 0; i < data.length; i++) {
const charCode = data.charCodeAt(i);
const keyChar = key.charCodeAt(i % key.length);
const encrypted = (charCode ^ keyChar) % 256;
result += encrypted.toString(16).padStart(2, '0');
}
return result;
}
// 解密函数
function decryptData(encrypted) {
let result = '';
const key = SECRET_KEY;
// 解密过程
for (let i = 0; i < encrypted.length; i += 2) {
const hex = encrypted.substr(i, 2);
const encryptedChar = parseInt(hex, 16);
const keyChar = key.charCodeAt((i/2) % key.length);
const decrypted = (encryptedChar ^ keyChar) % 256;
result += String.fromCharCode(decrypted);
}
return result;
}
// 生成校验位
function generateChecksum(data) {
let checksum = 0;
for (let i = 0; i < data.length; i++) {
checksum = ((checksum << 5) - checksum + data.charCodeAt(i)) >>> 0;
}
return checksum.toString(36);
}
// 检查激活状态
function checkActivation() {
const activationStatus = localStorage.getItem(ACTIVATION_KEY);
const deviceId = localStorage.getItem('deviceId');
const activationCode = localStorage.getItem('activation_code');
if (!deviceId) {
const newDeviceId = Math.random().toString(36).substring(2) + Date.now().toString(36);
localStorage.setItem('deviceId', newDeviceId);
return false;
}
// 检查激活码是否在黑名单中
if (activationCode) {
const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
if (blacklist.includes(activationCode)) {
// 如果在黑名单中,立即清除激活状态
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('激活码已被禁用,请重新激活');
return false;
}
}
return activationStatus === 'activated' && deviceId && activationCode;
}
// 创建激活对话框
function createActivationDialog() {
const overlay = document.createElement('div');
overlay.className = 'download-confirm-overlay';
const dialog = document.createElement('div');
dialog.className = 'download-confirm-dialog';
const deviceId = localStorage.getItem('deviceId');
// 检查是否已激活
const isActivated = checkActivation();
dialog.innerHTML = `
软件激活
${isActivated ? `
软件已激活
` : `
请输入激活码以继续使用
`}
`;
document.body.appendChild(overlay);
document.body.appendChild(dialog);
const confirmBtn = dialog.querySelector('.confirm-btn');
const cancelBtn = dialog.querySelector('.cancel-btn');
const activationInput = dialog.querySelector('#activationCode');
const deviceIdInput = dialog.querySelector('#deviceId');
// 复制设备ID功能
deviceIdInput.addEventListener('click', () => {
deviceIdInput.select();
document.execCommand('copy');
showFloatingTip('设备ID已复制到剪贴板');
});
function closeDialog() {
document.body.removeChild(overlay);
document.body.removeChild(dialog);
}
if (isActivated) {
const deactivateBtn = dialog.querySelector('.deactivate-btn');
deactivateBtn.addEventListener('click', () => {
if (confirm('确定要取消激活吗?取消后需要重新激活才能继续使用。')) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('已取消激活');
closeDialog();
}
});
} else {
confirmBtn.addEventListener('click', () => {
const code = activationInput.value.trim();
if (!code) {
showFloatingTip('请输入激活码');
return;
}
confirmBtn.disabled = true;
confirmBtn.textContent = '验证中...';
// 验证激活码
if (verifyActivationCode(deviceId, code)) {
localStorage.setItem(ACTIVATION_KEY, 'activated');
showFloatingTip('激活成功');
closeDialog();
} else {
showFloatingTip('激活码无效');
confirmBtn.disabled = false;
confirmBtn.textContent = '激活';
}
});
}
cancelBtn.addEventListener('click', closeDialog);
}
// 添加一个激活码生成工具函数(仅供开发使用)
function generateActivationCodeForDevice(deviceId) {
return generateActivationCode(deviceId);
}
// 显示浮动提示
function showFloatingTip(message) {
const tip = document.createElement('div');
tip.className = 'floating-tip';
tip.innerHTML = `
i
${message}
`;
document.body.appendChild(tip);
setTimeout(() => {
tip.classList.add('show');
}, 100);
setTimeout(() => {
tip.classList.remove('show');
setTimeout(() => {
document.body.removeChild(tip);
}, 300);
}, 3000);
}
// 将函数暴露到全局作用域
window.deactivateCode = function(code) {
if (!confirm('确定要取消此激活码吗?取消后此激活码将无法继续使用。')) {
return;
}
try {
// 将激活码添加到黑名单
let blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
if (!blacklist.includes(code)) {
blacklist.push(code);
localStorage.setItem('activation_blacklist', JSON.stringify(blacklist));
// 如果当前用户正在使用这个激活码,立即取消激活
const currentCode = localStorage.getItem('activation_code');
if (currentCode === code) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
}
// 广播黑名单更新事件
window.dispatchEvent(new CustomEvent('blacklistUpdated', { detail: { code } }));
}
// 更新查询结果显示
const resultDiv = document.getElementById('queryResult');
resultDiv.innerHTML = `
`;
showFloatingTip('激活码已成功取消');
// 强制刷新页面以确保状态更新
setTimeout(() => {
window.location.reload();
}, 1500);
} catch (e) {
console.error('取消激活码失败:', e);
showFloatingTip('取消激活码失败');
}
};
// 添加黑名单更新监听器
window.addEventListener('blacklistUpdated', function(event) {
// 检查当前激活码是否被取消
const currentCode = localStorage.getItem('activation_code');
if (currentCode === event.detail.code) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('激活码已被禁用,请重新激活');
window.location.reload();
}
});
// 添加定期检查机制
function startBlacklistCheck() {
setInterval(() => {
if (checkActivation() === false) {
// 如果检查失败,可能需要刷新页面
window.location.reload();
}
}, 30000); // 每30秒检查一次
}
// 在脚本初始化时启动检查
startBlacklistCheck();
window.queryActivationCode = function() {
const code = document.getElementById('queryCode').value.trim();
const resultDiv = document.getElementById('queryResult');
if (!code) {
showFloatingTip('请输入激活码');
return;
}
try {
// 首先检查黑名单
const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
if (blacklist.includes(code)) {
resultDiv.innerHTML = `
`;
resultDiv.style.display = 'block';
return;
}
const [encrypted, checksum, expireTimeStr] = code.split('-');
if (generateChecksum(encrypted) !== checksum) {
resultDiv.innerHTML = '无效的激活码
';
resultDiv.style.display = 'block';
return;
}
const decrypted = decryptData(encrypted);
const [deviceId, expireTime] = decrypted.split('|');
const now = Math.floor(Date.now() / 1000);
const expireTimeNum = parseInt(expireTimeStr, 36);
const isExpired = now > expireTimeNum;
resultDiv.innerHTML = `
设备ID: ${deviceId}
到期时间: ${new Date(expireTimeNum * 1000).toLocaleString()}
状态: ${isExpired ? '已过期' : '有效'}
${!isExpired ? `
` : ''}
`;
resultDiv.style.display = 'block';
} catch (e) {
resultDiv.innerHTML = '无效的激活码格式
';
resultDiv.style.display = 'block';
}
};
// 修改下载验证函数,添加强制刷新机制
function downloadWithActivationCheck(mediaUrl, mediaType, downloadFunction) {
// 首先检查是否有激活状态
const isActivated = checkActivation();
const activationCode = localStorage.getItem('activation_code');
const deviceId = localStorage.getItem('deviceId');
if (!isActivated || !activationCode || !deviceId) {
createActivationDialog();
return;
}
// 验证激活码
try {
// 1. 检查黑名单
const blacklist = JSON.parse(localStorage.getItem('activation_blacklist') || '[]');
if (blacklist.includes(activationCode)) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('激活码已被禁用,请重新激活');
// 强制刷新页面
setTimeout(() => {
window.location.reload();
}, 1500);
return;
}
// 2. 验证激活码格式和校验和
const [encrypted, checksum, expireTimeStr] = activationCode.split('-');
if (!encrypted || !checksum || !expireTimeStr || generateChecksum(encrypted) !== checksum) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('激活码无效,请重新激活');
createActivationDialog();
return;
}
// 3. 验证过期时间
const expireTime = parseInt(expireTimeStr, 36);
const now = Math.floor(Date.now() / 1000);
if (now > expireTime) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('激活码已过期,请重新激活');
createActivationDialog();
return;
}
// 4. 验证设备ID匹配
const decrypted = decryptData(encrypted);
const [originalDeviceId] = decrypted.split('|');
if (originalDeviceId !== deviceId) {
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('设备ID不匹配,请重新激活');
createActivationDialog();
return;
}
// 所有验证都通过,允许下载
createConfirmDialog(mediaUrl, mediaType, downloadFunction);
} catch (e) {
console.error('验证激活码出错:', e);
localStorage.removeItem(ACTIVATION_KEY);
localStorage.removeItem('activation_code');
showFloatingTip('激活码验证失败,请重新激活');
createActivationDialog();
}
}
// 修改确认对话框样式
const style = document.createElement('style');
style.textContent = `
@keyframes dialogShow {
from {
opacity: 0;
transform: translate(-50%, -48%) scale(0.96);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
@keyframes overlayShow {
from { opacity: 0; }
to { opacity: 1; }
}
.download-confirm-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
padding: 28px 24px;
border-radius: 14px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
0 0 0 1px rgba(0, 0, 0, 0.05);
z-index: 10000;
min-width: 320px;
max-width: 400px;
text-align: center;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
animation: dialogShow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.download-confirm-dialog h3 {
margin: 0 0 8px 0;
color: #1d1d1f;
font-size: 19px;
font-weight: 600;
letter-spacing: -0.022em;
}
.download-confirm-dialog p {
margin: 0 0 20px 0;
color: #86868b;
font-size: 14px;
line-height: 1.4;
letter-spacing: -0.016em;
}
.download-confirm-dialog .input-container {
margin: 20px 0;
text-align: left;
}
.download-confirm-dialog label {
display: block;
margin-bottom: 8px;
color: #1d1d1f;
font-size: 13px;
font-weight: 500;
letter-spacing: -0.016em;
}
.download-confirm-dialog input {
width: 100%;
padding: 12px 16px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 10px;
font-size: 15px;
color: #1d1d1f;
background: rgba(255, 255, 255, 0.8);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
box-sizing: border-box;
}
.download-confirm-dialog input:focus {
outline: none;
border-color: #0071e3;
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.15);
background: #ffffff;
}
.download-confirm-dialog .buttons {
margin-top: 28px;
display: flex;
gap: 12px;
justify-content: center;
}
.download-confirm-dialog button {
min-width: 128px;
padding: 12px 24px;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
letter-spacing: -0.016em;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.download-confirm-dialog .confirm-btn {
background: #0071e3;
color: white;
transform: scale(1);
}
.download-confirm-dialog .confirm-btn:hover {
background: #0077ED;
transform: scale(1.02);
}
.download-confirm-dialog .confirm-btn:active {
transform: scale(0.98);
}
.download-confirm-dialog .confirm-btn:disabled {
background: #999999;
cursor: not-allowed;
opacity: 0.7;
transform: scale(1);
}
.download-confirm-dialog .cancel-btn {
background: rgba(0, 0, 0, 0.05);
color: #1d1d1f;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.download-confirm-dialog .cancel-btn:hover {
background: rgba(0, 0, 0, 0.1);
}
.download-confirm-dialog .cancel-btn:active {
background: rgba(0, 0, 0, 0.15);
}
.download-confirm-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
z-index: 9999;
animation: overlayShow 0.3s ease-out;
}
@media (prefers-color-scheme: dark) {
.download-confirm-dialog {
background: rgba(40, 40, 45, 0.8);
}
.download-confirm-dialog h3 {
color: #ffffff;
}
.download-confirm-dialog p {
color: #98989d;
}
.download-confirm-dialog label {
color: #ffffff;
}
.download-confirm-dialog input {
background: rgba(60, 60, 65, 0.8);
border-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.download-confirm-dialog input:focus {
background: rgba(70, 70, 75, 0.8);
}
.download-confirm-dialog .cancel-btn {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
}
.download-confirm-dialog .cancel-btn:hover {
background: rgba(255, 255, 255, 0.15);
}
}
.download-confirm-dialog .tip {
font-size: 12px;
color: #86868b;
margin-top: 6px;
text-align: left;
}
.download-confirm-dialog .progress-text {
margin-top: 12px;
font-size: 13px;
color: #1d1d1f;
letter-spacing: -0.016em;
}
.download-confirm-dialog .success-icon {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
background: #00c853;
position: relative;
margin-right: 6px;
transform: scale(0);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.download-confirm-dialog .success-icon:after {
content: '';
position: absolute;
width: 8px;
height: 4px;
border: 2px solid white;
border-top: 0;
border-right: 0;
transform: rotate(-45deg);
top: 4px;
left: 4px;
}
.download-confirm-dialog .success-icon.show {
transform: scale(1);
}
@media (prefers-color-scheme: dark) {
.download-confirm-dialog .tip {
color: #98989d;
}
.download-confirm-dialog .progress-text {
color: #ffffff;
}
}
.floating-tip {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 12px 20px;
border-radius: 10px;
color: white;
font-size: 14px;
z-index: 9999;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
display: flex;
align-items: center;
gap: 8px;
pointer-events: none;
}
.floating-tip.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.floating-tip .icon {
width: 18px;
height: 18px;
background: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 12px;
color: #000;
}
@media (prefers-color-scheme: dark) {
.floating-tip {
background: rgba(255, 255, 255, 0.9);
color: #1d1d1f;
}
.floating-tip .icon {
background: #1d1d1f;
color: #fff;
}
}
.usage-tip {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(-100px);
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 16px 24px;
border-radius: 12px;
color: white;
font-size: 15px;
line-height: 1.4;
z-index: 9999;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
max-width: 90%;
width: auto;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.usage-tip.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.usage-tip .icon {
font-size: 24px;
flex-shrink: 0;
}
.usage-tip .content {
display: flex;
flex-direction: column;
gap: 4px;
}
.usage-tip .main-text {
font-weight: 500;
}
.usage-tip .contact {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
}
@media (prefers-color-scheme: dark) {
.usage-tip {
background: rgba(255, 255, 255, 0.95);
color: #1d1d1f;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.usage-tip .contact {
color: rgba(0, 0, 0, 0.6);
}
}
.remaining-time {
background: rgba(0, 0, 0, 0.03);
padding: 8px 12px;
border-radius: 8px;
text-align: center;
}
@media (prefers-color-scheme: dark) {
.remaining-time {
background: rgba(255, 255, 255, 0.05);
color: #98989d;
}
}
`;
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 activationCode = localStorage.getItem('activation_code');
let remainingTimeText = '';
if (activationCode) {
try {
const [, , expireTimeStr] = activationCode.split('-');
const expireTime = parseInt(expireTimeStr, 36);
const now = Math.floor(Date.now() / 1000);
const remainingSeconds = expireTime - now;
if (remainingSeconds > 0) {
const days = Math.floor(remainingSeconds / (24 * 60 * 60));
const hours = Math.floor((remainingSeconds % (24 * 60 * 60)) / (60 * 60));
const minutes = Math.floor((remainingSeconds % (60 * 60)) / 60);
remainingTimeText = `
激活码剩余时间:${days}天${hours}小时${minutes}分钟
`;
}
} catch (e) {
console.error('解析过期时间出错:', e);
}
}
// 获取当前日期时间作为默认文件名的备选
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 = `
下载${mediaType === 'video' ? '视频' : '图片'}
${remainingTimeText}
请确认下载信息
`;
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');
const progressText = dialog.querySelector('.progress-text');
const statusText = dialog.querySelector('.status-text');
const successIcon = dialog.querySelector('.success-icon');
function closeDialog() {
document.body.removeChild(overlay);
document.body.removeChild(dialog);
}
function handleDownloadProgress(percent) {
if (percent) {
progressText.style.display = 'block';
statusText.textContent = `正在下载...${percent}%`;
}
}
function handleDownloadSuccess() {
confirmBtn.style.display = 'none';
progressText.style.display = 'block';
successIcon.classList.add('show');
statusText.textContent = '下载完成';
setTimeout(() => {
closeDialog();
}, 1500);
}
function handleDownloadError(error) {
progressText.style.display = 'block';
statusText.textContent = `下载失败: ${error}`;
statusText.style.color = '#ff3b30';
confirmBtn.disabled = false;
confirmBtn.textContent = '重试';
}
confirmBtn.addEventListener('click', () => {
confirmBtn.disabled = true;
confirmBtn.textContent = '准备下载...';
const customFileName = fileNameInput.value.trim();
downloadFunction(
mediaUrl,
handleDownloadSuccess,
customFileName,
handleDownloadProgress,
handleDownloadError
);
});
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, onProgress, onError) {
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
},
onprogress: function(progress) {
if (progress.lengthComputable) {
const percent = Math.round((progress.loaded / progress.total) * 100);
if (onProgress) onProgress(percent);
}
},
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 {
if (onError) onError(`HTTP ${response.status}`);
}
},
onerror: function(error) {
if (onError) onError(error.message || '网络错误');
}
});
}
// 下载视频的函数
function downloadVideo(videoUrl, callback, customFileName, onProgress, onError) {
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) {
downloadWithActivationCheck(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) {
downloadWithActivationCheck(videoUrl, 'video', downloadVideo);
event.preventDefault();
}
}
}
}, true);
// 修改显示提示的函数
function showUsageTip() {
// 移除检查localStorage的逻辑,每次都显示提示
const tip = document.createElement('div');
tip.className = 'usage-tip';
tip.innerHTML = `
💡
点击图片或视频,单击鼠标右键即可免费下载无水印的图片或视频
有问题联系微信:11208596
`;
document.body.appendChild(tip);
// 显示提示
setTimeout(() => {
tip.classList.add('show');
}, 500);
// 10秒后自动隐藏提示
setTimeout(() => {
tip.classList.remove('show');
setTimeout(() => {
document.body.removeChild(tip);
}, 600);
}, 10000);
// 点击可以提前关闭提示
tip.addEventListener('click', () => {
tip.classList.remove('show');
setTimeout(() => {
document.body.removeChild(tip);
}, 600);
});
}
// 修改页面加载时的提示逻辑
function initUsageTip() {
if (window.location.hostname.includes('doubao.com') ||
window.location.hostname.includes('jimeng.jianying.com')) {
// 页面加载完成后显示提示
if (document.readyState === 'complete') {
showUsageTip();
} else {
window.addEventListener('load', showUsageTip);
}
// 监听页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
showUsageTip();
}
});
// 监听页面焦点变化
window.addEventListener('focus', showUsageTip);
}
}
// 初始化提示
initUsageTip();
})();