// ==UserScript==
// @name pt站选种插件
// @namespace http://tampermonkey.net/
// @version 1.10
// @description 在表格第一行插入一个按钮并通过右键弹出下拉框实现全选、取消全选和复制URL的功能,左键直接复制所有选中的URL
// @author joshua2117
// @match https://audiences.me/*
// @match https://hhanclub.top/*
// @match https://ptchdbits.co/*
// @match https://next.m-team.cc/*
// @match https://zmpt.cc/*
// @match https://sewerpt.com/*
// @match https://springsunday.net/*
// @match https://www.hddolby.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 站点配置
const siteConfigs = {
观众: {
rowSelector: 'table.torrents-table > tbody > tr',
urlFetcher: async function(row) {
const downloadImg = row.querySelector('.torrentname table tbody tr:first-child td:nth-child(2) img.download');
if (downloadImg) {
const aTag = downloadImg.closest('a');
if (aTag) {
const idMatch = aTag.href.match(/id=(\d+)/);
if (idMatch && idMatch[1]) {
const id = idMatch[1];
try {
const response = await fetch(`https://audiences.me/details.php?id=${id}`);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const bTag = doc.getElementById('torrent_dl_url');
if (bTag) {
const dlUrlATag = bTag.querySelector('a');
if (dlUrlATag) {
return dlUrlATag.href;
} else {
console.log('A 标签未找到在 torrent_dl_url b 标签内,ID:', id);
}
} else {
console.log('torrent_dl_url b 标签未找到,ID:', id);
}
} catch (err) {
console.error('获取详情页失败,ID:', id, err);
}
} else {
console.log('ID 未找到在 href 中:', aTag.href);
}
} else {
console.log('下载链接 A 标签未找到,在行:', row);
}
} else {
console.log('下载图片未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return rows[0];
},
skipFirstRowForCheckboxes: true,
checkIsFree: function(row){
var free = row.querySelector('.pro_free')
if(free){
if(free.alt==='Free'){
return true;
}
}
return false;
},
excludeHrItem: function(row){
return false;
}
},
憨憨: {
rowSelector: '.torrent-table-sub-info',
urlFetcher: async function(row) {
const manageDiv = row.querySelector('div.torrent-manage'); // 获取当前行下的 class 为 torrent-manage 的 div 标签
if (manageDiv) {
const downloadLink = manageDiv.querySelectorAll('a')[1];
if (downloadLink) {
return downloadLink.href;
} else {
console.log('下载链接未找到,在行:', row);
}
} else {
console.log('torrent-manage div 未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
const xpath = '//*[@id="mainContent"]/div/div[2]/div[1]/div';
const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
const element = result.singleNodeValue;
return element;
},
skipFirstRowForCheckboxes: false,
checkIsFree: function(row){
return false;
},
excludeHrItem: function(row){
return false;
}
},
彩虹岛: {
rowSelector: '.torrents > tbody > tr',
urlFetcher: async function(row) {
const downloadImg = row.querySelector('.torrentname > tbody > tr:first-child > td:nth-child(2) > a');
if (downloadImg) {
const aTag = downloadImg.closest('a');
if (aTag) {
const idMatch = aTag.href.match(/id=(\d+)/);
if (idMatch && idMatch[1]) {
const id = idMatch[1];
try {
const response = await fetch(`https://ptchdbits.co/details.php?id=${id}`);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const bTag = doc.getElementById('outer');
if (bTag) {
const dlUrlATag = bTag.querySelectorAll('.details >tbody >tr > .rowfollow >a')[4]
if (dlUrlATag) {
return dlUrlATag.href;
} else {
console.log('A 标签未找到在 torrent_dl_url b 标签内,ID:', id);
}
} else {
console.log('torrent_dl_url b 标签未找到,ID:', id);
}
} catch (err) {
console.error('获取详情页失败,ID:', id, err);
}
} else {
console.log('ID 未找到在 href 中:', aTag.href);
}
} else {
console.log('下载链接 A 标签未找到,在行:', row);
}
} else {
console.log('下载图片未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return rows[0];
},
skipFirstRowForCheckboxes: true,
checkIsFree: function(row){
return false;
},
excludeHrItem: function(row){
return false;
}
},
馒头: {
isDelay: true,
rowSelector: '.ant-spin-container > div > table > tbody > tr',
urlFetcher: async function(row) {
const downloadImg = row.querySelector('td:nth-child(2) > div > div:nth-child(2) > div');
if (downloadImg) {
const aTag = downloadImg.querySelector('td:nth-child(2) > div > div:nth-child(2) > div >a');
if (aTag) {
const idMatch = aTag.href.match(/(\d+)/);
if (idMatch && idMatch[1]) {
const id = idMatch[1];
try {
var apiUrls = (() => {
let urls = [];
try {
urls = _APIHOSTS.map((u) => new URL(u));
} catch (e) {
console.warn("get _APIHOSTS error:", e);
}
urls.push(new URL(location.origin + "/api"));
return urls;
})();
const apiUrl = localStorage.getItem("apiHost") || apiUrls[Math.random() * apiUrls.length | 0].href;
const f = new FormData();
f.set("id", id);
const opts ={
method: "POST",
headers: {
authorization: localStorage.getItem("auth"),
visitorId: localStorage.getItem("visitorId"),
did: localStorage.getItem("did"),
ts: Math.floor(Date.now() / 1e3)
}
}
opts.body = f;
const response = await fetch(apiUrl + "/torrent/genDlToken",opts);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const text = await response.json();
console.log('接口返回数据:',text)
return text.data
} catch (err) {
console.error('获取详情页失败,ID:', id, err);
}
} else {
console.log('ID 未找到在 href 中:', aTag.href);
}
} else {
console.log('下载链接 A 标签未找到,在行:', row);
}
}else {
console.log('下载图片未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('td');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return document.querySelector('.ant-spin-container > div > table > thead > tr');
},
skipFirstRowForCheckboxes: false,
checkIsFree: function(row){
var free = row.querySelector('.uppercase')
if(free){
if(free.textContent==='Free'){
return true;
}
}
return false;
},
excludeHrItem: function(row){
return false;
}
},
织梦: {
rowSelector: '.torrents > tbody > tr',
urlFetcher: async function(row) {
const downloadImg = row.querySelector('td:nth-child(3) img.download');
if (downloadImg) {
const aTag = downloadImg.closest('a');
if (aTag) {
const idMatch = aTag.href.match(/id=(\d+)/);
if (idMatch && idMatch[1]) {
const id = idMatch[1];
try {
const response = await fetch(`https://zmpt.cc/details.php?id=${id}`);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const bTag = doc.getElementById('content');
if (bTag) {
console.log(bTag);
return bTag.textContent.trim();
} else {
console.log('torrent_dl_url b 标签未找到,ID:', id);
}
} catch (err) {
console.error('获取详情页失败,ID:', id, err);
}
} else {
console.log('ID 未找到在 href 中:', aTag.href);
}
} else {
console.log('下载链接 A 标签未找到,在行:', row);
}
} else {
console.log('下载图片未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return rows[0];
},
skipFirstRowForCheckboxes: true,
checkIsFree: async function(row){
return false;
},
excludeHrItem: function(row){
return false;
}
},
下水道: {
rowSelector: '.torrents > tbody > tr',
urlFetcher: async function(row) {
const downloadImg = row.querySelector('td:nth-child(3) img.download');
if (downloadImg) {
const aTag = downloadImg.closest('a');
if (aTag) {
const idMatch = aTag.href.match(/id=(\d+)/);
if (idMatch && idMatch[1]) {
const id = idMatch[1];
try {
const response = await fetch(`https://sewerpt.com/details.php?id=${id}`);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const bTag = doc.getElementById('content');
if (bTag) {
console.log(bTag);
return bTag.textContent.trim();
} else {
console.log('torrent_dl_url b 标签未找到,ID:', id);
}
} catch (err) {
console.error('获取详情页失败,ID:', id, err);
}
} else {
console.log('ID 未找到在 href 中:', aTag.href);
}
} else {
console.log('下载链接 A 标签未找到,在行:', row);
}
} else {
console.log('下载图片未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return rows[0];
},
skipFirstRowForCheckboxes: true,
checkIsFree: async function(row){
return false;
},
excludeHrItem: function(row){
return false;
}
},
高清杜比: {
rowSelector: '.torrents > tbody > tr',
urlFetcher: async function(row) {
const downloadImg = row.querySelector('td:nth-child(3) img.download');
if (downloadImg) {
const aTag = downloadImg.closest('a');
if (aTag) {
const idMatch = aTag.href.match(/id=(\d+)/);
if (idMatch && idMatch[1]) {
const id = idMatch[1];
try {
const response = await fetch(`https://www.hddolby.com/details.php?id=${id}`);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const bTag = doc.getElementById('content');
if (bTag) {
console.log(bTag);
return bTag.textContent.trim();
} else {
console.log('torrent_dl_url b 标签未找到,ID:', id);
}
} catch (err) {
console.error('获取详情页失败,ID:', id, err);
}
} else {
console.log('ID 未找到在 href 中:', aTag.href);
}
} else {
console.log('下载链接 A 标签未找到,在行:', row);
}
} else {
console.log('下载图片未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return rows[0];
},
skipFirstRowForCheckboxes: true,
checkIsFree: function(row){
return false;
},
excludeHrItem: function(row){
return false;
}
},
不可说: {
rowSelector: '.torrents > tbody > tr',
urlFetcher: async function(row) {
const manageDiv = row.querySelector('.torrentname >tbody >tr >td:nth-child(2) >div >a');
if (manageDiv) {
return manageDiv.href;
} else {
console.log('torrent-manage div 未找到,在行:', row);
}
return null;
},
insertCheckbox: function(row) {
const checkboxCell = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkboxCell.appendChild(checkbox);
row.insertBefore(checkboxCell, row.firstElementChild);
},
getHeaderRow: function(rows) {
return rows[0];
},
skipFirstRowForCheckboxes: true,
checkIsFree: function(row){
return false;
},
excludeHrItem: function(row){
return false;
}
}
};
// 获取当前站点配置
function getCurrentSiteConfig() {
const currentUrl = window.location.href;
if (currentUrl.includes('audiences.me')) {
return siteConfigs['观众'];
} else if (currentUrl.includes('hhanclub.top')) {
return siteConfigs['憨憨'];
} else if (currentUrl.includes('ptchdbits.co')) {
return siteConfigs['彩虹岛'];
}else if (currentUrl.includes('next.m-team.cc')) {
return siteConfigs['馒头'];
}else if (currentUrl.includes('zmpt.cc')) {
return siteConfigs['织梦'];
}else if (currentUrl.includes('sewerpt.com')) {
return siteConfigs['下水道'];
}else if (currentUrl.includes('springsunday.net')) {
return siteConfigs['不可说'];
}else if (currentUrl.includes('www.hddolby.com')) {
return siteConfigs['高清杜比'];
}
return null;
}
// 获取表格行
function getTableRows(config) {
return document.querySelectorAll(config.rowSelector);
}
// 创建自定义弹窗
function createCustomAlert(message) {
const alertBox = document.createElement('div');
alertBox.style.position = 'fixed';
alertBox.style.top = '50%';
alertBox.style.left = '50%';
alertBox.style.transform = 'translate(-50%, -50%)';
alertBox.style.backgroundColor = 'white';
alertBox.style.border = '1px solid #ccc';
alertBox.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
alertBox.style.padding = '20px';
alertBox.style.zIndex = '9999';
alertBox.style.fontSize = '16px';
alertBox.style.fontFamily = 'Arial, sans-serif';
alertBox.style.color = '#333';
alertBox.style.borderRadius = '5px';
const messageText = document.createElement('span');
messageText.textContent = message;
const closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.marginLeft = '10px';
closeButton.style.padding = '5px 10px';
closeButton.style.border = 'none';
closeButton.style.borderRadius = '3px';
closeButton.style.cursor = 'pointer';
closeButton.style.backgroundColor = '#007bff';
closeButton.style.color = 'white';
closeButton.style.transition = 'background-color 0.3s ease';
closeButton.addEventListener('mouseover', () => {
closeButton.style.backgroundColor = '#0056b3';
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.backgroundColor = '#007bff';
});
closeButton.addEventListener('click', () => {
document.body.removeChild(alertBox);
});
alertBox.appendChild(messageText);
alertBox.appendChild(closeButton);
document.body.appendChild(alertBox);
setTimeout(() => {
document.body.removeChild(alertBox);
}, 3000); // 自动关闭弹窗,3秒后消失
}
// 初始化功能
function initFeatures() {
const config = getCurrentSiteConfig();
if (!config) {
console.log('不支持的站点');
return;
}
const rows = getTableRows(config);
if (rows.length > 0) {
// 创建一个按钮
const headerRow = config.getHeaderRow(rows);
if (headerRow) {
const selectCell = document.createElement('th');
selectCell.style.width = '30px';
const dropdownButton = document.createElement('button');
dropdownButton.textContent = '操作';
dropdownButton.style.padding = '5px 10px';
dropdownButton.style.border = 'none';
dropdownButton.style.borderRadius = '3px';
dropdownButton.style.cursor = 'pointer';
dropdownButton.style.backgroundColor = '#007bff';
dropdownButton.style.color = 'white';
dropdownButton.style.transition = 'background-color 0.3s ease';
dropdownButton.addEventListener('mouseover', () => {
dropdownButton.style.backgroundColor = '#0056b3';
});
dropdownButton.addEventListener('mouseout', () => {
dropdownButton.style.backgroundColor = '#007bff';
});
// 创建下拉菜单
const dropdownMenu = document.createElement('div');
dropdownMenu.style.position = 'fixed'; // 使用 fixed 定位
dropdownMenu.style.display = 'none';
dropdownMenu.style.backgroundColor = 'white';
dropdownMenu.style.border = '1px solid #ccc';
dropdownMenu.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
dropdownMenu.style.zIndex = '9999'; // 设置更高的 z-index 确保在最顶层
const selectAllItem = document.createElement('div');
selectAllItem.textContent = '全选';
selectAllItem.style.padding = '5px 10px';
selectAllItem.style.cursor = 'pointer';
selectAllItem.addEventListener('click', () => {
rows.forEach((row, index) => {
if (index !== 0 || !config.skipFirstRowForCheckboxes) { // 根据配置决定是否跳过第一行
const checkbox = row.querySelector('input[type="checkbox"]');
if (checkbox) {
checkbox.checked = true;
}
}
});
dropdownMenu.style.display = 'none';
});
const deselectAllItem = document.createElement('div');
deselectAllItem.textContent = '取消全选';
deselectAllItem.style.padding = '5px 10px';
deselectAllItem.style.cursor = 'pointer';
deselectAllItem.addEventListener('click', () => {
rows.forEach((row, index) => {
if (index !== 0 || !config.skipFirstRowForCheckboxes) { // 根据配置决定是否跳过第一行
const checkbox = row.querySelector('input[type="checkbox"]');
if (checkbox) {
checkbox.checked = false;
}
}
});
dropdownMenu.style.display = 'none';
});
const selectFreeAllItem = document.createElement('div');
selectFreeAllItem.textContent = '选中free';
selectFreeAllItem.style.padding = '5px 10px';
selectFreeAllItem.style.cursor = 'pointer';
selectFreeAllItem.addEventListener('click', () => {
rows.forEach((row, index) => {
if (index !== 0 || !config.skipFirstRowForCheckboxes) { // 根据配置决定是否跳过第一行
const checkbox = row.querySelector('input[type="checkbox"]');
if (checkbox && config.checkIsFree(row)) {
checkbox.checked = true
}
}
});
dropdownMenu.style.display = 'none';
});
const excludeHrItem = document.createElement('div');
excludeHrItem.textContent = '排除hr(未实现)';
excludeHrItem.style.padding = '5px 10px';
excludeHrItem.style.cursor = 'pointer';
excludeHrItem.addEventListener('click', () => {
rows.forEach((row, index) => {
if (index !== 0 || !config.skipFirstRowForCheckboxes) { // 根据配置决定是否跳过第一行
// const checkbox = row.querySelector('input[type="checkbox"]');
// if (checkbox && config.excludeHrItem(row)) {
// checkbox.checked = true
// }
}
});
dropdownMenu.style.display = 'none';
});
const copyButtonItem = document.createElement('div');
copyButtonItem.textContent = '复制URL';
copyButtonItem.style.padding = '5px 10px';
copyButtonItem.style.cursor = 'pointer';
copyButtonItem.addEventListener('click', async () => {
await performCopyUrls();
dropdownMenu.style.display = 'none';
});
dropdownMenu.appendChild(selectAllItem);
dropdownMenu.appendChild(deselectAllItem);
dropdownMenu.appendChild(copyButtonItem);
dropdownMenu.appendChild(selectFreeAllItem);
//dropdownMenu.appendChild(excludeHrItem);
// 将按钮和下拉菜单添加到表头第一列
selectCell.appendChild(dropdownButton);
selectCell.appendChild(dropdownMenu);
headerRow.insertBefore(selectCell, headerRow.firstElementChild);
// 显示和隐藏下拉菜单
dropdownButton.addEventListener('contextmenu', (e) => {
e.preventDefault();
showDropdownMenu(e);
});
document.addEventListener('click', (e) => {
if (!dropdownMenu.contains(e.target)) {
dropdownMenu.style.display = 'none';
}
});
// 左键点击按钮直接复制所有选中的URL
dropdownButton.addEventListener('click', async (e) => {
if (e.button === 0) { // 左键点击
await performCopyUrls();
}
});
// 复制URL的函数
async function performCopyUrls() {
const checkedRows = Array.from(rows).filter((row, index) => {
if (index === 0 && config.skipFirstRowForCheckboxes) {
return false; // 跳过第一行
}
const checkbox = row.querySelector('input[type="checkbox"]');
return checkbox && checkbox.checked;
});
let urls = [];
let processedCount = 0;
// 禁用按钮并更改文本和样式
dropdownButton.textContent = `正在复制... (${processedCount}/${checkedRows.length})`;
dropdownButton.style.backgroundColor = '#cccccc';
dropdownButton.style.pointerEvents = 'none'; // 禁用点击事件
for (const [index, row] of checkedRows.entries()) {
const url = await config.urlFetcher(row);
if (url) {
urls.push(url);
}
processedCount++;
dropdownButton.textContent = `正在复制... (${processedCount}/${checkedRows.length})`;
}
if (urls.length > 0) {
navigator.clipboard.writeText(urls.join('\n'))
.then(() => {
console.log('URL 已复制到剪贴板:', urls.join('\n'));
createCustomAlert('已复制完成');
})
.catch(err => {
console.error('复制 URL 失败:', err);
});
} else {
console.log('没有选择要复制的 URL。');
}
// 恢复按钮的状态
dropdownButton.textContent = '操作';
dropdownButton.style.backgroundColor = '#007bff';
dropdownButton.style.pointerEvents = 'auto'; // 启用点击事件
}
// 显示下拉菜单的函数
function showDropdownMenu(event) {
const rect = event.target.getBoundingClientRect();
dropdownMenu.style.left = `${rect.right}px`; // 出现在按钮右侧
dropdownMenu.style.top = `${rect.bottom}px`; // 出现在按钮下方
dropdownMenu.style.display = 'block';
}
}
// 在每个<tr>元素的第一<td>前插入复选框(根据配置决定是否跳过第一行)
rows.forEach((row, index) => {
if (!(index === 0 && config.skipFirstRowForCheckboxes)) { // 根据配置决定是否跳过第一行
config.insertCheckbox(row);
}
});
} else {
console.log('表格未找到。');
}
}
function waitForElement(selector, callback) {
const observer = new MutationObserver((mutations) => {
mutations.forEach(() => {
const element = document.querySelector(selector);
if (element) {
observer.disconnect();
callback(element);
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
const siteconfig = getCurrentSiteConfig();
let isInit = false;
if(siteconfig.isDelay){
waitForElement(siteconfig.rowSelector, function (el) {
if(!isInit){
console.log('表格元素已加载:', el);
// 初始化功能
setTimeout(initFeatures, 1000); // 可选延迟
isInit = true;
}
});
}else{
// 初始化功能
initFeatures();
}
})();