你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
// ==UserScript==
// @name FSDVD - 飞书文档视频下载
// @license GPL License
// @namespace https://bytedance.com
// @version 0.1
// @description 飞书文档视频下载工具
// @author 906051999
// @match *://*.feishu.cn/*
// @match *://*.larkoffice.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// 获取所有视频元素
function getAllVideoElements() {
const videoBlocks = document.querySelectorAll('div[data-block-type="view"]');
const videos = [];
videoBlocks.forEach(block => {
const fileNameElement = block.querySelector('.file-name');
if (fileNameElement) {
videos.push({
element: block,
name: fileNameElement.textContent.trim(),
});
}
});
return videos;
}
// 创建视频列表弹窗
// 在createVideoListPopup函数中添加复选框和批量操作栏
function createVideoListPopup(videos) {
const popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.width = '500px';
popup.style.maxHeight = '80vh';
popup.style.backgroundColor = 'white';
popup.style.borderRadius = '8px';
popup.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
popup.style.padding = '20px';
popup.style.zIndex = '9999';
popup.style.overflow = 'auto';
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.position = 'absolute';
closeBtn.style.right = '15px';
closeBtn.style.top = '15px';
closeBtn.style.border = 'none';
closeBtn.style.background = 'none';
closeBtn.style.fontSize = '20px';
closeBtn.style.cursor = 'pointer';
closeBtn.addEventListener('click', () => document.body.removeChild(popup));
popup.appendChild(closeBtn);
const title = document.createElement('h3');
title.textContent = `文档中的视频文件 (共 ${videos.length} 个)`; // 添加总数量
title.style.marginBottom = '20px';
popup.appendChild(title);
// 添加批量操作工具栏
const batchToolbar = document.createElement('div');
batchToolbar.style.display = 'flex';
batchToolbar.style.justifyContent = 'space-between';
batchToolbar.style.marginBottom = '15px';
batchToolbar.style.alignItems = 'center';
const selectedCount = document.createElement('span');
selectedCount.textContent = '已选 0 个视频';
selectedCount.id = 'selectedCount';
const btnGroup = document.createElement('div');
// 批量操作按钮
const selectAllBtn = document.createElement('button');
selectAllBtn.textContent = '全选';
selectAllBtn.style.marginRight = '5px';
const invertBtn = document.createElement('button');
invertBtn.textContent = '反选';
invertBtn.style.marginRight = '5px';
const clearBtn = document.createElement('button');
clearBtn.textContent = '取消';
clearBtn.style.marginRight = '5px';
const exportBtn = document.createElement('button');
exportBtn.textContent = '导出链接';
exportBtn.style.backgroundColor = '#3370ff';
exportBtn.style.color = 'white';
btnGroup.append(selectAllBtn, invertBtn, clearBtn, exportBtn);
batchToolbar.append(selectedCount, btnGroup);
popup.appendChild(batchToolbar);
const list = document.createElement('ul');
list.style.listStyle = 'none';
list.style.padding = '0';
list.style.margin = '0';
videos.forEach((video, index) => { // 添加index参数
const item = document.createElement('li');
item.style.padding = '10px';
item.style.borderBottom = '1px solid #eee';
item.style.display = 'flex';
item.style.justifyContent = 'space-between';
item.style.alignItems = 'center';
// 添加复选框
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.dataset.index = index;
checkbox.style.marginRight = '10px';
item.appendChild(checkbox);
// 添加序号
const indexSpan = document.createElement('span');
indexSpan.textContent = `${index + 1}. `;
indexSpan.style.marginRight = '5px';
item.appendChild(indexSpan);
const name = document.createElement('span');
name.textContent = video.name;
item.appendChild(name);
// 修改这里:调用createDownloadButton而不是直接创建按钮
const downloadBtn = createDownloadButton(video);
item.appendChild(downloadBtn);
list.appendChild(item);
});
// 在popup.appendChild(list);之后添加批量操作功能
popup.appendChild(list);
document.body.appendChild(popup);
// 批量操作功能实现
const checkboxes = popup.querySelectorAll('input[type="checkbox"]');
function updateSelectedCount() {
const selected = popup.querySelectorAll('input[type="checkbox"]:checked').length;
selectedCount.textContent = `已选 ${selected} 个视频`;
}
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', updateSelectedCount);
});
selectAllBtn.addEventListener('click', () => {
checkboxes.forEach(checkbox => checkbox.checked = true);
updateSelectedCount();
});
invertBtn.addEventListener('click', () => {
checkboxes.forEach(checkbox => checkbox.checked = !checkbox.checked);
updateSelectedCount();
});
clearBtn.addEventListener('click', () => {
checkboxes.forEach(checkbox => checkbox.checked = false);
updateSelectedCount();
});
exportBtn.addEventListener('click', async () => {
const selectedIndexes = Array.from(popup.querySelectorAll('input[type="checkbox"]:checked'))
.map(checkbox => parseInt(checkbox.dataset.index));
if (selectedIndexes.length === 0) {
alert('请至少选择一个视频');
return;
}
// 创建结果展示弹窗
const resultPopup = document.createElement('div');
resultPopup.style.position = 'fixed';
resultPopup.style.top = '50%';
resultPopup.style.left = '50%';
resultPopup.style.transform = 'translate(-50%, -50%)';
resultPopup.style.width = '600px';
resultPopup.style.maxHeight = '70vh';
resultPopup.style.backgroundColor = 'white';
resultPopup.style.borderRadius = '8px';
resultPopup.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
resultPopup.style.padding = '20px';
resultPopup.style.zIndex = '10000';
resultPopup.style.overflow = 'auto';
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.position = 'absolute';
closeBtn.style.right = '15px';
closeBtn.style.top = '15px';
closeBtn.style.border = 'none';
closeBtn.style.background = 'none';
closeBtn.style.fontSize = '20px';
closeBtn.style.cursor = 'pointer';
closeBtn.addEventListener('click', () => document.body.removeChild(resultPopup));
resultPopup.appendChild(closeBtn);
const title = document.createElement('h3');
title.textContent = '视频下载链接';
title.style.marginBottom = '15px';
resultPopup.appendChild(title);
// 添加状态栏
const statusBar = document.createElement('div');
statusBar.style.marginBottom = '10px';
statusBar.style.padding = '8px';
statusBar.style.backgroundColor = '#f5f5f5';
statusBar.style.borderRadius = '4px';
statusBar.textContent = '准备获取下载链接...';
resultPopup.appendChild(statusBar);
const textarea = document.createElement('textarea');
textarea.style.width = '100%';
textarea.style.height = '300px';
textarea.style.marginBottom = '15px';
textarea.style.padding = '10px';
textarea.style.border = '1px solid #ddd';
textarea.style.borderRadius = '4px';
textarea.readOnly = true;
resultPopup.appendChild(textarea);
const btnGroup = document.createElement('div');
btnGroup.style.display = 'flex';
btnGroup.style.justifyContent = 'flex-end';
btnGroup.style.gap = '10px';
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制链接';
copyBtn.style.padding = '8px 16px';
copyBtn.style.backgroundColor = '#3370ff';
copyBtn.style.color = 'white';
copyBtn.style.border = 'none';
copyBtn.style.borderRadius = '4px';
copyBtn.style.cursor = 'pointer';
copyBtn.addEventListener('click', () => {
textarea.select();
document.execCommand('copy');
alert('链接已复制到剪贴板');
});
btnGroup.append(copyBtn);
resultPopup.appendChild(btnGroup);
document.body.appendChild(resultPopup);
// 实时获取并显示下载链接
for (const index of selectedIndexes) {
const video = videos[index];
statusBar.textContent = `正在获取: ${video.name}...`;
try {
const options = await getVideoDownloadOptions(video.element);
if (options && options.length > 0) {
const bestQuality = options.find(o => o.quality === '原画') ||
options[options.length - 1];
textarea.value += `${bestQuality.url}\n`;
statusBar.textContent = `获取成功: ${video.name}`;
} else {
statusBar.textContent = `获取失败: ${video.name} (未找到下载链接)`;
}
} catch (e) {
statusBar.textContent = `获取失败: ${video.name} (${e.message})`;
}
// 滚动到底部
textarea.scrollTop = textarea.scrollHeight;
await new Promise(resolve => setTimeout(resolve, 300)); // 添加短暂延迟
}
statusBar.textContent = `已完成 ${selectedIndexes.length} 个视频的链接获取`;
});
}
// 主函数
function main() {
// 创建主按钮
const mainBtn = document.createElement('button');
mainBtn.textContent = '显示视频列表';
mainBtn.style.position = 'fixed';
mainBtn.style.bottom = '20px';
mainBtn.style.left = '20px'; // 从right改为left
mainBtn.style.padding = '10px 20px';
mainBtn.style.backgroundColor = '#3370ff';
mainBtn.style.color = 'white';
mainBtn.style.border = 'none';
mainBtn.style.borderRadius = '4px';
mainBtn.style.cursor = 'pointer';
mainBtn.style.zIndex = '9998';
// 创建引导提示
const guideTip = document.createElement('div');
guideTip.innerHTML = '请滑动列表等待文档中所有视频元素加载完毕 <br> 重复打开关闭界面可以刷新加载内容 <br> 然后点击此按钮即可下载和获取视频地址 <button id="closeGuide" style="margin-left:10px;background:none;border:none;color:#3370ff;cursor:pointer;">我知道了</button>';
guideTip.style.position = 'fixed';
guideTip.style.bottom = '60px';
guideTip.style.left = '20px';
guideTip.style.padding = '10px 15px';
guideTip.style.backgroundColor = '#f0f7ff';
guideTip.style.border = '1px solid #3370ff';
guideTip.style.borderRadius = '4px';
guideTip.style.zIndex = '9997';
guideTip.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
// 关闭引导提示
document.body.appendChild(guideTip);
guideTip.querySelector('#closeGuide').addEventListener('click', () => {
document.body.removeChild(guideTip);
});
mainBtn.addEventListener('click', () => {
const videos = getAllVideoElements();
createVideoListPopup(videos);
});
document.body.appendChild(mainBtn);
}
// 页面加载完成后执行
window.addEventListener('load', main);
// 获取视频下载选项
async function getVideoDownloadOptions(videoElement) {
// 点击视频元素展开选项
const previewBtn = videoElement.querySelector('.btn-preview');
if (previewBtn) previewBtn.click();
// 等待选项列表出现
return new Promise(resolve => {
const checkInterval = setInterval(() => {
const optionsList = document.querySelector('.xg-options-list:not(.hide)');
if (optionsList) {
clearInterval(checkInterval);
// 提取所有下载选项
const options = Array.from(optionsList.querySelectorAll('.option-item')).map(item => ({
text: item.getAttribute('showtext'),
url: item.getAttribute('url'),
quality: item.getAttribute('definition')
}));
// 关闭选项列表
const closeBtn = document.querySelector('.xg-options-list:not(.hide) .close-btn');
if (closeBtn) closeBtn.click();
resolve(options);
}
}, 200);
});
}
// 创建下载按钮
function createDownloadButton(video) {
const btn = document.createElement('button');
btn.textContent = '下载';
btn.style.padding = '5px 10px';
btn.style.backgroundColor = '#3370ff';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '4px';
btn.style.cursor = 'pointer';
btn.addEventListener('click', async () => {
const options = await getVideoDownloadOptions(video.element);
if (options && options.length > 0) {
const optionPopup = document.createElement('div');
optionPopup.style.position = 'fixed';
optionPopup.style.top = '50%';
optionPopup.style.left = '50%';
optionPopup.style.transform = 'translate(-50%, -50%)';
optionPopup.style.backgroundColor = 'white';
optionPopup.style.padding = '20px';
optionPopup.style.borderRadius = '8px';
optionPopup.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
optionPopup.style.zIndex = '10000';
// 添加关闭按钮
const popupCloseBtn = document.createElement('button');
popupCloseBtn.textContent = '×';
popupCloseBtn.style.position = 'absolute';
popupCloseBtn.style.right = '5px';
popupCloseBtn.style.top = '5px';
popupCloseBtn.style.border = 'none';
popupCloseBtn.style.background = 'none';
popupCloseBtn.style.fontSize = '20px';
popupCloseBtn.style.cursor = 'pointer';
popupCloseBtn.addEventListener('click', () => {
document.body.removeChild(optionPopup);
});
optionPopup.appendChild(popupCloseBtn);
const title = document.createElement('h4');
title.textContent = `选择下载清晰度: ${video.name}`;
optionPopup.appendChild(title);
options.forEach(option => {
const optionBtn = document.createElement('button');
optionBtn.textContent = option.text;
optionBtn.style.display = 'block';
optionBtn.style.width = '100%';
optionBtn.style.margin = '5px 0';
optionBtn.style.padding = '8px 16px';
optionBtn.style.backgroundColor = '#3370ff';
optionBtn.style.color = 'white';
optionBtn.style.border = 'none';
optionBtn.style.borderRadius = '4px';
optionBtn.addEventListener('click', () => {
window.open(option.url, '_blank');
document.body.removeChild(optionPopup);
});
optionPopup.appendChild(optionBtn);
});
document.body.appendChild(optionPopup);
}
});
return btn;
}
})();