// ==UserScript==
// @name PT Medal Monitor
// @namespace http://tampermonkey.net/
// @version 2025-01-08
// @description 监控所有站点的勋章状况
// @author Schalkiii
// @match http*://*/medal*.php*
// @match http*://*/badge*.php*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
// Changelog 2025-01-03:
// 添加、修改了一些站点和match规则
// Changelog 2024-12-29:
// 右上角添加按钮,添加只显示有背景色行功能,即只保留最关键的可领取和未来才可领取的勋章。
// Changelog 2024-12-28:
// 右上角添加按钮,点击才运行。
// 抓取结果展示添加按钮跳转方便领取。
// 可领取勋章背景绿色高亮。
// 未来才开始领取的勋章背景橙色高亮。
// 更新、移除部分网址。
// Changelog 2024-12-27-1:
// 修复手续费抓取。
(function() {
'use strict';
// 定义站点配置
const SITES = [
{ name: '1PTBA', url: 'https://1ptba.com/medal.php' },
{ name: '52PT', url: 'https://52pt.site/medal.php' },
{ name: 'Audiences', url: 'https://audiences.me/medal.php' },
{ name: 'BTSchool', url: 'https://pt.btschool.club/medal.php' },
{ name: 'BYR', url: 'https://byr.pt/medal.php' },
{ name: 'CHDBits', url: 'https://ptchdbits.co/medal.php' },
{ name: 'CMCT', url: 'https://springsunday.net/badges.php' },
{ name: 'CarPt', url: 'https://carpt.net/medal.php' },
{ name: 'DiscFan', url: 'https://discfan.net/medal.php' },
{ name: 'Dragon', url: 'https://www.dragonhd.xyz/medal.php' },
{ name: 'FreeFarm', url: 'https://pt.0ff.cc/medal.php' },
{ name: 'GPW', url: 'https://greatposterwall.com/medal.php' },
{ name: 'HDArea', url: 'https://hdarea.club/medal.php' },
{ name: 'HDAtmos', url: 'https://hdatmos.club/medal.php' },
{ name: 'HDCity', url: 'https://hdcity.city/medal.php' },
{ name: 'HDDolby', url: 'https://www.hddolby.com/medals.php' },
{ name: 'HDHome', url: 'https://hdhome.org/medal.php' },
{ name: 'HDPost', url: 'https://pt.hdpost.top/medal.php' },
{ name: 'HDPt', url: 'https://hdpt.xyz/medal.php' },
{ name: 'HDSky', url: 'https://hdsky.me/medal.php' },
{ name: 'HDSpace', url: 'https://hd-space.org/medal.php' },
{ name: 'HDTime', url: 'https://hdtime.org/medal.php' },
{ name: 'HDU', url: 'https://pt.hdupt.com/medal.php' },
{ name: 'HDVideo', url: 'https://hdvideo.one/medal.php' },
{ name: 'HDfans', url: 'http://hdfans.org/medal.php' },
{ name: 'HITPT', url: 'https://www.hitpt.com/medal.php' },
{ name: 'HUDBT', url: 'https://hudbt.hust.edu.cn/medal.php' },
{ name: 'HaiDan', url: 'https://www.haidan.video/medal.php' },
{ name: 'HaresClub', url: 'https://club.hares.top/medal.php' },
{ name: 'ITZMX', url: 'https://pt.itzmx.com/medal.php' },
{ name: 'JoyHD', url: 'https://www.joyhd.net/medal.php' },
{ name: 'NanYang', url: 'https://nanyangpt.com/medal.php' },
{ name: 'Oshen', url: 'http://www.oshen.win/medal.php' },
{ name: 'OurBits', url: 'https://ourbits.club/medal.php' },
{ name: 'PTT', url: 'https://www.pttime.org/medal.php' },
{ name: 'PThome', url: 'https://www.pthome.net/medal.php' },
{ name: 'PTsbao', url: 'https://ptsbao.club/medal.php' },
{ name: 'SoulVoice', url: 'https://pt.soulvoice.club/medal.php' },
{ name: 'TCCF', url: 'https://et8.org/medal.php' },
{ name: 'TJUPT', url: 'https://www.tjupt.org/medal.php' },
{ name: 'TLFbits', url: 'https://pt.eastgame.org/medal.php' },
{ name: 'TTG', url: 'https://totheglory.im/medal.php' },
{ name: 'WT-Sakura', url: 'https://wintersakura.net/medal.php' },
{ name: 'HHClub', url: 'https://hhanclub.top/medal.php' },
{ name: 'Monika', url: 'https://monikadesign.uk/medal.php' },
{ name: '红叶', url: 'https://leaves.red/medal.php' },
{ name: 'ICC', url: 'https://www.icc2022.com/medal.php' },
{ name: 'CyanBug', url: 'https://cyanbug.net/medal.php' },
{ name: 'ZHUQUE', url: 'https://zhuque.in/medal.php' },
{ name: 'DaJiao', url: 'https://dajiao.cyou/medal.php' },
{ name: '海棠', url: 'https://www.htpt.cc/medal.php' },
{ name: '杏林', url: 'https://xingtan.one/medal.php' },
{ name: 'SRVFI', url: 'https://srvfi.top/medal.php' },
{ name: 'OKPT', url: 'https://www.okpt.net/medal.php' },
{ name: 'GGPT', url: 'https://www.gamegamept.com/medal.php' },
{ name: 'RS', url: 'https://resource.xidian.edu.cn/medal.php' },
{ name: 'Panda', url: 'https://pandapt.net/medal.php' },
{ name: 'KuFei', url: 'https://kufei.org/medal.php' },
{ name: 'RouSi', url: 'https://rousi.zip/medal.php' },
{ name: '悟空', url: 'https://wukongwendao.top/medal.php' },
{ name: 'PTCafe', url: 'https://ptcafe.club/medal.php' },
{ name: 'PTChina', url: 'https://ptchina.org/medal.php' },
{ name: 'GTK', url: 'https://pt.gtkpw.xyz/medal.php' },
{ name: '麒麟', url: 'https://www.hdkyl.in/medal.php' },
{ name: 'AGSV', url: 'https://www.agsvpt.com/medal.php' },
{ name: 'ECUST', url: 'https://pt.ecust.pp.ua/medal.php' },
{ name: 'iloli', url: 'https://share.ilolicon.com/medal.php' },
{ name: 'CrabPt', url: 'https://crabpt.vip/medal.php' },
{ name: 'QingWa', url: 'https://qingwapt.com/medal.php' },
{ name: 'FNP', url: 'https://fearnopeer.com/medal.php' },
{ name: 'YemaPT', url: 'https://www.yemapt.org/medal.php' },
{ name: 'PTFans', url: 'https://ptfans.cc/medal.php' },
{ name: '影', url: 'https://star-space.net/medal.php' },
{ name: 'PTzone', url: 'https://ptzone.xyz/medal.php' },
{ name: '雨', url: 'https://raingfh.top/medal.php' },
{ name: 'PTLGS', url: 'https://ptlgs.org/medal.php' },
//{ name: 'NJTUPT', url: 'https://njtupt.top/medal.php' },
{ name: 'LemonHD', url: 'https://lemonhd.club/medal.php' },
{ name: 'FRDS', url: 'https://pt.keepfrds.com/medal.php' },
{ name: 'U2', url: 'https://u2.dmhy.org/medal.php' },
{ name: 'jpop', url: 'https://jpopsuki.eu/medal.php' },
{ name: 'HONE', url: 'https://hawke.uno/medal.php' },
{ name: 'Ubits', url: 'https://ubits.club/medal.php' },
{ name: 'Piggo', url: 'https://piggo.me/medal.php' },
{ name: 'KamePT', url: 'https://kamept.com/medal.php' }
];
const usupportUrls = [
{ name: 'PTer', url: 'https://pterclub.com/medal.php' },
{ name: 'ZMPT', url: 'https://zmpt.cc/medal.php' },
{ name: '象岛', url: 'https://ptvicomo.net/medal.php' }
];
// Medal.php 404的站点,可能是站点没有勋章功能
const errorUrls = [
"https://52pt.site/medal.php",
"https://audiences.me/medal.php",
"https://pt.btschool.club/medal.php",
"https://ptchdbits.co/medal.php",
"https://www.dragonhd.xyz/medal.php",
"https://hdarea.club/medal.php",
"https://hdcity.city/medal.php",
"https://hdhome.org/medal.php",
"https://pt.hdpost.top/medal.php",
"https://hdsky.me/medal.php",
"https://hd-space.org/medal.php",
"https://pt.hdupt.com/medal.php",
"https://www.hitpt.com/medal.php",
"https://hudbt.hust.edu.cn/medal.php",
"https://www.haidan.video/medal.php",
"https://club.hares.top/medal.php",
"https://pt.itzmx.com/medal.php",
"https://www.joyhd.net/medal.php",
"https://nanyangpt.com/medal.php",
"https://ourbits.club/medal.php",
"https://www.pthome.net/medal.php",
"https://ptsbao.club/medal.php",
"https://et8.org/medal.php",
"https://totheglory.im/medal.php",
"https://monikadesign.uk/medal.php",
"https://www.htpt.cc/medal.php",
"https://fearnopeer.com/medal.php",
"https://star-space.net/medal.php",
"https://lemonhd.club/medal.php",
"https://pt.keepfrds.com/medal.php",
"https://u2.dmhy.org/medal.php",
"https://jpopsuki.eu/medal.php",
"https://pt.eastgame.org/medal.php",
"https://www.tjupt.org/medal.php",
"https://hdvideo.one/medal.php",
"https://byr.pt/medal.php",
"https://resource.xidian.edu.cn/medal.php",
"https://hawke.uno/medal.php"
];
const newSITES = SITES.filter(site => {
return!errorUrls.includes(site.url);
});
//console.log(newSITES);
// 在页面上展示表格
function displayTableOnPage(medals, siteName, siteUrl) {
// 创建表格容器
const container = document.createElement('div');
container.style.margin = '20px';
container.style.padding = '20px';
container.style.border = '1px solid #ccc';
container.style.borderRadius = '5px';
container.style.backgroundColor = '#f9f9f9';
// 创建站点标题
const siteHeader = document.createElement('h2');
siteHeader.textContent = `${siteName} (一共 ${medals.length} 个勋章)`;
// 创建链接
const link = document.createElement('a');
link.href = siteUrl; // 设置链接的 URL
link.textContent = '访问站点'; // 链接显示的文本
link.style.color = '#007bff'; // 设置链接颜色
link.style.textDecoration = 'none'; // 去掉下划线
link.style.marginLeft = '10px'; // 添加左边距
link.style.fontWeight = 'bold'; // 加粗字体
// 添加点击事件,确保链接在新标签页打开
link.addEventListener('click', function (event) {
event.preventDefault(); // 阻止默认行为
window.open(siteUrl, '_blank'); // 在新标签页打开链接
});
// 将链接添加到标题中
siteHeader.appendChild(link);
// 将标题添加到容器中
container.appendChild(siteHeader);
// 创建表格
const table = document.createElement('table');
table.style.width = '100%';
table.style.borderCollapse = 'collapse';
table.style.marginBottom = '20px';
// 创建表头
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
const headers = ['图片', '名称', '描述', '可购买时间', '有效期', '奖励倍数', '价格', '库存', '可购买', '手续费'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
th.style.border = '1px solid #ddd';
th.style.padding = '8px';
th.style.backgroundColor = '#f2f2f2';
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// 创建表格内容
const tbody = document.createElement('tbody');
medals.forEach(medal => {
const row = document.createElement('tr');
// 如果勋章可购买,则高亮显示该行
if (medal.canBuy) {
row.style.backgroundColor = '#e6ffe6'; // 使用浅绿色背景高亮
row.style.fontWeight = 'bold'; // 加粗字体
}
// 检查可购买时间的开始点是否在未来
const availableTime = medal.availableTime;
if (availableTime && availableTime.trim() !== '不限 ~ 不限') {
const startTimeStr = availableTime.split('~')[0].trim(); // 获取开始时间部分
if (startTimeStr !== '不限') {
const startTime = new Date(startTimeStr); // 将字符串转换为日期对象
const now = new Date(); // 获取当前时间
if (startTime > now) { // 如果开始时间在未来
row.style.backgroundColor = '#ffe6cc'; // 使用橙色背景高亮
row.style.fontWeight = 'bold'; // 加粗字体
}
}
}
const columns = [
`<img src="${medal.image}" width="50" height="50">`,
medal.name,
medal.description,
medal.availableTime,
medal.validity,
medal.bonusMultiplier,
medal.price,
medal.stock,
medal.canBuy ? '是' : '否',
medal.giftFee
];
columns.forEach(columnText => {
const td = document.createElement('td');
td.innerHTML = columnText;
td.style.border = '1px solid #ddd';
td.style.padding = '8px';
row.appendChild(td);
});
tbody.appendChild(row);
});
table.appendChild(tbody);
// 将表格添加到容器中
container.appendChild(table);
// 将容器添加到页面中
document.body.appendChild(container);
}
// 从HTML字符串中提取勋章数据
function extractMedalsFromHtml(html, siteName, siteUrl) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const medals = [];
// 查找包含勋章数据的表格
const tables = doc.querySelectorAll('table');
let medalTable = null;
// 查找包含正确列头的表格
for (const table of tables) {
const headers = table.querySelectorAll('.colhead');
if (headers.length >= 7) {
// 检查是否包含简体或繁体的“购买”或“价格”字样
const hasBuyOrPriceInHeader = Array.from(headers).some(header => {
const text = header.textContent;
return (text.includes('购买') || text.includes('購買') || text.includes('价格') || text.includes('價格'));
});
if (hasBuyOrPriceInHeader) {
medalTable = table;
break;
}
}
}
if (!medalTable) {
console.error(`[${siteName}] 未找到勋章表格`);
return medals;
}
// 处理勋章表格中的行
medalTable.querySelectorAll('tbody > tr').forEach(row => {
// Skip header row
if (!row.querySelector('.colhead')) {
// 确保行中有图片和数据
const imgElement = row.querySelector('td:nth-child(1) img');
const nameElement = row.querySelector('td:nth-child(2) h1');
if (imgElement && nameElement) {
const medal = {
site: siteName,
image: imgElement.src || '',
name: nameElement.textContent.trim() || '',
description: row.querySelector('td:nth-child(2)')?.textContent.trim().replace(nameElement.textContent, '').trim() || '',
availableTime: row.querySelector('td:nth-child(3)')?.textContent.trim().replace(/\s+/g, ' ') || '',
validity: row.querySelector('td:nth-child(4)')?.textContent.trim() || '',
bonusMultiplier: row.querySelector('td:nth-child(5)')?.textContent.trim() || '',
price: parseInt(row.querySelector('td:nth-child(6)')?.textContent.replace(/,/g, ''), 10) || 0,
stock: row.querySelector('td:nth-child(7)')?.textContent.trim() || '',
canBuy:!row.querySelector('td:nth-child(8) input')?.disabled,
giftFee: row.querySelector('td:nth-child(9) span.nowrap')?.textContent.replace('手續費: ', '').replace('手续费: ', '') || ''
};
medals.push(medal);
}
}
});
console.group(`${siteName} (一共 ${medals.length} 个勋章)`);
console.table(medals);
console.groupEnd();
// 在页面上展示表格
displayTableOnPage(medals,siteName,siteUrl);
return medals;
}
// 从单个站点抓取勋章
async function scrapeSite(site) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: site.url,
headers: {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
},
onload: function (response) {
if (response.status === 200) {
const medals = extractMedalsFromHtml(response.responseText, site.name, site.url);
resolve(medals);
} else {
console.log(response);
reject(new Error(`HTTP error! status: ${response.status}`));
}
},
onerror: function (error) {
console.error(`[${site.name}] 抓取失败:`, error);
reject(error);
}
});
});
}
// 抓取所有站点的勋章
async function scrapeAllSites() {
try {
// 并行请求所有站点
const allMedals = await Promise.allSettled(
newSITES.map(site => scrapeSite(site))
);
// 合并所有结果
const flatMedals = allMedals.flat();
// 按站点分组显示结果
const groupedMedals = {};
flatMedals.forEach(medal => {
if (!groupedMedals[medal.site]) {
groupedMedals[medal.site] = [];
}
groupedMedals[medal.site].push(medal);
});
// 打印结果
for (const [site, medals] of Object.entries(groupedMedals)) {
console.group(`${site} (共有 ${medals.length} 个勋章)`);
console.table(medals);
console.groupEnd();
}
return flatMedals;
} catch (error) {
console.error('抓取过程出错:', error);
throw error;
}
}
window.addEventListener('load', function () {
// 创建按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.style.position = 'fixed'; // 固定在页面显眼位置
buttonContainer.style.top = '20px';
buttonContainer.style.right = '20px';
buttonContainer.style.display = 'flex'; // 使用 Flex 布局
buttonContainer.style.gap = '10px'; // 按钮之间的间距
buttonContainer.style.zIndex = '1000'; // 确保按钮在最上层
// 创建“开始抓取勋章”按钮
const scrapeButton = document.createElement('button');
scrapeButton.textContent = '开始抓取勋章';
scrapeButton.style.padding = '10px 20px';
scrapeButton.style.backgroundColor = '#007bff';
scrapeButton.style.color = '#fff';
scrapeButton.style.border = 'none';
scrapeButton.style.borderRadius = '5px';
scrapeButton.style.cursor = 'pointer';
// 创建“隐藏无背景色行”按钮
const hideButton = document.createElement('button');
hideButton.textContent = '隐藏无背景色行';
hideButton.style.padding = '10px 20px';
hideButton.style.backgroundColor = '#28a745'; // 绿色背景
hideButton.style.color = '#fff';
hideButton.style.border = 'none';
hideButton.style.borderRadius = '5px';
hideButton.style.cursor = 'pointer';
// 将按钮添加到按钮容器中
buttonContainer.appendChild(scrapeButton);
buttonContainer.appendChild(hideButton);
// 将按钮容器添加到页面中
document.body.appendChild(buttonContainer);
// “开始抓取勋章”按钮点击事件
scrapeButton.addEventListener('click', function () {
scrapeButton.textContent = '抓取中...'; // 点击后改变按钮文字
scrapeButton.disabled = true; // 禁用按钮,防止重复点击
scrapeAllSites()
.then(medals => {
console.log('所有站点抓取完成,总共抓取到', medals.length, '个勋章');
scrapeButton.textContent = '抓取完成'; // 抓取完成后更新按钮文字
})
.catch(error => {
console.error('抓取过程出错:', error);
scrapeButton.textContent = '抓取出错,重试'; // 出错后更新按钮文字
scrapeButton.disabled = false; // 允许用户重试
});
});
// “隐藏无背景色行”按钮点击事件
hideButton.addEventListener('click', function () {
const tables = document.querySelectorAll('table'); // 获取页面中的所有表格
if (tables.length === 0) {
console.error('未找到表格');
return;
}
let isHidden = hideButton.textContent === '显示所有行'; // 当前是否为隐藏状态
tables.forEach(table => {
const rows = table.querySelectorAll('tbody tr'); // 获取当前表格的所有行
rows.forEach(row => {
const backgroundColor = row.style.backgroundColor;
// 如果没有背景色或背景色为默认值
if (!backgroundColor || backgroundColor === '' || backgroundColor === 'transparent') {
row.style.display = isHidden ? '' : 'none'; // 切换隐藏/显示
}
});
});
// 切换按钮文字
hideButton.textContent = isHidden ? '隐藏无背景色行' : '显示所有行';
});
});
})();