Extract magnet links from sukebei.nyaa.si with filtering, (de)select-all and single-click init
// ==UserScript==
// @name NYAA Magnet Extractor with Filter
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Extract magnet links from sukebei.nyaa.si with filtering, (de)select-all and single-click init
// @author sauterne
// @match https://sukebei.nyaa.si/*
// @match https://nyaa.si/*
// @grant GM_addStyle
// @grant GM_setClipboard
// @license GPL-3.0-or-later
// ==/UserScript==
(function () {
'use strict';
/* ---------- Styles ---------- */
GM_addStyle(`
#magnetHelperPanel {
background:#333;
border:1px solid #555;
border-radius:8px;
padding:15px;
margin-bottom:12px;
color:#eee;
font-size:14px;
}
#magnetHelperPanel input[type="text"],
#magnetHelperPanel textarea{
width:100%;
padding:6px 8px;
margin:6px 0 10px;
border:1px solid #666;
border-radius:4px;
background:#444;
color:#eee;
}
#magnetHelperPanel button{
padding:6px 12px;
margin-right:8px;
border:none;
border-radius:4px;
cursor:pointer;
background:#007bff;
color:#fff;
}
#magnetHelperPanel button:hover{ background:#0056b3; }
.magnet-helper-highlight{ background:#556B2F !important; }
.magnet-helper-checkbox{ margin-right:6px; vertical-align:middle; }
#magnetHelperTriggerBtn{
position:fixed;
bottom:20px; right:20px;
z-index:9999;
padding:10px 15px;
background:#28a745;
color:#fff;
border:none;
border-radius:5px;
cursor:pointer;
box-shadow:0 2px 5px rgba(0,0,0,0.3);
}
#magnetHelperTriggerBtn:hover{ background:#218838; }
#magnetHelperPanel .row{margin-bottom:8px;}
#magnetHelperPanel label{margin-left:4px;}
`);
/* ---------- Globals ---------- */
let checkboxesAdded = false;
let panelAdded = false;
let torrentRows = [];
/* ---------- Elements ---------- */
const triggerBtn = document.createElement('button');
triggerBtn.id = 'magnetHelperTriggerBtn';
triggerBtn.textContent = 'Filter & Extract';
document.body.appendChild(triggerBtn);
let panel, searchInput, searchBtn, extractBtn, copyBtn,
selectAllBox, resultsArea;
/* ---------- Helper functions ---------- */
function addCheckboxes() {
if (checkboxesAdded) return;
const tableBody = document.querySelector('.torrent-list > tbody');
if (!tableBody) return;
torrentRows = Array.from(tableBody.querySelectorAll('tr'));
torrentRows.forEach(row => {
// 跳过已加过的行
if (row.querySelector('.magnet-helper-checkbox')) return;
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'magnet-helper-checkbox';
const firstTd = row.querySelector('td');
firstTd && firstTd.prepend(cb);
});
checkboxesAdded = true;
}
function buildPanel() {
if (panelAdded) return;
/* --- 容器插入在表格上方 --- */
panel = document.createElement('div');
panel.id = 'magnetHelperPanel';
panel.innerHTML = `
<div class="row">
<input type="checkbox" id="magnetSelectAll"><label for="magnetSelectAll">Select All</label>
</div>
<div class="row">
<input type="text" id="magnetSearchInput" placeholder="Enter keyword (e.g., Deadmau)">
<button id="magnetSearchBtn">Search & Auto-Check</button>
</div>
<div class="row">
<button id="magnetExtractBtn">Extract Selected</button>
<button id="magnetCopyBtn" style="display:none;">Copy All</button>
</div>
<textarea id="magnetResultTextarea" readonly placeholder="Extracted magnet links will appear here..."></textarea>
`;
/* 把面板插在 torrent 列表之前 */
const table = document.querySelector('.torrent-list');
table.parentNode.insertBefore(panel, table);
/* 缓存内部节点 */
searchInput = panel.querySelector('#magnetSearchInput');
searchBtn = panel.querySelector('#magnetSearchBtn');
extractBtn = panel.querySelector('#magnetExtractBtn');
copyBtn = panel.querySelector('#magnetCopyBtn');
resultsArea = panel.querySelector('#magnetResultTextarea');
selectAllBox = panel.querySelector('#magnetSelectAll');
/* --- 事件绑定 --- */
searchBtn.addEventListener('click', handleSearch);
searchInput.addEventListener('keypress', e => { if (e.key === 'Enter') handleSearch(); });
extractBtn.addEventListener('click', handleExtract);
copyBtn.addEventListener('click', handleCopy);
selectAllBox.addEventListener('change', toggleSelectAll);
panelAdded = true;
}
function toggleSelectAll() {
const checked = selectAllBox.checked;
torrentRows.forEach(r => {
const cb = r.querySelector('.magnet-helper-checkbox');
if (cb) cb.checked = checked;
r.classList.toggle('magnet-helper-highlight', checked);
});
}
function handleSearch() {
const term = searchInput.value.toLowerCase().trim();
torrentRows.forEach(r => {
const cb = r.querySelector('.magnet-helper-checkbox');
if (!cb) return;
/* 获取标题文本 */
let nameCell = r.querySelector('td[colspan="2"]') || r.querySelectorAll('td')[1];
const txt = (nameCell?.innerText || '').toLowerCase();
const hit = term && txt.includes(term);
cb.checked = hit;
r.classList.toggle('magnet-helper-highlight', hit);
});
}
function handleExtract() {
const links = [];
torrentRows.forEach(r => {
const cb = r.querySelector('.magnet-helper-checkbox');
if (cb && cb.checked) {
const a = r.querySelector('a[href^="magnet:"]');
a && links.push(a.href);
}
});
resultsArea.value = links.join('\n');
copyBtn.style.display = links.length ? 'inline-block' : 'none';
alert(links.length ? `Extracted ${links.length} links.` : 'No links selected.');
}
function handleCopy() {
GM_setClipboard(resultsArea.value);
alert('Copied to clipboard!');
}
/* ---------- Trigger button ---------- */
triggerBtn.addEventListener('click', () => {
/* 第一次点:先加复选框 + 面板;之后只切换显示状态 */
if (!checkboxesAdded) addCheckboxes();
if (!panelAdded) buildPanel();
/* 刷新行 cache(如翻页后) */
const body = document.querySelector('.torrent-list > tbody');
if (body) torrentRows = Array.from(body.querySelectorAll('tr'));
/* toggle 显示 */
panel.style.display = (panel.style.display === 'none' || !panel.style.display) ? 'block' : 'none';
});
})();