scriptCat/douyin/douyinVideoDownload.user.js
intelligrow 805d3037e6 feat: 增强人群画像批量下载功能,支持自定义文件名
在现有的人群画像批量下载功能中,新增了自定义文件名的选项,用户可以根据需求设置下载文件的名称。这一改进提升了用户的灵活性和体验,同时保持了原有功能的稳定性。
2025-06-23 16:41:24 +08:00

430 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==UserScript==
// @name 抖音视频下载助手
// @namespace https://github.com/scriptCat/
// @version 1.0.0
// @description 抖音视频下载助手监听视频详情API并提供下载功能
// @author wangxi
// @match https://www.douyin.com/*
// @grant GM_addStyle
// @grant GM_download
// @connect douyin.com
// @connect *
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 配置
const CONFIG = {
enabled: true
};
// 当前视频信息
let currentVideoInfo = null;
// 简化工具函数
function showToast(message, type = 'info') {
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 2000
});
Toast.fire({ icon: type, title: message });
}
// 下载视频
function downloadVideo() {
if (!currentVideoInfo) {
showToast('没有可下载的视频', 'error');
return;
}
const filename = `${currentVideoInfo.videoId}.mp4`;
console.log('[抖音下载] 下载链接:', currentVideoInfo.downloadUrl);
console.log('[抖音下载] 文件名:', filename);
updateFloatingButton('downloading');
// 使用fetch下载并创建blob链接
fetch(currentVideoInfo.downloadUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://www.douyin.com/',
'Accept': '*/*'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(blob => {
// 创建下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('下载成功', 'success');
updateFloatingButton('ready');
})
.catch(error => {
console.error('[抖音下载] 下载失败:', error);
showToast('下载失败', 'error');
updateFloatingButton('ready');
});
}
// 创建悬浮按钮
function createFloatingButton() {
// 检查是否已存在
if (document.querySelector('.douyin-download-float')) {
return;
}
const floatingButton = document.createElement('div');
floatingButton.className = 'douyin-download-float';
floatingButton.innerHTML = `
<div class="douyin-download-btn" title="下载视频">
<svg class="douyin-download-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
</svg>
<span class="douyin-download-text">下载</span>
</div>
`;
// 添加样式
GM_addStyle(`
.douyin-download-float {
position: fixed;
bottom: 100px;
right: 30px;
z-index: 9999;
user-select: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0.8;
}
.douyin-download-float:hover {
opacity: 1;
transform: scale(1.05);
}
.douyin-download-float.dragging {
transition: none !important;
opacity: 1;
transform: scale(1.05);
}
.douyin-download-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
color: white;
border-radius: 25px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
transition: all 0.2s ease;
min-width: 80px;
justify-content: center;
}
.douyin-download-btn:hover {
background: linear-gradient(135deg, #ff5252 0%, #ff7979 100%);
box-shadow: 0 6px 16px rgba(255, 107, 107, 0.4);
}
.douyin-download-btn:active {
transform: scale(0.95);
}
.douyin-download-btn.disabled {
background: linear-gradient(135deg, #d1d5db 0%, #9ca3af 100%);
cursor: not-allowed;
opacity: 0.6;
}
.douyin-download-btn.downloading {
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
}
.douyin-download-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.douyin-download-icon.spinning {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.douyin-download-text {
font-size: 13px;
font-weight: 600;
}
/* 吸附效果 */
.douyin-download-float.snap-right {
right: 0;
border-radius: 25px 0 0 25px;
}
.douyin-download-float.snap-left {
left: 0;
border-radius: 0 25px 25px 0;
}
`);
// 添加到页面
document.body.appendChild(floatingButton);
// 设置拖拽功能
setupDragAndSnap(floatingButton);
// 设置点击事件
const downloadBtn = floatingButton.querySelector('.douyin-download-btn');
downloadBtn.addEventListener('click', () => {
if (downloadBtn.classList.contains('disabled')) return;
// 点击反馈
downloadBtn.style.transform = 'scale(0.9)';
setTimeout(() => {
downloadBtn.style.transform = '';
}, 150);
downloadVideo();
});
}
// 设置拖拽和吸附功能
function setupDragAndSnap(floatingButton) {
let isDragging = false;
let startX, startY, startLeft, startTop;
floatingButton.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = floatingButton.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
floatingButton.classList.add('dragging');
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
let newLeft = startLeft + deltaX;
let newTop = startTop + deltaY;
// 限制在视窗内
const maxX = window.innerWidth - floatingButton.offsetWidth;
const maxY = window.innerHeight - floatingButton.offsetHeight;
newLeft = Math.max(0, Math.min(newLeft, maxX));
newTop = Math.max(0, Math.min(newTop, maxY));
// 设置位置
floatingButton.style.left = newLeft + 'px';
floatingButton.style.top = newTop + 'px';
floatingButton.style.right = 'auto';
floatingButton.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
floatingButton.classList.remove('dragging');
// 吸附效果
const rect = floatingButton.getBoundingClientRect();
const windowWidth = window.innerWidth;
const centerX = rect.left + rect.width / 2;
// 移除吸附类
floatingButton.classList.remove('snap-left', 'snap-right');
if (centerX < windowWidth / 2) {
// 吸附到左边
floatingButton.style.left = '0px';
floatingButton.style.right = 'auto';
floatingButton.classList.add('snap-left');
} else {
// 吸附到右边
floatingButton.style.right = '0px';
floatingButton.style.left = 'auto';
floatingButton.classList.add('snap-right');
}
});
}
// 更新悬浮按钮状态
function updateFloatingButton(status = 'ready') {
const floatingButton = document.querySelector('.douyin-download-float');
if (!floatingButton) return;
const downloadBtn = floatingButton.querySelector('.douyin-download-btn');
const icon = floatingButton.querySelector('.douyin-download-icon');
const text = floatingButton.querySelector('.douyin-download-text');
if (!downloadBtn || !icon || !text) return;
// 移除所有状态类
downloadBtn.classList.remove('disabled', 'downloading');
icon.classList.remove('spinning');
switch (status) {
case 'ready':
if (currentVideoInfo) {
downloadBtn.title = `下载视频: ${currentVideoInfo.title}`;
text.textContent = '下载';
icon.innerHTML = '<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>';
} else {
downloadBtn.classList.add('disabled');
downloadBtn.title = '等待视频加载...';
text.textContent = '等待';
icon.innerHTML = '<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>';
}
break;
case 'downloading':
downloadBtn.classList.add('downloading');
downloadBtn.title = '正在下载...';
text.textContent = '下载中';
icon.classList.add('spinning');
icon.innerHTML = '<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>';
break;
}
}
// 早期初始化
function earlyInit() {
// 简化的早期初始化
}
// 从页面提取视频信息
function extractVideoInfoFromPage() {
// 直接从HTML video/source标签提取
extractFromVideoTags();
}
// 从HTML video/source标签提取下载链接
function extractFromVideoTags() {
// 直接使用完整选择器获取第一个source
const firstSource = document.querySelector("xg-video-container video source:first-child");
if (firstSource) {
const src = firstSource.src || firstSource.getAttribute('src');
if (src) {
const videoId = extractVideoIdFromPage();
currentVideoInfo = {
videoId: videoId || 'unknown',
vid: 'vid',
downloadUrl: src,
title: '抖音视频'
};
updateFloatingButton();
}
}
}
// 从页面提取视频ID
function extractVideoIdFromPage() {
// 方法1从URL提取
const urlMatch = window.location.href.match(/\/video\/(\d+)/);
if (urlMatch) return urlMatch[1];
// 方法2从指定CSS选择器的链接提取
const linkElement = document.querySelector('#user_detail_element > div > div._1PChJilq > div > div > div._akC5KCF.W6aLyAOC > div > div.UTSD8keU.bVgnIvTC > ul > li:nth-child(4) > div > a');
if (linkElement && linkElement.href) {
const linkMatch = linkElement.href.match(/\/video\/(\d+)/);
if (linkMatch) return linkMatch[1];
}
return null;
}
// 界面初始化
function uiInit() {
// 创建悬浮按钮
createFloatingButton();
// 提取视频信息(带重试机制)
function tryExtractVideo(retries = 5) {
extractVideoInfoFromPage();
if (!currentVideoInfo && retries > 0) {
setTimeout(() => tryExtractVideo(retries - 1), 1000);
}
}
tryExtractVideo();
// 监听页面变化
let currentUrl = location.href;
new MutationObserver(() => {
// 检查URL变化
if (location.href !== currentUrl) {
currentUrl = location.href;
currentVideoInfo = null;
updateFloatingButton();
setTimeout(() => tryExtractVideo(3), 500);
}
// 检查新video标签
if (!currentVideoInfo) {
setTimeout(extractFromVideoTags, 500);
}
}).observe(document, {
subtree: true,
childList: true
});
}
// 立即执行早期初始化
earlyInit();
// 等待页面准备好后初始化界面
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(uiInit, 500);
});
} else {
setTimeout(uiInit, 500);
}
})();