中文PT站官邀查询器

中国私人追踪器官方招募路线

当前为 2024-09-11 提交的版本,查看 最新版本

// ==UserScript==
// @name     中文PT站官邀查询器
// @namespace  https://greasyfork.org/zh-CN/users/1270887-co-ob
// @version    0.4.1
// @description  中国私人追踪器官方招募路线
// @author     ChatGPT, cO_ob
// @match    *://tieba.baidu.com/*
// @grant    none
// @license    MIT
// ==/UserScript==

(function() {
  'use strict';
  const dataUrl = 'https://ptorcn.netlify.app/data.json';
  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 = `
      #overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 9998;
        display: none;
      }
      #inviteRouteButton {
        position: fixed;
        bottom: 20px;
        right: 20px;
        width: 60px;
        height: 60px;
        border-radius: 50%;
        background-color: rgba(128, 128, 128, 0.5);
        color: white;
        border: none;
        font-size: 24px;
        cursor: pointer;
        z-index: 1000;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      #routeCalcForm {
        --light: hsl(0, 0%, 100%);
        --background: linear-gradient(to right bottom, hsl(236, 50%, 50%), hsl(195, 50%, 50%));
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 700px;
        height: 400px;
        background: var(--background);
        border-radius: 12px;
        padding: 20px;
        user-select: none;
        z-index: 9999;
      }
      #routeCalcForm button {
        margin-bottom: 10px;
        padding: 16px 16px;
        color: var(--light);
        background: transparent;
        font-family: inherit;
        letter-spacing: 2px;
        cursor: pointer;
      }
      #routeContainer {
        display: flex;
        margin-top: 32px;
        align-items: center;
        justify-content: space-between;
        padding: 10px;
        position: relative;
      }

      #routeTitle {
        color: var(--light);
        background: transparent;
        font-size: 16px;
        text-align: center;
      }

      #routeCalcForm #sourceTracker,
      #routeCalcForm #targetTracker {
        position: absolute;
        top: 30px;
        width: 160px;
        height: 54px;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
      }

      #routeCalcForm #sourceTracker {
        left: 80px;
      }

      #routeCalcForm #targetTracker {
        right: 80px;
      }

      #routeCalcForm #targetTracker #target,
      #routeCalcForm #sourceTracker #source {
        margin-top: -90px;
        padding: 80px 10px 10px 10px;
        color: var(--light);
        background: transparent;
        font-size: 16px;
        font-weight: bold;
        font-family: inherit;
        letter-spacing: 2px;
        transition: .2s;
        cursor: pointer;
        z-index: 6000;
      }

      #routeCalcForm #sourceTracker button,
      #routeCalcForm #targetTracker button{
        margin-top: 10px;
        border: none;
        padding: 16px 25px;
        color: var(--light);
        background: transparent;
        font-size: 32px;
        font-family: inherit;
        letter-spacing: 2px;
        transition: .2s;
        cursor: pointer;
      }

      #routeCalcForm #exitButton {
        position: absolute;
        color: var(--light);
        background: transparent;
        top: 5px;
        right: 5px;
        font-size: 32px;
      }

      #prevroute, #nextroute {
        display: none;
        position: absolute;
        bottom: 0;
        padding: 8px 8px;
        height: 22px;
        color: var(--light);
        background: transparent;
        font-size: 48px;
        font-family: inherit;
        letter-spacing: 1px;
        cursor: pointer;
      }

      #prevroute {
        left: 0;
      }

      #nextroute {
        right: 0;
      }

      #routeText {
        padding: 10px;
        margin-top: 20px;
        color: var(--light);
        font-size: 13px;
        max-height: 240px;
        overflow: auto;
        line-height: 1.6;
      }

      #routeText::-webkit-scrollbar {
        width: 0;
        height: 0;
      }

      #routeText::-webkit-scrollbar-track {
        background: transparent;
      }

      #routeText::-webkit-scrollbar-thumb {
        background: transparent;
      }

      .separator {
        height: 1px;
        background-color: hsla(0, 0%, 100%, .4);
        margin: 10px 0;
      }

      #gridContainer {
        display: none;
        position: absolute;
        width: 600px;
        top: 130px;
        left: 60px;
        max-height: 240px;
        overflow: hidden;
        background: #f9f9f9;
        padding: 10px;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        border-radius: 8px;
        overflow-y: auto;
        z-index: 10000;
      }

      #gridContainer::-webkit-scrollbar {
        display: none;
      }

      #gridContainer {
        -ms-overflow-style: none;
        scrollbar-width: none;
      }

      #gridContainer div {
        padding: 8px;
        background: #fff;
        text-align: center;
        cursor: pointer;
        white-space: nowrap;
        min-width: max-content;
      }

      #gridContainer div.border {
        border: 1px solid #000;
      }

    `;
    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 = `

      <div id="sourceTracker">
        <button type="button" id="sourcenick">始发站</button>
        <button type="button" id="source">From</button>
      </div>
      <div id="targetTracker">
        <button type="button" id="targetnick">终点站</button>
        <button type="button" id="target">To</button>
      </div>
      <div id="routeContainer">
        <div id="routeTitle"></div>
        <div id="prevroute">&#8249;&#8249;</div>
        <div id="nextroute">&#8250;&#8250;</div>
      </div>
      <div id="routeText"></div>
      <div id="gridContainer"></div>
      <div id="exitButton">
        <button type="button" id="exit">×</button>
      </div>

    `;

    const inviteRouteButton = document.createElement('button');
    inviteRouteButton.innerText = '💊';
    inviteRouteButton.id = 'inviteRouteButton';

    document.body.appendChild(inviteForm);
    document.body.appendChild(inviteRouteButton);

    inviteRouteButton.addEventListener('click', () => {
      const form = document.getElementById('routeCalcForm');
      const isFormVisible = form.style.display === 'block';
      form.style.display = isFormVisible ? 'none' : 'block';
      overlay.style.display = isFormVisible ? 'none' : 'block';
      if (isFormVisible) {
      } else {
      }
    });

    document.getElementById('exit').addEventListener('click', function() {
      document.getElementById('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;
        if (key === 'From' || key === 'To') {
          item.classList.add('border');
        }
        document.getElementById('gridContainer').appendChild(item);
        item.addEventListener('click', () => {
          if (buttonId === 'source') {
            if (key === 'From') {
              setTextContent('source', 'From');
              setTextContent('sourcenick', '始发站');
            } else {
              setTextContent('source', key);
              setTextContent('sourcenick', nicknameList[key] || key);
            }
          } else if (buttonId === 'target') {
            if (key === 'To') {
              setTextContent('target', 'To');
              setTextContent('targetnick', '终点站');
            } else {
              setTextContent('target', key);
              setTextContent('targetnick', nicknameList[key] || key);
            }
          }
          gridContainer.style.display = 'none';
          calculateRoute();
        });
        container.appendChild(item);
      });

      gridContainer.appendChild(container);
    }

    function calculateRoute() {

      const routeContainer = document.getElementById('routeContainer');
      const resultTitle = document.getElementById('routeTitle');
      const resultDescription = document.getElementById('routeText');
      const prevRouteButton = document.getElementById('prevroute');
      const nextRouteButton = document.getElementById('nextroute');

      resultTitle.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 = '';
        routeContainer.style.display = 'block';
        return;
      }

      if (start === 'From') {
        if (end === 'To') {
          resultTitle.innerText = '选一个';
          resultDescription.innerText = '';
          routeContainer.style.display = 'block';
          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 = '';
          routeContainer.style.display = 'block';
          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 = '';
          routeContainer.style.display = 'block';
          return;
        }
        allRoutes = findAllRoutes(start, end);
      }

      if (allRoutes.length === 0) {
        resultTitle.innerText = '没查到';
        resultDescription.innerText = '';
        routeContainer.style.display = 'block';
        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 routeContainer = document.getElementById('routeContainer');
      const resultTitle = document.getElementById('routeTitle');
      const resultDescription = document.getElementById('routeText');
      const prevRouteButton = document.getElementById('prevroute');
      const nextRouteButton = document.getElementById('nextroute');

      const totalRoutes = allRoutes.length;
      const routeCountText = `第 ${currentRouteIndex + 1} 条 / 总 ${totalRoutes} 条`;

      resultTitle.innerText = `${routeCountText}\n当前路线换乘 ${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)} 天`;

      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 '';
      });

      resultDescription.innerHTML = details.join('');

      prevRouteButton.style.display = currentRouteIndex === 0 ? 'none' : 'inline';
      nextRouteButton.style.display = currentRouteIndex === allRoutes.length - 1 ? 'none' : 'inline';
      routeContainer.style.display = 'block';
    }

    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]]];
      const visited = new Set();

      while (stack.length) {
        const [node, route] = stack.pop();

        if (node === end) {
          result.push(route);
          continue;
        }

        visited.add(node);

        for (const [next, _] of Object.entries(routeInfo[node] || {})) {
          if (!route.includes(next) && !visited.has(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;
    }

  });
})();