您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
中国私人追踪器官方招募路线
当前为
// ==UserScript== // @name 中文PT站官邀查询器 // @namespace https://greasyfork.org/zh-CN/users/1270887-co-ob // @version 0.5.3 // @description 中国私人追踪器官方招募路线 // @author ChatGPT, cO_ob // @match *://tieba.baidu.com/* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const dataUrl = 'https://or.jpt.com.np/data.json'; const inviteRouteButton = document.createElement('button'); inviteRouteButton.innerText = '💊'; inviteRouteButton.id = 'inviteRouteButton'; document.body.appendChild(inviteRouteButton); inviteRouteButton.addEventListener('click', (event) => { const form = document.getElementById('routeCalcForm'); const overlay = document.getElementById('overlay'); const isFormVisible = form.style.display === 'block'; form.style.display = isFormVisible ? 'none' : 'block'; overlay.style.display = isFormVisible ? 'none' : 'block'; event.stopPropagation(); }); async function fetchData() { try { const response = await fetch(dataUrl); if (!response.ok) { throw new Error('Network response was not ok'); } return await response.json(); } catch (error) { console.error('Error fetching data:', error); return null; } } window.addEventListener('load', async function() { const data = await fetchData(); if (!data) return; const { routeInfo, unlockInviteClass, classInfo, nicknameList } = data; var style = document.createElement('style'); style.textContent = ` :root { --light: hsl(0, 0%, 100%); --background: linear-gradient(to right bottom, hsl(236, 50%, 50%), hsl(195, 50%, 50%)); } #overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9000; } #inviteRouteButton { display: flex; position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; align-items: center; justify-content: center; border-radius: 50%; background-color: rgba(128, 128, 128, 0.5); color: white; border: none; font-size: 24px; cursor: pointer; } #routeCalcForm { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 700px; height: 400px; padding: 20px; background: var(--background); border-radius: 12px; z-index: 9001; user-select: none; } #routeCalcForm button { margin-bottom: 10px; padding: 16px 16px; font-family: inherit; font-size: 16px; color: var(--light); background: transparent; letter-spacing: 2px; cursor: pointer; } #externalLinks { position: absolute; top: 8px; right: 4px; display: flex; align-items: center; font-size: 24px; } #trackerStatus,#officialRecruitments { margin-right: 12px; color: var(--light); } #headerTable { margin: 0 auto; width: 100vw; height: 80px; max-height: 80px; table-layout: fixed; text-align: center; max-width: calc(min(100%,1080px)); border-collapse: collapse; overflow: hidden; } .col1, .col5 { width: 10%; } .col2, .col4 { width: 20%; text-align: center; position: relative; } .col3 { width: 40%; } td { overflow: hidden; } #routeChain { display: flex; justify-content: center; align-items: center; height: 70%; max-height: 78px; font-size: 16px; color: var(--light); overflow: hidden; text-overflow: ellipsis; white-space: normal; } #target, #source { display: block; position: absolute; left: 10px; right: 10px; top: -5px; font-size: 16px; font-family: inherit; color: var(--light); background: transparent; cursor: pointer; z-index: 2; padding-top: 60px; } #sourceNickname, #targetNickname { display: inline-block; position: relative; width: 100%; top: -15%; font-weight: bold; font-size: 32px; color: var(--light); z-index: 1; } #prevroute, #nextroute { display: none; position: relative; top:8px; font-size: calc(max(2.5vw, 32px)); color: var(--light); letter-spacing: 1px; cursor: pointer; } #chainInfo { color: var(--light); height: 30%; font-size: 16px; text-align: center; border-collapse: collapse; } #detailsTable { padding: 10px; color: var(--light); font-size: 13px; max-height: 300px; overflow: auto; line-height: 1.6; } #detailsTable, #gridContainer { -ms-overflow-style: none; scrollbar-width: none; } #detailsTable::-webkit-scrollbar, #gridContainer::-webkit-scrollbar { display: none; } .separator { height: 1px; background-color: hsla(0, 0%, 100%, .4); margin: 10px 0; } #gridContainer { display: none; position: absolute; top: 130px; left: 60px; width: 600px; max-height: 240px; padding: 10px; background: #f9f9f9; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 8px; overflow: hidden; overflow-y: auto; z-index: 9002; } #gridContainer div { padding: 8px; background: #fff; font-size: 13px; text-align: center; cursor: pointer; white-space: nowrap; min-width: max-content; } `; document.head.appendChild(style); const overlay = document.createElement('div'); overlay.id = 'overlay'; document.body.appendChild(overlay); const inviteForm = document.createElement('form'); inviteForm.id = 'routeCalcForm'; inviteForm.style.display = 'none'; inviteForm.innerHTML = ` <table id="headerTable"> <tr> <td class="col1"><div id="prevroute">‹‹</div></td> <td class="col2"> <div id="sourceNickname">始发站</div> <div id="source">From</div> </td> <td class="col3"> <table> <tr> <div id="routeChain"></div> </tr> <tr> <div id="chainInfo"></div> </tr> </table></td> <td class="col4"> <div id="targetNickname">终点站</div> <div id="target">To</div> </td> <td class="col5"><div id="nextroute">››</div></td> </tr> </table> <div id="externalLinks"> <a id="trackerStatus" href="https://ti-or.github.io/status/" target="_blank">?</a> <a id="officialRecruitments" href="https://ti-or.github.io/" target="_blank">!</a> </div> <div id="detailsTable"></div> <div id="gridContainer"></div> `; document.body.appendChild(inviteForm); document.addEventListener('click', function(event) { var routeCalcForm = document.getElementById('routeCalcForm'); var overlay = document.getElementById('overlay'); if (!routeCalcForm.contains(event.target) && event.target !== inviteRouteButton) { routeCalcForm.style.display = 'none'; overlay.style.display = 'none'; } }); document.getElementById('source').addEventListener('click', function() { const gridContainer = document.getElementById('gridContainer'); if (gridContainer.style.display === 'none' || gridContainer.style.display === '') { populateGrid('source'); gridContainer.style.display = 'block'; } else { gridContainer.style.display = 'none'; } }); document.getElementById('target').addEventListener('click', function() { const gridContainer = document.getElementById('gridContainer'); if (gridContainer.style.display === 'none' || gridContainer.style.display === '') { populateGrid('target'); gridContainer.style.display = 'block'; } else { gridContainer.style.display = 'none'; } }); document.getElementById('prevroute').addEventListener('click', function() { showRoute(-1); }); document.getElementById('nextroute').addEventListener('click', function() { showRoute(1); }); document.addEventListener('click', function(event) { const gridContainer = document.getElementById('gridContainer'); if (gridContainer.style.display === 'block' && !gridContainer.contains(event.target) && event.target.id !== 'source' && event.target.id !== 'target') { gridContainer.style.display = 'none'; } }); let allRoutes = []; let currentRouteIndex = 0; function populateGrid(buttonId) { const gridContainer = document.getElementById('gridContainer'); gridContainer.innerHTML = ''; const uniqueKeys = Array.from(new Set( Object.keys(routeInfo) .flatMap(startKey => [startKey, ...Object.keys(routeInfo[startKey])]) )) .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); if (buttonId === 'source') { uniqueKeys.unshift('From'); } else if (buttonId === 'target') { uniqueKeys.unshift('To'); } const container = document.createElement('div'); container.style.display = 'grid'; container.style.gridTemplateColumns = 'repeat(auto-fit, minmax(100px, auto))'; container.style.gap = '0px'; uniqueKeys.forEach(key => { const item = document.createElement('div'); item.textContent = key; document.getElementById('gridContainer').appendChild(item); item.addEventListener('click', () => { if (buttonId === 'source') { if (key === 'From') { setTextContent('source', 'From'); setTextContent('sourceNickname', '始发站'); } else { setTextContent('source', key); setTextContent('sourceNickname', nicknameList[key] || key); } } else if (buttonId === 'target') { if (key === 'To') { setTextContent('target', 'To'); setTextContent('targetNickname', '终点站'); } else { setTextContent('target', key); setTextContent('targetNickname', nicknameList[key] || key); } } gridContainer.style.display = 'none'; calculateRoute(); }); container.appendChild(item); }); gridContainer.appendChild(container); } function calculateRoute() { const resultTitle = document.getElementById('routeChain'); const resultDescription = document.getElementById('detailsTable'); const prevRouteButton = document.getElementById('prevroute'); const nextRouteButton = document.getElementById('nextroute'); const chainInfo = document.getElementById('chainInfo'); resultTitle.innerText = ''; chainInfo.innerText = ''; resultDescription.innerHTML = ''; prevRouteButton.style.display = 'none'; nextRouteButton.style.display = 'none'; const start = document.getElementById('source').textContent.trim(); const end = document.getElementById('target').textContent.trim(); if (start === end) { resultTitle.innerText = '注册多个账号的用户将被禁止!'; resultDescription.innerText = ''; return; } if (start === 'From') { if (end === 'To') { resultTitle.innerText = '选一个'; resultDescription.innerText = ''; return; } allRoutes = Object.keys(routeInfo) .filter(node => routeInfo[node] && routeInfo[node][end]) .map(node => [node, end]); } else if (end === 'To') { if (!routeInfo[start]) { resultTitle.innerText = '没收录'; resultDescription.innerText = ''; return; } allRoutes = Object.keys(routeInfo[start]) .map(node => [start, node]) .filter(([from, to]) => routeInfo[from] && routeInfo[from][to]) .map(([from, to]) => [from, to]); } else { if (!routeInfo[start]) { resultTitle.innerText = '没收录'; resultDescription.innerText = ''; return; } allRoutes = findAllRoutes(start, end); } if (allRoutes.length === 0) { resultTitle.innerText = '没查到'; resultDescription.innerText = ''; return; } allRoutes = allRoutes.sort((a, b) => { const timeA = a.reduce((sum, node, index) => { if (index < a.length - 1 && routeInfo[node] && routeInfo[node][a[index + 1]]) { return sum + getMaxDays(node, a[index + 1]); } return sum; }, 0); const timeB = b.reduce((sum, node, index) => { if (index < b.length - 1 && routeInfo[node] && routeInfo[node][b[index + 1]]) { return sum + getMaxDays(node, b[index + 1]); } return sum; }, 0); return timeA - timeB; }); currentRouteIndex = 0; showRoute(0); } function showRoute(direction) { if (allRoutes.length === 0) return; currentRouteIndex = (currentRouteIndex + direction + allRoutes.length) % allRoutes.length; const route = allRoutes[currentRouteIndex]; const resultTitle = document.getElementById('routeChain'); const resultDescription = document.getElementById('detailsTable'); const prevRouteButton = document.getElementById('prevroute'); const nextRouteButton = document.getElementById('nextroute'); const chainInfo = document.getElementById('chainInfo'); const totalRoutes = allRoutes.length; const routeCountText = `第 ${currentRouteIndex + 1} / ${totalRoutes} 条`; function getAbbreviation(name) { return nicknameList[name] || name; } const abbreviatedRoute = route.map(node => getAbbreviation(node)); let displayRoute; if (abbreviatedRoute.length === 2) { displayRoute = ' -> '; } else if (abbreviatedRoute.length > 2) { const middleItems = abbreviatedRoute.slice(1, -1); displayRoute = middleItems.length > 0 ? ` -> ${middleItems.join(' -> ')} -> ` : ''; } else { displayRoute = abbreviatedRoute[0] || ''; } resultTitle.innerText = displayRoute; const details = route.map((node, index) => { console.log("Processing node:", node); if (index < route.length - 1 && routeInfo[node] && routeInfo[node][route[index + 1]]) { const nextNode = route[index + 1]; const maxDays = getMaxDays(node, nextNode); const [time, userclass, requirement, lastActivity] = routeInfo[node][nextNode]; const formattedDate = formatDate(lastActivity); const fromUserClassIndex = Object.keys(classInfo[node]).indexOf(userclass); const unlockUserClassIndex = Object.keys(classInfo[node]).indexOf(unlockInviteClass[node]); const higherUserClass = (unlockUserClassIndex !== -1) ? (fromUserClassIndex > unlockUserClassIndex ? userclass : unlockInviteClass[node]) : userclass; let upgradeReqText = ''; if (higherUserClass && Object.keys(classInfo[node]).includes(higherUserClass)) { const upgradeReq = classInfo[node][higherUserClass]; const upgradeReqParts = []; if (upgradeReq[0]) upgradeReqParts.push(`注册时间${upgradeReq[0]}天`); if (upgradeReq[1]) upgradeReqParts.push(`下载量${upgradeReq[1]}`); if (upgradeReq[2]) upgradeReqParts.push(`分享率${upgradeReq[2]}`); if (upgradeReq[3]) upgradeReqParts.push(upgradeReq[3]); upgradeReqText = upgradeReqParts.join(' & '); } let baseText = `<div class="separator"></div>${node} -> ${nextNode} 时间:${maxDays}天 ${unlockInviteClass[node] ? ` 解锁等级:${unlockInviteClass[node]}` : ''} ${userclass ? `申请等级:${userclass}` : ''} 最近活动:${formattedDate}`; if (userclass || unlockInviteClass[node]) { baseText += `<br>升级要求:${upgradeReqText}`; } if (requirement) { baseText += `<br>额外要求:${requirement}`; } return baseText; } return ''; }); chainInfo.innerText = `${routeCountText} 换乘 ${route.length - 2} 次 耗时 ${route.reduce((sum, node, index) => { if (index < route.length - 1 && routeInfo[node] && routeInfo[node][route[index + 1]]) { return sum + getMaxDays(node, route[index + 1]); } return sum; }, 0)} 天`; resultDescription.innerHTML = details.join(''); prevRouteButton.style.display = currentRouteIndex === 0 ? 'none' : 'inline'; nextRouteButton.style.display = currentRouteIndex === allRoutes.length - 1 ? 'none' : 'inline'; } function getMaxDays(start, end) { const days1 = routeInfo[start][end][0]; const userclassRequirement = routeInfo[start][end][1]; const days2 = (userclassRequirement === '') ? 0 : classInfo[start][userclassRequirement][0]; let days3 = 0; const unlockUserClass = unlockInviteClass[start]; if (unlockUserClass) { days3 = classInfo[start][unlockUserClass][0]; } return Math.max(days1, days2, days3); } function findAllRoutes(start, end) { const result = []; const stack = [[start, [start]]]; while (stack.length) { const [node, route] = stack.pop(); if (node === end) { result.push(route); continue; } for (const [next, _] of Object.entries(routeInfo[node] || {})) { if (!route.includes(next)) { stack.push([next, [...route, next]]); } } } return result; } function formatDate(dateString) { const year = Math.floor(dateString / 100); const month = dateString % 100; return `20${year}年${month.toString().padStart(2, '0')}月`; } function setTextContent(id, text) { document.getElementById(id).textContent = text; } }); })();