Warning: fopen(/www/sites/update.greasyfork.icu/index/store/temp/c5af6e961cbb1908f80af43f7f9c9854.js): failed to open stream: No space left on device in /www/sites/update.greasyfork.icu/index/scriptControl.php on line 65
// ==UserScript==
// @name FSM 收藏夹链接提取
// @namespace http://tampermonkey.net/
// @version 0.3
// @description Extract and convert FSM torrent links from all pages (on-demand authentication)
// @author You
// @match https://fsm.name/Torrents/mine?type=favorite*
// @grant GM_setClipboard
// @downloadURL https://update.greasyfork.icu/scripts/528768/FSM%20%E6%94%B6%E8%97%8F%E5%A4%B9%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%96.user.js
// @updateURL https://update.greasyfork.icu/scripts/528768/FSM%20%E6%94%B6%E8%97%8F%E5%A4%B9%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%96.meta.js
// ==/UserScript==
(function() {
'use strict';
let passkey = '';
let authorization = '';
let deviceId = '';
let maxPage = 1;
let currentPage = 1;
let allLinks = [];
// 创建加载覆盖层
function createLoadingOverlay() {
const overlay = document.createElement('div');
overlay.id = 'fsm-loading-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
overlay.style.zIndex = '10000';
overlay.style.display = 'flex';
overlay.style.flexDirection = 'column';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
overlay.style.color = 'white';
overlay.style.fontSize = '24px';
const loadingText = document.createElement('div');
loadingText.id = 'fsm-loading-text';
loadingText.textContent = '正在获取下载链接...';
loadingText.style.marginBottom = '20px';
const progressText = document.createElement('div');
progressText.id = 'fsm-progress-text';
progressText.textContent = '进度: 0/0 页';
progressText.style.fontSize = '18px';
overlay.appendChild(loadingText);
overlay.appendChild(progressText);
document.body.appendChild(overlay);
return overlay;
}
// 获取凭证信息
function getCredentials() {
authorization = localStorage.getItem('token');
deviceId = localStorage.getItem('DeviceId');
if (!authorization || !deviceId) {
showNotification('获取凭证失败,请确保已登录', 'error');
return false;
}
return true;
}
// 获取 passkey
async function fetchPasskey() {
try {
if (!getCredentials()) return false;
const response = await fetch('https://fsm.name/api/Users/infos', {
headers: {
'accept': 'application/json, text/plain, */*',
'authorization': authorization,
'deviceid': deviceId
}
});
const data = await response.json();
if (data.success && data.data.passkey) {
passkey = data.data.passkey;
return true;
}
showNotification('获取 passkey 失败: ' + (data.msg || '未知错误'), 'error');
return false;
} catch (error) {
console.error('获取 passkey 失败:', error);
showNotification('获取 passkey 失败: ' + error.message, 'error');
return false;
}
}
// 获取收藏种子列表
async function fetchFavorites(page) {
try {
if (!getCredentials()) return null;
const response = await fetch(`https://fsm.name/api/Torrents/listMyFavorite?page=${page}`, {
headers: {
'accept': 'application/json, text/plain, */*',
'authorization': authorization,
'deviceid': deviceId
}
});
const data = await response.json();
if (data.success) {
// 更新总页数
if (data.data.maxPage > maxPage) {
maxPage = data.data.maxPage;
}
return data.data.list || [];
}
showNotification('获取收藏列表失败: ' + (data.msg || '未知错误'), 'error');
return null;
} catch (error) {
console.error('获取收藏列表失败:', error);
showNotification('获取收藏列表失败: ' + error.message, 'error');
return null;
}
}
// 更新进度文本
function updateProgress(current, total) {
const progressText = document.getElementById('fsm-progress-text');
if (progressText) {
progressText.textContent = `进度: ${current}/${total} 页`;
}
}
// 显示通知
function showNotification(message, type) {
if (window.$notify) {
window.$notify({
message: message,
type: type
});
} else {
alert(message);
}
}
// 获取当前页面的种子链接
async function extractCurrentPageLinks() {
if (!passkey) {
const success = await fetchPasskey();
if (!success) return [];
}
const links = document.querySelectorAll('.el-table__body-wrapper a');
return Array.from(links)
.map(link => {
const tid = new URL(link.href).searchParams.get('tid');
return `https://api.fsm.name/Torrents/download?tid=${tid}&passkey=${passkey}&source=direct`;
});
}
// 提取单页的链接
function extractLinksFromData(list) {
return list.map(item => {
return `https://api.fsm.name/Torrents/download?tid=${item.tid}&passkey=${passkey}&source=direct`;
});
}
// 获取所有页面的链接
async function extractAllPagesLinks() {
const overlay = createLoadingOverlay();
try {
// 在按钮点击时获取 passkey
if (!passkey) {
const success = await fetchPasskey();
if (!success) {
overlay.remove();
return;
}
}
// 获取第一页数据,确定总页数
const firstPageData = await fetchFavorites(1);
if (!firstPageData) {
overlay.remove();
return;
}
// 添加第一页链接
allLinks = extractLinksFromData(firstPageData);
updateProgress(1, maxPage);
// 获取剩余页面
for (let page = 2; page <= maxPage; page++) {
const pageData = await fetchFavorites(page);
if (pageData) {
const pageLinks = extractLinksFromData(pageData);
allLinks = allLinks.concat(pageLinks);
}
updateProgress(page, maxPage);
// 稍微延迟,避免请求过快
await new Promise(resolve => setTimeout(resolve, 300));
}
// 复制到剪贴板
GM_setClipboard(allLinks.join('\n'));
showNotification(`已复制所有收藏链接 (共 ${allLinks.length} 个)`, 'success');
} catch (error) {
console.error('获取所有链接失败:', error);
showNotification('获取所有链接失败: ' + error.message, 'error');
} finally {
overlay.remove();
}
}
// 复制当前页面链接
async function extractAndCopyCurrentPageLinks() {
try {
// 在按钮点击时获取 passkey
if (!passkey) {
const success = await fetchPasskey();
if (!success) return;
}
const links = await extractCurrentPageLinks();
if (links.length > 0) {
GM_setClipboard(links.join('\n'));
showNotification(`已复制当前页面链接 (共 ${links.length} 个)`, 'success');
} else {
showNotification('没有找到可复制的链接', 'warning');
}
} catch (error) {
console.error('提取当前页链接失败:', error);
showNotification('提取当前页链接失败: ' + error.message, 'error');
}
}
// 添加按钮
function addButtons() {
// 检查是否已存在按钮
if (document.getElementById('fsm-extract-buttons')) {
return;
}
// 创建按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.id = 'fsm-extract-buttons';
buttonContainer.style.position = 'fixed';
buttonContainer.style.right = '20px';
buttonContainer.style.bottom = '20px';
buttonContainer.style.zIndex = '9999';
buttonContainer.style.display = 'flex';
buttonContainer.style.flexDirection = 'column';
buttonContainer.style.gap = '10px';
// 当前页面按钮
const currentPageBtn = document.createElement('button');
currentPageBtn.className = 'el-button el-button--primary el-button--small';
currentPageBtn.innerHTML = '提取当前页链接';
currentPageBtn.addEventListener('click', extractAndCopyCurrentPageLinks);
// 所有页面按钮
const allPagesBtn = document.createElement('button');
allPagesBtn.className = 'el-button el-button--danger el-button--small';
allPagesBtn.innerHTML = '提取所有页链接';
allPagesBtn.addEventListener('click', extractAllPagesLinks);
// 添加到容器
buttonContainer.appendChild(currentPageBtn);
buttonContainer.appendChild(allPagesBtn);
// 添加到页面
document.body.appendChild(buttonContainer);
}
// 使用 MutationObserver 确保在页面动态加载后添加按钮
const observer = new MutationObserver((mutations, obs) => {
if (document.querySelector('.el-table__body-wrapper')) {
addButtons();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// 页面加载完成时也尝试添加按钮
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addButtons);
} else {
addButtons();
}
// 移除了初始获取 passkey 的部分
})();