// ==UserScript==
// @name 一键复制磁力链和推送到115离线
// @author wangzijian0@vip.qq.com
// @description 目前支持BT4G/BTDig/BTSOW/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录115会员账号)
// @version 1.0.9.20250423
// @icon https://github.githubassets.com/assets/mona-loading-default-c3c7aad1282f.gif
// @include https://bt4gprx.com/*
// @include https://btdig.com/*
// @include https://www.btdig.com/*
// @include https://sobt*.*/*
// @include https://nyaa.si/*
// @include https://btsow.*/*
// @include https://cl*.cl*/*
// @match https://*.btmulu.work/*
// @match https://*.dmhy.org/*
// @grant GM_setClipboard
// @grant GM_notification
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @connect 115.com
// @connect login.115.com
// @connect *
// @run-at document-end
// @namespace https://greasyfork.org/users/1453515
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/531685/%E4%B8%80%E9%94%AE%E5%A4%8D%E5%88%B6%E7%A3%81%E5%8A%9B%E9%93%BE%E5%92%8C%E6%8E%A8%E9%80%81%E5%88%B0115%E7%A6%BB%E7%BA%BF.user.js
// @updateURL https://update.greasyfork.icu/scripts/531685/%E4%B8%80%E9%94%AE%E5%A4%8D%E5%88%B6%E7%A3%81%E5%8A%9B%E9%93%BE%E5%92%8C%E6%8E%A8%E9%80%81%E5%88%B0115%E7%A6%BB%E7%BA%BF.meta.js
// ==/UserScript==
(function() {
'use strict';
// 移动设备检测
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// 配置参数
const CONFIG = {
notificationTimeout: isMobile ? 5000 : 3000,
retryCount: 3,
retryDelay: 1000,
cookieRefreshInterval: 30 * 60 * 1000 // 30分钟刷新一次Cookie
};
// 错误码映射
const ERROR_CODES = {
10008: '任务已存在,无需重复添加',
911: '需要账号验证,请确保已登录115会员账号',
990: '任务包含违规内容,无法添加',
991: '服务器繁忙,请稍后再试',
992: '离线下载配额已用完',
993: '当前账号无权使用离线下载功能',
994: '文件大小超过限制',
995: '不支持的链接类型',
996: '网络错误,请检查连接',
997: '服务器内部错误',
998: '请求超时',
999: '未知错误'
};
// 初始化脚本
initScript();
function initScript() {
// 添加Tampermonkey菜单命令
GM_registerMenuCommand("检查115登录状态", async () => {
const isLoggedIn = await check115Login(true);
showNotification('115状态', isLoggedIn ? '已登录' : '未登录');
if (!isLoggedIn) {
setTimeout(() => {
if (confirm('需要登录115网盘,是否进入115网盘登录页面?')) {
window.open("https://115.com/?mode=login", "_blank");
}
}, 500);
}
});
GM_registerMenuCommand("清除115登录状态", () => {
GM_setValue('115_cookies', '');
GM_setValue('115_last_cookie_refresh', 0);
showNotification('115状态', '已清除登录状态');
});
GM_registerMenuCommand("打开115网盘", () => window.open("https://115.com", "_blank"));
// 设置定时刷新Cookie
setInterval(() => {
const lastRefresh = GM_getValue('115_last_cookie_refresh', 0);
if (Date.now() - lastRefresh > CONFIG.cookieRefreshInterval) {
check115Login(false);
}
}, 5 * 60 * 1000); // 每5分钟检查一次
// 监听页面变化
const observer = new MutationObserver(handleDomChanges);
observer.observe(document, {
childList: true,
subtree: true
});
// 初始添加按钮
addActionButtons();
}
// DOM变化处理
function handleDomChanges(mutations) {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
addActionButtons();
}
}
}
// 添加操作按钮
function addActionButtons() {
// BT4G网站处理
if (window.location.host.includes('bt4gprx.com')) {
handleBT4GSite();
}
/* ------------- CLM计划移除Start ------------- */
// CLM网站处理
else if (/cl[^.]+\.[^.]+\..+/.test(window.location.host)) {
handleCLMSite();
}
/* ------------- CLM计划移除End ------------- */
// 其他网站处理
else {
handleCommonSites();
}
/* ------------- CLM计划移除Start ------------- */
}
// 处理CLM网站
function handleCLMSite() {
// 处理磁力链部分
const magnetWrapper = document.querySelector('.Information_magnet_wrapper');
if (!magnetWrapper || magnetWrapper.dataset.buttonsAdded) return;
// 标记已处理
magnetWrapper.dataset.buttonsAdded = true;
// 获取磁力链接
const magnetLink = document.querySelector('.Information_magnet');
if (!magnetLink || !magnetLink.href.startsWith('magnet:')) return;
// 创建按钮容器
const btnContainer = document.createElement('div');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.margin = '10px 0';
btnContainer.style.display = 'flex';
btnContainer.style.gap = '10px';
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在磁力链后面
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
/* ------------- CLM计划移除End ------------- */
}
// 处理BT4G网站
function handleBT4GSite() {
document.querySelectorAll('.card-body').forEach(cardBody => {
if (cardBody.dataset.buttonsAdded) return;
const magnetBtn = cardBody.querySelector('a[href*="downloadtorrentfile.com/hash/"]');
if (!magnetBtn) return;
// 标记已处理
cardBody.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('div');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginRight = '10px';
// 添加按钮
btnContainer.appendChild(createBT4GCopyButton(magnetBtn));
btnContainer.appendChild(createBT4GOfflineButton(magnetBtn));
// 插入到DOM
magnetBtn.parentNode.insertBefore(btnContainer, magnetBtn);
});
}
// 创建BT4G风格的复制按钮
function createBT4GCopyButton(element) {
const btn = document.createElement('button');
btn.className = 'btn btn-sm copy-magnet-btn';
btn.innerHTML = '🔗 复制磁力链';
Object.assign(btn.style, {
cursor: 'pointer',
backgroundColor: '#000000',
color: '#ffffff',
border: '1px solid #000000',
borderRadius: '6px',
padding: isMobile ? '0.5rem 1rem' : '0.25rem 0.5rem',
fontSize: isMobile ? '1rem' : '0.875rem',
marginRight: '10px',
transition: 'all 0.15s ease-in-out',
fontWeight: '400',
lineHeight: isMobile ? '1.2' : '2',
textAlign: 'center',
verticalAlign: 'middle',
userSelect: 'none',
// 移动端优化
touchAction: 'manipulation',
minWidth: isMobile ? '120px' : 'auto',
minHeight: isMobile ? '40px' : 'auto'
});
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = '#333333';
btn.style.borderColor = '#333333';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = '#000000';
btn.style.borderColor = '#000000';
});
// 触摸反馈
btn.addEventListener('touchstart', () => {
btn.style.backgroundColor = '#333333';
btn.style.borderColor = '#333333';
});
btn.addEventListener('touchend', () => {
btn.style.backgroundColor = '#000000';
btn.style.borderColor = '#000000';
});
const handleCopy = async (e) => {
e.preventDefault();
e.stopPropagation();
const magnetLink = await extractMagnetLink(element);
if (!magnetLink) return;
try {
GM_setClipboard(magnetLink, 'text');
// 移动端备用方案
if (isMobile && navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(magnetLink);
} catch (clipboardError) {
console.log('使用navigator.clipboard失败:', clipboardError);
}
}
showNotification('磁力链已复制', magnetLink);
// 按钮反馈效果
const originalHTML = btn.innerHTML;
btn.innerHTML = '🔗 复制成功!';
btn.disabled = true;
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.disabled = false;
}, 2000);
} catch (error) {
showNotification('复制失败', `请手动复制: ${magnetLink}`);
}
};
// 同时监听点击和触摸事件
btn.addEventListener('click', handleCopy);
btn.addEventListener('touchend', handleCopy);
return btn;
}
// 创建BT4G风格的115离线按钮
function createBT4GOfflineButton(element) {
const btn = document.createElement('button');
btn.className = 'btn btn-sm offline-115-btn';
btn.innerHTML = '
推送到115离线';
Object.assign(btn.style, {
cursor: 'pointer',
backgroundColor: '#1E50A2',
color: '#fff',
border: '1px solid #1a4580',
borderRadius: '6px',
padding: isMobile ? '0.5rem 1rem' : '0.25rem 0.5rem',
fontSize: isMobile ? '1rem' : '0.875rem',
transition: 'all 0.15s ease-in-out',
fontWeight: '400',
lineHeight: isMobile ? '1.2' : '2',
textAlign: 'center',
verticalAlign: 'middle',
userSelect: 'none',
// 移动端优化
touchAction: 'manipulation',
minWidth: isMobile ? '140px' : 'auto',
minHeight: isMobile ? '40px' : 'auto'
});
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = '#1a4580';
btn.style.borderColor = '#163c70';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = '#1E50A2';
btn.style.borderColor = '#1a4580';
});
// 触摸反馈
btn.addEventListener('touchstart', () => {
btn.style.backgroundColor = '#1a4580';
btn.style.borderColor = '#163c70';
});
btn.addEventListener('touchend', () => {
btn.style.backgroundColor = '#1E50A2';
btn.style.borderColor = '#1a4580';
});
const handleOffline = async (e) => {
e.preventDefault();
e.stopPropagation();
const magnetLink = await extractMagnetLink(element);
if (!magnetLink) return;
await process115Offline(magnetLink);
};
// 同时监听点击和触摸事件
btn.addEventListener('click', handleOffline);
btn.addEventListener('touchend', handleOffline);
return btn;
}
// 处理其他通用网站
function handleCommonSites() {
// SOBT网站处理
if (/sobt[^.]+\..+/.test(window.location.host)) {
handleSOBTSite();
}
// BTDig网站处理
else if (window.location.host.endsWith('btdig.com')) {
handleBTDigSite();
}
// BTMulu网站处理
else if (window.location.host.endsWith('btmulu.work')) {
handleBTMuluSite();
}
// Nyaa网站处理
else if (window.location.host.includes('nyaa.si')) {
handleNyaaSite();
}
// DMHY网站处理
else if (window.location.host.includes('dmhy.org')) {
handleDMHYSite();
}
// BTSOW网站处理
else if (window.location.host.includes('btsow.pics') || window.location.host.includes('btsow.com')) {
handleBTSOWSite();
}
}
// 处理BTSOW网站
function handleBTSOWSite() {
// 处理搜索结果项
document.querySelectorAll('.data-list .row > a[href*="/magnet/detail/hash/"]').forEach(titleLink => {
if (titleLink.dataset.buttonsAdded) return;
// 标记已处理
titleLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginLeft = '15px';
btnContainer.style.marginRight = '10px';
// 提取hash
const hashMatch = titleLink.href.match(/\/hash\/([a-f0-9]+)/i);
if (!hashMatch || !hashMatch[1]) return;
const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`;
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在标题下
titleLink.parentNode.insertBefore(btnContainer, titleLink.nextSibling);
titleLink.closest('.row').appendChild(btnContainer);
});
}
// 处理SOBT网站
function handleSOBTSite() {
// 处理搜索结果页
document.querySelectorAll('h3 > a[href^="/torrent/"]').forEach(titleLink => {
if (titleLink.dataset.buttonsAdded) return;
// 标记已处理
titleLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginLeft = '10px';
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(titleLink));
btnContainer.appendChild(createCommonOfflineButton(titleLink));
// 插入到DOM
titleLink.parentNode.insertBefore(btnContainer, titleLink.nextSibling);
});
// 处理详情页
document.querySelectorAll('.panel-body a[href^="magnet:"]').forEach(magnetLink => {
if (magnetLink.dataset.buttonsAdded) return;
// 标记已处理
magnetLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('div');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.margin = '10px 0';
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
});
}
// 处理BTDig网站
function handleBTDigSite() {
// 处理搜索结果项
document.querySelectorAll('.torrent_name > a').forEach(titleLink => {
if (titleLink.dataset.buttonsAdded) return;
// 标记已处理
titleLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginRight = '10px';
// 获取磁力链
const magnetLink = document.querySelector('.torrent_magnet a[href^="magnet:"]');
if (!magnetLink) return;
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在标题前面
titleLink.parentNode.insertBefore(btnContainer, titleLink);
});
}
// 处理BTMulu网站
function handleBTMuluSite() {
// 处理搜索结果项
document.querySelectorAll('article.item a[href^="/hash/"]').forEach(titleLink => {
if (titleLink.dataset.buttonsAdded) return;
// 标记已处理
titleLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginLeft = '10px';
// 提取hash
const hashMatch = titleLink.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
if (!hashMatch || !hashMatch[1]) return;
const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`;
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在标题后面
titleLink.parentNode.insertBefore(btnContainer, titleLink.nextSibling);
});
}
// 处理Nyaa网站
function handleNyaaSite() {
// 处理搜索结果项
document.querySelectorAll('td.text-center a[href^="magnet:"]').forEach(magnetLink => {
if (magnetLink.dataset.buttonsAdded) return;
// 标记已处理
magnetLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginLeft = '10px';
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在磁力链图标后面
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
});
}
// 处理DMHY网站
function handleDMHYSite() {
// 调整磁力链列宽度为18%
const magnetHeader = document.querySelector('#topic_list th:nth-child(4)');
if (magnetHeader) {
magnetHeader.style.width = '18%';
}
// 处理搜索结果项
document.querySelectorAll('a.download-arrow.arrow-magnet').forEach(magnetLink => {
if (magnetLink.dataset.buttonsAdded) return;
// 标记已处理
magnetLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginLeft = '5px';
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在磁力链图标前面
magnetLink.parentNode.insertBefore(btnContainer, magnetLink);
});
// 处理详情页的磁力链接
document.querySelectorAll('#tabs-1 a.magnet, #tabs-1 a#magnet2').forEach(magnetLink => {
if (magnetLink.dataset.buttonsAdded) return;
// 标记已处理
magnetLink.dataset.buttonsAdded = true;
// 创建按钮容器
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginLeft = '5px';
// 添加按钮
btnContainer.appendChild(createCommonCopyButton(magnetLink));
btnContainer.appendChild(createCommonOfflineButton(magnetLink));
// 插入到DOM - 放在磁力链后面
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
});
}
// 创建通用复制按钮
function createCommonCopyButton(element) {
const btn = document.createElement('button');
btn.className = 'btn btn-info me-2 copy-magnet-btn';
btn.innerHTML = '🔗 复制';
Object.assign(btn.style, {
cursor: 'pointer',
backgroundColor: '#000000',
color: '#ffffff',
border: '1px solid #000000',
borderRadius: '4px',
padding: isMobile ? '8px 12px' : '4px 8px',
fontSize: isMobile ? '14px' : '12px',
marginRight: '5px',
transition: 'all 0.15s ease-in-out',
fontWeight: '400',
lineHeight: '1.5',
verticalAlign: 'middle',
// 移动端优化
touchAction: 'manipulation',
minWidth: isMobile ? '120px' : 'auto',
minHeight: isMobile ? '40px' : 'auto'
});
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = '#333333';
btn.style.borderColor = '#333333';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = '#000000';
btn.style.borderColor = '#000000';
});
// 触摸反馈
btn.addEventListener('touchstart', () => {
btn.style.backgroundColor = '#333333';
btn.style.borderColor = '#333333';
});
btn.addEventListener('touchend', () => {
btn.style.backgroundColor = '#000000';
btn.style.borderColor = '#000000';
});
const handleCopy = async (e) => {
e.preventDefault();
e.stopPropagation();
const magnetLink = await extractMagnetLink(element);
if (!magnetLink) return;
try {
GM_setClipboard(magnetLink, 'text');
// 移动端备用方案
if (isMobile && navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(magnetLink);
} catch (clipboardError) {
console.log('使用navigator.clipboard失败:', clipboardError);
}
}
showNotification('磁力链已复制', magnetLink);
// 按钮反馈效果
const originalHTML = btn.innerHTML;
btn.innerHTML = '🔗 完成';
btn.disabled = true;
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.disabled = false;
}, 2000);
} catch (error) {
showNotification('复制失败', `请手动复制: ${magnetLink}`);
}
};
// 同时监听点击和触摸事件
btn.addEventListener('click', handleCopy);
btn.addEventListener('touchend', handleCopy);
return btn;
}
// 创建通用115离线按钮
function createCommonOfflineButton(element) {
const btn = document.createElement('button');
btn.className = 'btn btn-danger me-2 offline-115-btn';
btn.innerHTML = '
115离线';
Object.assign(btn.style, {
cursor: 'pointer',
backgroundColor: '#1E50A2',
color: '#fff',
border: '1px solid #1a4580',
borderRadius: '4px',
padding: isMobile ? '8px 12px' : '4px 8px',
fontSize: isMobile ? '14px' : '12px',
transition: 'all 0.15s ease-in-out',
fontWeight: '400',
lineHeight: '1.5',
verticalAlign: 'middle',
// 移动端优化
touchAction: 'manipulation',
minWidth: isMobile ? '140px' : 'auto',
minHeight: isMobile ? '40px' : 'auto'
});
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = '#1a4580';
btn.style.borderColor = '#163c70';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = '#1E50A2';
btn.style.borderColor = '#1a4580';
});
// 触摸反馈
btn.addEventListener('touchstart', () => {
btn.style.backgroundColor = '#1a4580';
btn.style.borderColor = '#163c70';
});
btn.addEventListener('touchend', () => {
btn.style.backgroundColor = '#1E50A2';
btn.style.borderColor = '#1a4580';
});
const handleOffline = async (e) => {
e.preventDefault();
e.stopPropagation();
const magnetLink = await extractMagnetLink(element);
if (!magnetLink) return;
await process115Offline(magnetLink);
};
// 同时监听点击和触摸事件
btn.addEventListener('click', handleOffline);
btn.addEventListener('touchend', handleOffline);
return btn;
}
// 从元素提取磁力链
async function extractMagnetLink(element) {
try {
// 如果是SOBT网站的标题链
if (element.href && element.href.includes('/torrent/')) {
const hashMatch = element.href.match(/\/torrent\/([a-f0-9]+)\.html$/i);
if (hashMatch && hashMatch[1]) {
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
}
}
// 如果是BT4G网站的磁力链
else if (element.href && element.href.includes('downloadtorrentfile.com/hash/')) {
const hashMatch = element.href.match(/hash\/([a-f0-9]+)/i);
if (hashMatch && hashMatch[1]) {
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
}
}
// 如果是BTMulu网站的标题链
else if (element.href && element.href.includes('/hash/')) {
const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
if (hashMatch && hashMatch[1]) {
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
}
}
// 如果是BTSOW网站的标题链
else if (element.href && element.href.includes('/magnet/detail/hash/')) {
const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)/i);
if (hashMatch && hashMatch[1]) {
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
}
}
// 如果是Nyaa/DMHY网站或直接磁力链
else if (element.href && element.href.startsWith('magnet:')) {
return element.href;
}
// 如果是字符串直接返回
else if (typeof element === 'string' && element.startsWith('magnet:')) {
return element;
}
throw new Error('无法提取磁力链Hash');
} catch (error) {
showNotification('错误', error.message);
return null;
}
}
// 检查115登录状态
async function check115Login(forceCheck = false) {
try {
// 如果有强制检查标志或者Cookie为空或者超过刷新间隔,则重新获取
const lastRefresh = GM_getValue('115_last_cookie_refresh', 0);
const currentCookies = GM_getValue('115_cookies', '');
if (!forceCheck && currentCookies && Date.now() - lastRefresh < CONFIG.cookieRefreshInterval) {
return true;
}
// 尝试获取当前有效的Cookie
const cookies = await getCurrent115Cookies();
if (!cookies) {
GM_setValue('115_cookies', '');
GM_setValue('115_last_cookie_refresh', 0);
return false;
}
// 验证Cookie是否有效
const isValid = await validate115Cookies(cookies);
if (isValid) {
GM_setValue('115_cookies', cookies);
GM_setValue('115_last_cookie_refresh', Date.now());
return true;
}
GM_setValue('115_cookies', '');
GM_setValue('115_last_cookie_refresh', 0);
return false;
} catch (error) {
console.error('检查登录状态失败:', error);
return false;
}
}
// 获取当前有效的115 Cookie
function getCurrent115Cookies() {
return new Promise((resolve) => {
GM_xmlhttpRequest({
url: 'https://115.com/',
method: 'GET',
anonymous: true, // 不发送现有Cookie
onload: function(response) {
// 从响应头获取Cookie
const cookieHeader = response.responseHeaders
.split('\n')
.find(row => row.toLowerCase().startsWith('set-cookie:'));
if (cookieHeader) {
const cookies = cookieHeader.replace(/^set-cookie:\s*/i, '').split(';')[0];
resolve(cookies);
} else {
// 检查是否重定向到登录页面
if (response.finalUrl.includes('login.115.com')) {
resolve('');
} else {
// 尝试使用之前存储的Cookie
const savedCookies = GM_getValue('115_cookies', '');
resolve(savedCookies);
}
}
},
onerror: () => resolve('')
});
});
}
// 验证Cookie是否有效
function validate115Cookies(cookies) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
url: 'https://115.com/web/lixian/',
method: 'GET',
headers: {
'Cookie': cookies
},
onload: function(response) {
// 检查是否重定向到登录页面
if (response.finalUrl.includes('login.115.com')) {
resolve(false);
} else {
resolve(true);
}
},
onerror: () => resolve(false)
});
});
}
// 115离线下载处理流程
async function process115Offline(magnetLink) {
const notificationId = Date.now();
try {
// 步骤1:检查登录状态
showNotification('115离线', '正在检查登录状态...', notificationId);
const isLoggedIn = await check115Login(true);
if (!isLoggedIn) {
throw new Error('请先登录115网盘');
}
// 步骤2:提交离线任务
showNotification('115离线', '正在提交离线任务...', notificationId);
const result = await submit115OfflineTask(magnetLink);
// 处理结果
handleOfflineResult(result);
} catch (error) {
showNotification('115离线失败', error.message);
// 如果是未登录错误,显示登录提示
if (error.message.includes('登录')) {
setTimeout(() => {
if (confirm('需要登录115网盘,是否进入115网盘登录页面?')) {
window.open('https://115.com/?mode=login', '_blank');
}
}, 500);
}
} finally {
// 关闭进度通知
GM_notification({ id: notificationId, done: true });
}
}
// 提交115离线任务
async function submit115OfflineTask(magnetLink) {
const cookies = GM_getValue('115_cookies', '');
if (!cookies) {
throw new Error('未检测到有效的登录状态');
}
const response = await fetch115Api(
`https://115.com/web/lixian/?ct=lixian&ac=add_task_url&url=${encodeURIComponent(magnetLink)}`,
{
headers: {
'Cookie': cookies
}
}
);
return tryParseJson(response);
}
// 处理离线结果
function handleOfflineResult(result) {
if (!result) {
throw new Error('无效的响应');
}
if (result.state) {
showNotification('115离线成功', '任务已成功添加到离线下载列表');
return;
}
const errorMsg = ERROR_CODES[result.errcode] || result.error_msg || '未知错误';
throw new Error(errorMsg);
}
// 通用请求函数
function fetch115Api(url, options = {}) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url: url,
method: options.method || 'GET',
headers: {
'User-Agent': navigator.userAgent,
'Origin': 'https://115.com',
...(options.headers || {})
},
data: options.body,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
resolve(response.responseText);
} else {
reject(new Error(`请求失败: ${response.status}`));
}
},
onerror: reject
});
});
}
// 尝试解析JSON
function tryParseJson(text) {
try {
return JSON.parse(text);
} catch (e) {
return null;
}
}
// 显示通知
function showNotification(title, text, id = null) {
GM_notification({
title: title,
text: text,
timeout: CONFIG.notificationTimeout,
...(id ? { id } : {})
});
}
})();