// ==UserScript==
// @name 一键复制磁力链和推送到115离线
// @author [email protected]
// @description 目前支持BT4G/BTDig/SOBT/BTMulu/Nyaa/DMHY/CLM(磁力链搜索引擎)添加一键复制磁力链和推送到115网盘进行离线(推送离线任务需当前浏览器已登录(不可用)115会员账号)
// @version 1.1.1.20250727
// @icon https://github.githubassets.com/assets/mona-loading-default-c3c7aad1282f.gif
// @include *://bt4gprx.com/*
// @include *://btdig.com/*
// @include *://*.btdig.com/*
// @include *://sobt*.*/*
// @include *://nyaa.si/*
// @include *://cl*.cl*/*
// @include *://*.btmulu.*/*
// @include *://btmulu.*/*
// @include *://idope.se/*
// @include *://*.dmhy.org/*
// @include *://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://gf.qytechs.cn/users/1453515
// @license MIT
// ==/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: '未知错误'
};
// 按钮样式常量
const BUTTON_STYLES = {
common: {
cursor: 'pointer',
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 ? '120px' : 'auto',
minHeight: isMobile ? '40px' : 'auto'
},
copy: {
backgroundColor: '#000000',
color: '#ffffff',
border: '1px solid #000000',
marginRight: '5px'
},
offline: {
backgroundColor: '#1E50A2',
color: '#fff',
border: '1px solid #1a4580'
},
hoverCopy: {
backgroundColor: '#333333',
borderColor: '#333333'
},
hoverOffline: {
backgroundColor: '#1a4580',
borderColor: '#163c70'
}
};
// 主初始化函数
function initializeScript() {
// 添加Tampermonkey菜单命令
addMenuCommands();
// 设置定时刷新Cookie
setInterval(checkCookieRefresh, 5 * 60 * 1000);
// 监听页面变化
setupMutationObserver();
// 初始添加按钮
addActionButtons();
injectBtmuluTitleWidthCSS();
insertIdopeDetailButtons();
}
// 添加Tampermonkey菜单命令
function addMenuCommands() {
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是否需要刷新
function checkCookieRefresh() {
const lastRefresh = GM_getValue('115_last_cookie_refresh', 0);
if (Date.now() - lastRefresh > CONFIG.cookieRefreshInterval) {
check115Login(false);
}
}
// 设置DOM变化观察器
function setupMutationObserver() {
const observer = new MutationObserver(handleDomChanges);
observer.observe(document, {
childList: true,
subtree: true
});
}
// DOM变化处理
function handleDomChanges(mutations) {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
addActionButtons();
injectBtmuluTitleWidthCSS();
insertIdopeDetailButtons();
}
}
}
// 注入btmulu标题宽度自适应CSS
function injectBtmuluTitleWidthCSS() {
if (!/([^.]+\.)?btmulu\.[^/]+$/.test(window.location.host)) return;
if (document.getElementById('btmulu-title-width-style')) return;
const style = document.createElement('style');
style.id = 'btmulu-title-width-style';
style.textContent = `
article.item > div > a {
display: inline-block !important;
width: auto !important;
max-width: 100% !important;
vertical-align: middle;
}
article.item > div > a h4 {
display: inline !important;
}
`;
document.head.appendChild(style);
}
// 处理iDope详情页按钮插入到标题左侧
function insertIdopeDetailButtons() {
const nameDiv = document.getElementById('name');
const magnetA = document.getElementById('mangetinfo');
if (!nameDiv || !magnetA) return;
if (nameDiv.dataset.buttonsAdded) return;
nameDiv.dataset.buttonsAdded = 'true';
const magnetLink = magnetA.href;
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-flex';
btnContainer.style.alignItems = 'center';
btnContainer.style.marginRight = '10px';
btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));
nameDiv.insertBefore(btnContainer, nameDiv.firstChild);
}
// 添加操作按钮
function addActionButtons() {
// BT4G网站处理
if (window.location.host.includes('bt4gprx.com')) {
handleBT4GSite();
}
// CLM网站处理
else if (/cl[^.]+\.[^.]+\..+/.test(window.location.host)) {
handleCLMSite();
}
// iDope网站处理
else if (window.location.host.includes('idope.se')) {
handleIdopeSite();
}
// 其他网站处理
else {
handleCommonSites();
}
}
// 处理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(createButton('copy', magnetLink));
btnContainer.appendChild(createButton('offline', magnetLink));
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
}
// 处理iDope网站
function handleIdopeSite() {
document.querySelectorAll('.resultdivtop').forEach(resultDiv => {
if (resultDiv.dataset.buttonsAdded) return;
resultDiv.dataset.buttonsAdded = true;
const titleLink = resultDiv.querySelector('a[href^="/torrent/"]');
if (!titleLink) return;
const hashMatch = titleLink.href.match(/\/torrent\/[^\/]+\/([a-f0-9]+)\//i);
if (!hashMatch || !hashMatch[1]) return;
const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`;
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-flex';
btnContainer.style.alignItems = 'center';
btnContainer.style.marginRight = '8px';
btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));
const titleDiv = titleLink.querySelector('.resultdivtopname');
if (titleDiv) {
titleDiv.insertBefore(btnContainer, titleDiv.firstChild);
} else {
titleLink.insertBefore(btnContainer, titleLink.firstChild);
}
});
}
// 创建按钮的通用函数
function createButton(type, element, icon = null, styleType = 'common', noDefaultClick = false) {
const btn = document.createElement('button');
btn.className = `${type}-magnet-btn`;
// 设置按钮内容和样式
if (styleType === 'idope') {
applyIdopeButtonStyle(btn, type, icon);
} else if (styleType === 'btmulu') {
applyBtmuluButtonStyle(btn, type, element);
} else {
applyCommonButtonStyle(btn, type, icon);
}
// 添加点击事件
if (!noDefaultClick) {
setupButtonClickHandler(btn, type, element, styleType);
}
return btn;
}
// 应用iDope风格按钮样式
function applyIdopeButtonStyle(btn, type, icon) {
Object.assign(btn.style, {
cursor: 'pointer',
backgroundColor: 'transparent',
color: '#555',
border: '1px solid #ddd',
borderRadius: '4px',
padding: '2px 6px',
fontSize: '12px',
marginRight: '5px',
transition: 'all 0.15s ease-in-out',
fontWeight: '400',
lineHeight: '1.5',
verticalAlign: 'middle',
touchAction: 'manipulation',
width: '30px',
height: '26px',
minWidth: '30px',
minHeight: '26px',
boxSizing: 'border-box',
});
btn.innerHTML = icon || (type === 'copy' ? '🔗' : '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">');
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = '#f0f0f0';
btn.style.borderColor = '#ccc';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = 'transparent';
btn.style.borderColor = '#ddd';
});
btn.addEventListener('touchstart', () => {
btn.style.backgroundColor = '#f0f0f0';
btn.style.borderColor = '#ccc';
});
btn.addEventListener('touchend', () => {
btn.style.backgroundColor = 'transparent';
btn.style.borderColor = '#ddd';
});
}
// 应用BTMulu风格按钮样式
function applyBtmuluButtonStyle(btn, type, typeLabel) {
btn.textContent = type === 'copy' ? '复制' : '离线';
btn.style.height = typeLabel.offsetHeight ? typeLabel.offsetHeight + 'px' : '22px';
btn.style.lineHeight = typeLabel.offsetHeight ? typeLabel.offsetHeight + 'px' : '22px';
btn.style.padding = '0 10px';
btn.style.fontSize = '12px';
btn.style.background = type === 'copy' ? '#000' : '#1E50A2';
btn.style.color = '#fff';
btn.style.border = 'none';
btn.style.borderRadius = '4px';
btn.style.marginRight = '5px';
btn.style.marginTop = '1px';
btn.style.marginLeft = type === 'copy' ? '-2px' : '2px';
btn.style.verticalAlign = 'middle';
btn.style.cursor = 'pointer';
btn.style.transition = 'background 0.15s';
btn.addEventListener('mouseenter', () => {
btn.style.background = type === 'copy' ? '#333' : '#163c70';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = type === 'copy' ? '#000' : '#1E50A2';
});
}
// 应用通用按钮样式
function applyCommonButtonStyle(btn, type, icon) {
Object.assign(btn.style, BUTTON_STYLES.common);
Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline);
btn.innerHTML = icon || (type === 'copy' ? '🔗 复制' : '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 离线');
btn.addEventListener('mouseenter', () => {
Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.hoverCopy : BUTTON_STYLES.hoverOffline);
});
btn.addEventListener('mouseleave', () => {
Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline);
});
btn.addEventListener('touchstart', () => {
Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.hoverCopy : BUTTON_STYLES.hoverOffline);
});
btn.addEventListener('touchend', () => {
Object.assign(btn.style, type === 'copy' ? BUTTON_STYLES.copy : BUTTON_STYLES.offline);
});
}
// 设置按钮点击事件处理
function setupButtonClickHandler(btn, type, element, styleType) {
const handleClick = async (e) => {
e.preventDefault();
e.stopPropagation();
const magnetLink = typeof element === 'string' ? element : await extractMagnetLink(element);
if (!magnetLink) return;
if (type === 'copy') {
// 针对不同站点传递 styleType
let feedbackType = styleType;
// nyaa.si 识别
if (window.location.host.includes('nyaa.si')) feedbackType = 'nyaa';
await handleCopyAction(btn, magnetLink, feedbackType);
} else {
await handleOfflineAction(btn, magnetLink, styleType);
}
};
btn.addEventListener('click', handleClick);
btn.addEventListener('touchend', handleClick);
}
// 处理复制操作
async function handleCopyAction(btn, magnetLink, styleType) {
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;
let feedbackHTML = '';
if (["idope", "btmulu", "nyaa"].includes(styleType)) {
// 绿色对勾SVG
feedbackHTML = '<svg width="18" height="18" viewBox="0 0 18 18" style="display:inline-block;vertical-align:middle;"><circle cx="9" cy="9" r="9" fill="#4caf50"/><polyline points="5,10 8,13 13,6" fill="none" stroke="#fff" stroke-width="2"/></svg>';
} else {
// "完成"或"成功"
feedbackHTML = styleType === 'bt4g' ? '🔗 完成' : '🔗 完成';
}
btn.innerHTML = feedbackHTML;
btn.disabled = true;
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.disabled = false;
}, 2000);
} catch (error) {
showNotification('复制失败', `请手动复制: ${magnetLink}`);
}
}
// 处理离线操作
async function handleOfflineAction(btn, magnetLink, styleType) {
await process115Offline(magnetLink);
// 按钮反馈效果
const originalHTML = btn.innerHTML;
btn.innerHTML = styleType === 'idope' ? '<svg width="18" height="18" viewBox="0 0 18 18" style="display:inline-block;vertical-align:middle;"><circle cx="9" cy="9" r="9" fill="#4caf50"/><polyline points="5,10 8,13 13,6" fill="none" stroke="#fff" stroke-width="2"/></svg>' :
styleType === 'btmulu' ? '成功' :
'<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:middle;"> 成功';
btn.disabled = true;
setTimeout(() => {
btn.innerHTML = originalHTML;
btn.disabled = false;
}, 2000);
}
// 处理BT4G网站
function handleBT4GSite() {
// 在搜索结果标题前插入按钮
document.querySelectorAll('.result-item h5 > a[href^="/magnet/"]').forEach(titleA => {
if (titleA.dataset.bt4gButtonsAdded) return;
titleA.dataset.bt4gButtonsAdded = 'true';
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-block';
btnContainer.style.marginRight = '8px';
btnContainer.style.verticalAlign = 'middle';
// 复制按钮
const copyBtn = createButton('copy', titleA, null, 'common', true);
copyBtn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const magnet = await fetchBT4GMagnetFromDetail(titleA.href);
if (!magnet) return showNotification('复制失败', '未获取到磁力链');
GM_setClipboard(magnet, 'text');
showNotification('磁力链已复制', magnet);
copyBtn.innerHTML = '🔗 完成';
copyBtn.disabled = true;
setTimeout(() => {
copyBtn.innerHTML = '🔗 复制';
copyBtn.disabled = false;
}, 2000);
});
// 离线按钮
const offlineBtn = createButton('offline', titleA, null, 'common', true);
offlineBtn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const magnet = await fetchBT4GMagnetFromDetail(titleA.href);
if (!magnet) return showNotification('推送失败', '未获取到磁力链');
await process115Offline(magnet);
offlineBtn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 成功';
offlineBtn.disabled = true;
setTimeout(() => {
offlineBtn.innerHTML = '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 离线';
offlineBtn.disabled = false;
}, 2000);
});
btnContainer.appendChild(copyBtn);
btnContainer.appendChild(offlineBtn);
titleA.parentNode.insertBefore(btnContainer, titleA);
});
// 详情页按钮逻辑
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(createButton('copy', magnetBtn, '🔗 复制', 'bt4g'));
btnContainer.appendChild(createButton('offline', magnetBtn, '<img src="https://115.com/favicon.ico" style="width:14px;height:14px;vertical-align:text-bottom;margin-right:2px;"> 离线', 'bt4g'));
magnetBtn.parentNode.insertBefore(btnContainer, magnetBtn);
});
}
// 异步获取BT4G详情页磁力链
async function fetchBT4GMagnetFromDetail(detailHref) {
try {
let url = detailHref;
if (!/^https?:/.test(url)) {
url = location.origin + url;
}
const resp = await fetch(url, { credentials: 'omit' });
const html = await resp.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const magnetA = doc.querySelector('a.btn.btn-primary.me-2[href*="downloadtorrentfile.com/hash/"]');
if (!magnetA) return null;
const hashMatch = magnetA.href.match(/hash\/([a-f0-9]{40})/i);
if (!hashMatch) return null;
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
} catch (e) {
return null;
}
}
// 处理其他通用网站
function handleCommonSites() {
// SOBT网站处理
if (/sobt[^.]+\..+/.test(window.location.host)) {
handleSOBTSite();
}
// BTDig网站处理
else if (window.location.host.endsWith('btdig.com')) {
handleBTDigSite();
}
// BTMulu网站处理
else if (/([^.]+\.)?btmulu\.[^/]+$/.test(window.location.host)) {
handleBTMuluSite();
}
// Nyaa网站处理
else if (window.location.host.includes('nyaa.si')) {
handleNyaaSite();
}
// DMHY网站处理
else if (window.location.host.includes('dmhy.org')) {
handleDMHYSite();
}
}
// 处理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.style.marginRight = '5px';
btnContainer.appendChild(createButton('copy', titleLink));
btnContainer.appendChild(createButton('offline', titleLink));
titleLink.parentNode.insertBefore(btnContainer, titleLink);
});
// 处理详情页的磁力链接
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(createButton('copy', magnetLink));
btnContainer.appendChild(createButton('offline', magnetLink));
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
});
// 处理详情页磁力链 input 框右侧按钮
const mLinkInput = document.getElementById('m_link');
if (mLinkInput && !mLinkInput.dataset.idopeButtonsAdded) {
mLinkInput.dataset.idopeButtonsAdded = 'true';
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-flex';
btnContainer.style.alignItems = 'center';
btnContainer.style.marginLeft = '8px';
// 复制按钮
btnContainer.appendChild(createButton('copy', mLinkInput.value, '🔗', 'idope'));
// 离线按钮
btnContainer.appendChild(createButton('offline', mLinkInput.value, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));
mLinkInput.parentNode.insertBefore(btnContainer, mLinkInput.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';
let resultDiv = titleLink.closest('.one_result');
let magnetLink = resultDiv ? resultDiv.querySelector('.torrent_magnet a[href^="magnet:"]') : null;
if (!magnetLink) return;
btnContainer.appendChild(createButton('copy', magnetLink));
btnContainer.appendChild(createButton('offline', magnetLink));
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 h4 = titleLink.querySelector('h4');
if (!h4) return;
const typeLabel = h4.querySelector('span.label');
if (!typeLabel) return;
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-flex';
btnContainer.style.alignItems = 'center';
btnContainer.style.marginLeft = '10px';
btnContainer.style.gap = '5px';
const hashMatch = titleLink.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
if (!hashMatch || !hashMatch[1]) return;
const magnetLink = `magnet:?xt=urn:btih:${hashMatch[1]}`;
// 使用iDope风格的按钮
btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));
typeLabel.after(btnContainer);
});
}
// 处理Nyaa网站
function handleNyaaSite() {
document.querySelectorAll('td.text-center a[href^="magnet:"]').forEach(magnetLink => {
if (magnetLink.dataset.buttonsAdded) return;
magnetLink.dataset.buttonsAdded = true;
let tr = magnetLink.closest('tr');
let downloadBtn = tr ? tr.querySelector("a[href^='/download/']") : null;
const btnContainer = document.createElement('span');
btnContainer.className = 'magnet-action-buttons';
btnContainer.style.display = 'inline-flex';
btnContainer.style.alignItems = 'center';
btnContainer.style.marginRight = '6px';
btnContainer.appendChild(createButton('copy', magnetLink, '🔗', 'idope'));
btnContainer.appendChild(createButton('offline', magnetLink, '<img src="https://115.com/favicon.ico" style="width:12px;height:12px;vertical-align:middle;">', 'idope'));
if (downloadBtn) {
downloadBtn.parentNode.insertBefore(btnContainer, downloadBtn);
} else {
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
}
});
}
// 处理DMHY网站
function handleDMHYSite() {
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(createButton('copy', magnetLink));
btnContainer.appendChild(createButton('offline', magnetLink));
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(createButton('copy', magnetLink));
btnContainer.appendChild(createButton('offline', magnetLink));
magnetLink.parentNode.insertBefore(btnContainer, magnetLink.nextSibling);
});
}
// 从元素提取磁力链
async function extractMagnetLink(element) {
try {
if (element.href) {
// SOBT网站的标题链
if (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.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.includes('/hash/')) {
const hashMatch = element.href.match(/\/hash\/([a-f0-9]+)\.html$/i);
if (hashMatch && hashMatch[1]) {
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
}
}
// iDope网站的标题链
else if (element.href.includes('/torrent/')) {
const hashMatch = element.href.match(/\/torrent\/[^\/]+\/([a-f0-9]+)\//i);
if (hashMatch && hashMatch[1]) {
return `magnet:?xt=urn:btih:${hashMatch[1]}`;
}
}
// Nyaa/DMHY网站或直接磁力链
else if (element.href.startsWith('magnet:')) {
return element.href;
}
}
// 如果是字符串直接返回
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 {
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;
}
const cookies = await getCurrent115Cookies();
if (!cookies) {
GM_setValue('115_cookies', '');
GM_setValue('115_last_cookie_refresh', 0);
return false;
}
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,
onload: function(response) {
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 {
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) {
resolve(!response.finalUrl.includes('login.115.com'));
},
onerror: () => resolve(false)
});
});
}
// 离线下载处理流程
async function process115Offline(magnetLink) {
const notificationId = Date.now();
try {
showNotification('115离线', '正在检查登录(不可用)状态...', notificationId);
const isLoggedIn = await check115Login(true);
if (!isLoggedIn) {
throw new Error('请先登录(不可用)115网盘');
}
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 });
}
}
// 提交离线任务
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 } : {})
});
}
// 启动脚本
initializeScript();
})();