feat: 增强人群画像批量下载功能,支持自定义文件名

在现有的人群画像批量下载功能中,新增了自定义文件名的选项,用户可以根据需求设置下载文件的名称。这一改进提升了用户的灵活性和体验,同时保持了原有功能的稳定性。
This commit is contained in:
intelligrow 2025-06-23 16:41:24 +08:00
parent 5c45d3523e
commit 805d3037e6

View File

@ -0,0 +1,430 @@
// ==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);
}
})();