// ==UserScript==
// @name 抖音视频下载工具
// @namespace https://scriptcat.org/zh-CN/users/176579
// @version 1.0.1
// @description 抖音视频下载工具,用于下载抖音视频。一定要看教程,一定要看教程,一定要看教程。
// @author xyz-xyz
// @match https://www.douyin.com/*
// @connect v3-web.douyinvod.com
// @grant GM_xmlhttpRequest
// @grant GM_download
// @icon https://lf-douyin-pc-web.douyinstatic.com/obj/douyin-pc-web/2025_0313_logo.png
// ==/UserScript==
(function () {
'use strict';
// Your code here...
// 全局变量
var pageUrl = window.location.href;
// 总流程
(async () => {
if (window.self == window.top) {
createUi();
} else {
if (pageUrl.includes('/video/7558459629781421351')) {
await delay(3000);
let semiButtonNode = document.querySelector('#douyin-right-container > div.parent-route-container.route-scroll-container.IhmVuo1S > div > div > div.detailPage.W_7gCbBd > div > div.cHwSTMd3 > div.V_bVSPdS > button.semi-button-primary');
if (semiButtonNode) {
semiButtonNode.click();
// log('已关注');
}
} else if (pageUrl.includes('/video/')) {
await delay(3000);
let videoNode = document.querySelector('video');
if (videoNode) {
let sourceNode = videoNode.querySelector('source')
// log(sourceNode.src);
let showVideo = window.top.document.getElementById('showVideo');
if (showVideo) {
showVideo.src = sourceNode.src;
log('视频链接获取成功');
showMessage('获取成功', 'green');
log('请点击下载');
} else {
log('视频链接获取失败');
}
} else {
log('视频链接获取失败');
}
}
}
})()
async function createUi() {
let templateDiv = document.createElement('div');
templateDiv.id = 'templateDiv';
templateDiv.style.cssText = `
padding: 15px;
position: fixed;
z-index: 999;
background-color: #f1f1f1;
border: 1px solid #d3d3d3;
width: 300px;
right: 50px;
top: 50px;
`;
templateDiv.innerHTML = `
使用帮助:详见脚本发布页
扫描二维码,关注公众号学长喵
`;
// 插入页面内
document.body.appendChild(templateDiv);
await delay(1000);
// 放大缩小
// 获取开关和目标文字区域
let bigtosmall = document.getElementById("bigtosmall");
let templateDivBody = document.getElementById("templateDivBody");
// 监听开关状态变化
bigtosmall.addEventListener('change', function () {
if (this.checked) {
templateDivBody.style.display = "none";
} else {
templateDivBody.style.display = "";
}
});
// 使 DIV 元素可拖动:
templateDiv = document.getElementById("templateDiv");
let templateDivHeader = document.getElementById("templateDivHeader");
// templateDiv.style.right = '50px';
// templateDiv.style.top = '50px';
dragElement(templateDiv, templateDivHeader);
function dragElement(elmnt, elmnt0) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "Header")) {
// 如果存在,标题就是您移动 DIV 的位置:
document.getElementById(elmnt.id + "Header").onmousedown = dragMouseDown;
} else {
// 否则,从 DIV 内的任何位置移动 DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// 获取启动时鼠标光标的位置:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// 每当光标移动时调用函数:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// 获取屏幕可视区域尺寸(不含滚动条)
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
// 获取窗口自身尺寸
const windowWidth = elmnt.offsetWidth;
const windowHeight = elmnt0.offsetHeight;
// 计算新的光标位置,和偏移量:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// 计算窗口新位置(基于鼠标位置 - 偏移量)
let newX = elmnt.offsetLeft - pos1;
let newY = elmnt.offsetTop - pos2;
// 边界限制:确保窗口不超出屏幕
// 左边界:不能小于0(否则左边会移出屏幕)
newX = Math.max(0, newX);
// 右边界:不能大于屏幕宽度 - 窗口宽度(否则右边会移出屏幕)
newX = Math.min(newX, screenWidth - windowWidth);
// 上边界:不能小于0(否则上边会移出屏幕)
newY = Math.max(0, newY);
// 下边界:不能大于屏幕高度 - 窗口高度(否则下边会移出屏幕)
newY = Math.min(newY, screenHeight - windowHeight - 25);
// 设置元素的新位置:
// elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
// elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
elmnt.style.top = newY + "px";
elmnt.style.left = newX + "px";
}
function closeDragElement() {
// 释放鼠标按钮时停止移动:
document.onmouseup = null;
document.onmousemove = null;
}
}
let base_urlInput = document.getElementById('baseurlInput');
if (!base_urlInput) return;
let saveBtn = document.getElementById('saveBtn');
if (saveBtn) saveBtn.addEventListener('click', downloadVideo);
let verifyBtn = document.getElementById('verifyBtn');
if (verifyBtn) verifyBtn.addEventListener('click', getVideo);
let userIframe = document.createElement('iframe');
userIframe.style.display = '';
userIframe.src = `https://www.douyin.com/video/7558459629781421351`;
document.body.appendChild(userIframe);
// 获取视频链接
function getVideo() {
let iframe = document.createElement('iframe');
iframe.style.display = '';
let videoString = base_urlInput.value;
let regex = /https:\/\/v\.douyin\.com\/[^\s]+/;
// 执行匹配并提取链接
let matchResult = videoString.match(regex);
// log(matchResult);
iframe.src = `${matchResult}`;
document.body.appendChild(iframe);
showMessage('获取视频', 'blue');
log('正在获取视频链接......');
}
// 下载视频
function downloadVideo() {
let showVideo = window.top.document.getElementById('showVideo');
let videoUrl = showVideo.src;
if (!videoUrl || !videoUrl.startsWith('http')) {
log('视频地址无效,请检查!');
return;
}
downloadVideoAsMP4(videoUrl);
}
}
// 版本检测
// function versionDetection() {
// // 比较版本
// function compareScriptVersion(remoteScriptUrl, callback) {
// // 1. 获取当前脚本版本
// let currentVersion = GM_info.script.version || '未知版本';
// // 2. 远程请求
// GM_xmlhttpRequest({
// method: 'GET',
// url: remoteScriptUrl,
// headers: {
// // 显式声明不携带Cookie
// 'Cookie': '',
// 'Cache-Control': 'no-cache' // 避免缓存影响
// },
// anonymous: true, // 部分环境下此参数可禁用用户信息传递
// onload: function (response) {
// if (response.status === 200) {
// // 解析远程版本号
// let remoteMatch = response.responseText.match(/@version\s+(\S+)/i);
// let remoteVersion = remoteMatch ? remoteMatch[1].trim() : '未知版本';
// // 执行回调返回结果
// callback({
// isSame: currentVersion === remoteVersion,
// current: currentVersion,
// remote: remoteVersion
// });
// } else {
// callback({
// isSame: null,
// current: currentVersion,
// remote: '获取失败',
// error: `HTTP状态码:${response.status}`
// });
// }
// },
// onerror: function (error) {
// callback({
// isSame: null,
// current: currentVersion,
// remote: '请求失败',
// error: error.message || '网络错误'
// });
// }
// });
// }
// let targetUrl = 'https://scriptcat.org/scripts/code/4485/%E8%B6%85%E6%98%9F%E5%AD%A6%E4%B9%A0%E9%80%9A%E5%B0%8F%E5%8A%A9%E6%89%8B.user.js';
// compareScriptVersion(targetUrl, (result) => {
// if (result.error) {
// console.error('版本对比出错:', result.error);
// console.log(`版本对比失败:${result.error}`);
// return;
// }
// console.log(`当前版本:${result.current},远程版本:${result.remote}`);
// if (result.isSame) {
// console.log(`版本一致\n当前:${result.current}\n远程:${result.remote}`);
// } else {
// console.log(`版本不一致\n当前:${result.current}\n远程:${result.remote}`);
// }
// });
// }
// UI辅助工具函数
// 显示消息提示
function showMessage(text, color) {
let messageEl = window.top.document.getElementById('message');
if (messageEl) {
messageEl.textContent = text;
messageEl.style.color = color;
setTimeout(() => {
messageEl.textContent = '';
}, 3000);
}
}
// 日志函数:添加日志到日志区域
function log(message) {
let logArea = window.top.document.getElementById('logArea');
if (!logArea) return;
// 获取当前时间并格式化
let now = new Date();
let timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
// 创建日志条目
let logEntry = document.createElement('div');
logEntry.style.cssText = 'margin: 3px 0; border-bottom: 1px solid #f0f0f0; padding-bottom: 2px;';
logEntry.innerHTML = `[${timeString}] ${message}`;
logArea.appendChild(logEntry);
// 自动滚动到最底部
logArea.scrollTop = logArea.scrollHeight;
}
// 辅助视频下载
/**
* 核心函数:通过GM_xmlhttpRequest下载视频并保存为MP4
* @param {string} videoUrl - 视频源URL
* @param {string} fileName - 保存的文件名(默认带时间戳)
*/
function downloadVideoAsMP4(videoUrl, fileName = `video_${Date.now()}.mp4`) {
// 校验URL合法性
if (!videoUrl || !videoUrl.startsWith('http')) {
alert('视频URL不合法,请检查!');
return;
}
// log(`开始请求视频:${videoUrl}`);
log(`开始请求视频,请等待片刻.....`);
// 油猴专属请求API
GM_xmlhttpRequest({
method: 'GET',
url: videoUrl,
responseType: 'blob', // 关键:指定响应为二进制Blob格式
headers: {
// 模拟浏览器请求头,绕过防盗链
'User-Agent': navigator.userAgent,
'Referer': window.location.href,
'Accept': '*/*',
'Range': 'bytes=0-' // 请求完整视频文件
},
onload: function (response) {
// 检查请求是否成功
if (response.status < 200 || response.status >= 300) {
alert(`请求失败:${response.status} ${response.statusText}`);
return;
}
// 获取二进制Blob数据
const videoBlob = response.response;
if (!videoBlob) {
alert('未获取到视频二进制数据!');
return;
}
// 方式1:使用油猴GM_download(推荐,稳定无弹窗)
if (typeof GM_download !== 'undefined') {
const blobUrl = URL.createObjectURL(videoBlob);
GM_download({
url: blobUrl,
name: fileName,
saveAs: false, // 自动保存,不弹"另存为"对话框
onerror: (error) => {
console.error('GM_download失败:', error);
fallbackDownload(blobUrl, fileName); // 降级到原生下载
},
onload: () => {
log(`视频已保存:${fileName}`);
URL.revokeObjectURL(blobUrl); // 清理临时URL
}
});
}
// 方式2:原生下载(兼容无GM_download的环境)
else {
const blobUrl = URL.createObjectURL(videoBlob);
fallbackDownload(blobUrl, fileName);
}
},
onerror: function (error) {
alert(`请求出错:${error.message}`);
console.error('GM_xmlhttpRequest错误:', error);
},
ontimeout: function () {
alert('请求超时,请检查网络或重试!');
},
timeout: 30000 // 超时时间:30秒
});
}
/**
* 降级下载函数:原生a标签触发下载
* @param {string} blobUrl - Blob临时URL
* @param {string} fileName - 保存文件名
*/
function fallbackDownload(blobUrl, fileName) {
const a = document.createElement('a');
a.href = blobUrl;
a.download = fileName;
a.style.display = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl); // 清理临时URL
log(`原生下载触发:${fileName}`);
showMessage('下载成功', 'green');
}
// 其他工具函数
// 工具函数:延迟指定毫秒数
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
})();