SCBOY摸鱼插件

将 SCBoy 论坛改造成简洁表格风格:全局纯白黑字,隐藏头像和侧栏,论坛列表页+帖子详情页完全表格化。

// ==UserScript==
// @name         SCBOY摸鱼插件
// @namespace    https://www.scboy.cc/
// @version      0.14
// @description  将 SCBoy 论坛改造成简洁表格风格:全局纯白黑字,隐藏头像和侧栏,论坛列表页+帖子详情页完全表格化。
// @author       (Based on original by )
// @match        https://www.scboy.cc/*
// @icon         https://www.scboy.cc/view/img/favicon.png
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      www.scboy.cc
// @run-at       document-end
// @license MIT

// ==/UserScript==

(function () {
  'use strict';

  /****************** 全局样式 ******************/
  GM_addStyle(`
    html, body { margin:0; padding:0; background:#FFFFFF !important; color:#000000 !important; }
    * { color:#000000 !important; }
    a { color:#000000 !important; text-decoration:none !important; }
    a:hover { text-decoration:underline !important; }
    img[class*="avatar"], .avatar, .media-left img { display:none !important; }
    .aside, .sidebar, .sidenav, .right-container { display:none !important; }
    #scboy-root { width:100vw; min-height:100vh; padding:16px; background:#FFFFFF; }
    table { border-collapse:collapse; width:100%; table-layout: fixed; }
    table th, table td { border:1px solid #CCC; padding:8px; word-wrap:break-word; }
    table th { background:#f0f0f0; font-weight:bold; text-align:center; }
    table tbody tr:nth-child(even) { background:#f9f9f9; }
    table tbody tr:hover { background:#f1f1f1; }
    .sc-nav-table td { width:20%; text-align:center; background:#f0f0f0; font-weight:bold; }
    .scboy-thread-title { font-weight:bold; color:#000000 !important; display:block; }
    .scboy-radar-content { font-weight:bold; color:#000000 !important; }
    /* 隐藏原始内容 (列表页和详情页通用) */
    #main, #header, #footer, .card-header, .card-footer, .nav, .navbar, .breadcrumb,
    .pagination, .form, .btn-toolbar, .dropdown, .modal, .badge,
    .haya-post-info-thread-see-him, .haya-post-info-post-see-him,
    .icon, .approve_gold_name, .badges, .js-sc-follow-add, .post_update, .icon-warning,
    .social-share, .floor-parent, .floor, #replyform*, #floor_*, #pushfloor_*, #floor_expand_*,
    #floor_pagination_*, .btn, .text-grey, .mr-3, .ml-2, .mr-2, .ml-0, .mr-0, .mr-1, .ml-1,
    .d-none, .hidden, .hide, .small, .text-muted, .text-right, .justify-content-between,
    .d-flex, .media-left, .dl-horizontal, .dl, .dd, .pull-right, .pull-left,
    .card-thread .media-body > div:not(:has(h4)),
    .media-body > .d-flex,
    .message .d-flex,
    .xs_show, .hidden-sm, .hidden-xs /* 列表页特定隐藏 */
     { display: none !important; }
    /* 定义详情页表格列宽 */
    #sc-main-table:not(.sc-main-table-list) col:nth-child(1) { width: 70%; } /* 内容 */
    #sc-main-table:not(.sc-main-table-list) col:nth-child(2) { width: 10%; } /* 作者 */
    #sc-main-table:not(.sc-main-table-list) col:nth-child(3) { width: 10%; } /* 时间 */
    #sc-main-table:not(.sc-main-table-list) col:nth-child(4) { width: 10%; } /* 点赞 */
    /* 定义列表页表格列宽 */
    #sc-main-table.sc-main-table-list col:nth-child(1) { width: 45%; } /* 主题 */
    #sc-main-table.sc-main-table-list col:nth-child(2) { width: 10%; } /* 注意! (雷达) */
    #sc-main-table.sc-main-table-list col:nth-child(3) { width: 15%; } /* 作者 */
    #sc-main-table.sc-main-table-list col:nth-child(4) { width: 15%; } /* 时间 */
    #sc-main-table.sc-main-table-list col:nth-child(5) { width: 15%; } /* 回复 */
    #sc-pagination { margin-top:12px; text-align:center; }
  `);

  /****************** 通用辅助函数 ******************/
  const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
  const $1 = (sel, root = document) => root.querySelector(sel);
  const txt = (el) => el ? el.textContent.trim() : "";
  const isForumListPage = () => location.search.startsWith("?forum-");
  const isThreadPage = () => location.search.startsWith("?thread-");

  /****************** 导航栏 ******************/
  function renderNavHTML() {
    return `
      <table class="sc-nav-table">
        <tr>
          <td><a href="https://www.scboy.cc/">首页</a></td>
          <td><a href="/?forum-2.htm">乔伊雷酒吧</a></td>
          <td><a href="/?forum-3.htm">科普鲁星区</a></td>
          <td><a href="/?forum-4.htm">直播讨论</a></td>
          <td><a href="https://www.scboy.cc/?bet-home.htm">老板二楼请</a></td>
        </tr>
      </table>
    `;
  }

  /****************** 列表页逻辑 ******************/
  function parseThreadItem(li) {
    // 标题
    const titleAnchor = $1('div.subject > a.xs-thread-a', li);
    const title = txt(titleAnchor) || '(无标题)';
    let url = titleAnchor ? titleAnchor.href : '';
    if (url && url.startsWith('/')) url = location.origin + url;
    const rel = url.replace(location.origin + '/', '');
    // 作者
    const authorEl = $1('.username.text-grey.mr-1.hidden-sm[uid]', li);
    const author = txt(authorEl);
    // 时间
    const timeEl = $1('.date.text-grey.hidden-sm', li);
    const time = txt(timeEl);
    // 回复数
    let replyCount = '';
    const replyIcon = $1('i.icon-comment-o', li);
    if (replyIcon && replyIcon.parentElement) {
      replyCount = txt(replyIcon.parentElement);
    }
    return { title, url, rel, author, time, replyCount };
  }

  function renderListTableHTML(rows, pagination) {
    return `
      <table class="sc-main-table sc-main-table-list" id="sc-main-table">
        <colgroup>
          <col><col><col><col><col> <!-- 由 CSS 定义宽度 -->
        </colgroup>
        <thead>
          <tr>
            <th>主题</th><th>注意!</th><th>作者</th><th>时间</th><th>回复</th>
          </tr>
        </thead>
        <tbody>
          ${rows.map(r => `
            <tr data-tid="${r.rel}">
              <td><a class="scboy-thread-title" href="${r.url}">${r.title}</a></td>
              <td><div class="scboy-radar-content"></div></td>
              <td>${r.author}</td>
              <td>${r.time}</td>
              <td>${r.replyCount}</td>
            </tr>`).join('')}
        </tbody>
      </table>
      <div id="sc-pagination">${pagination || ''}</div>
    `;
  }

  // 雷达用户
  const RADAR_USERS = [
    { id:29, name:'F91' }, { id:9, name:'蟑螂' }, { id:10, name:'鹅' },
    { id:56789, name:'兔兔' }, { id:14, name:'八甲神牛' }, { id:89053, name:'乌龟' }
  ];

  function normalizeRel(url) {
    try { return new URL(url, location.origin).pathname + new URL(url, location.origin).search; }
    catch { return url; }
  }

  function fetchUserPages(user, cb) {
    GM_xmlhttpRequest({
      method: 'GET',
      url: `https://www.scboy.cc/?user-${user.id}.htm`,
      onload: res => {
        const doc = new DOMParser().parseFromString(res.responseText, 'text/html');
        const anchors = $$('a[href*="?thread-"]', doc);
        const set = new Set(anchors.map(a => normalizeRel(a.href)));
        cb({ user, set });
      }
    });
  }

  function runRadar() {
    const rows = $$('#sc-main-table tbody tr');
    if (rows.length === 0) return;
    let pending = RADAR_USERS.length;
    const maps = [];
    RADAR_USERS.forEach(u => {
      fetchUserPages(u, data => {
        maps.push(data); pending--;
        if (pending === 0) {
          rows.forEach(tr => {
            const rel = tr.getAttribute('data-tid');
            const box = tr.querySelector('.scboy-radar-content');
            const hits = maps.filter(m => m.set.has(rel)).map(m => `有${m.user.name}`);
            box.textContent = hits.join(' ');
          });
        }
      });
    });
  }

  function reformatForumPage() {
    const items = $$('li.media.thread.tap');
    const rows = items.map(li => parseThreadItem(li));
    const pagination = $1('nav ul.pagination')?.outerHTML || '';
    document.body.innerHTML = `<div id="scboy-root">${renderNavHTML()}${renderListTableHTML(rows, pagination)}</div>`;
    // 尝试运行雷达功能
    try {
        runRadar();
    } catch (e) {
        console.error("SCBoy 表格模式雷达功能出错:", e);
    }
    // --- 修改列表页标题 ---
    document.title = "工作簿2 - SCBoy 列表页";
  }

  /****************** 详情页逻辑 ******************/
  function parseThread() {
    const rows = [];
    // --- 主楼 ---
    const mainCard = $1(".card.card-thread");
    if (mainCard) {
        // 第一行:主楼标题 (更精确地定位 h4 并移除标签)
        let threadTitleEl = $1("h4.break-all", mainCard);
        if (threadTitleEl) {
            const titleClone = threadTitleEl.cloneNode(true);
            $$(".badge", titleClone).forEach(b => b.remove());
            const threadTitle = txt(titleClone);
            if (threadTitle) {
                rows.push({
                    content: `<strong>${threadTitle}</strong>`,
                    author: "",
                    date: "",
                    like: ""
                });
            }
        }
        // 第二行:主楼正文、作者、时间、点赞
        const mainContentEl = $1(".message.break-all[isfirst='1']", mainCard);
        const mainContent = mainContentEl?.innerHTML || "";
        const mainAuthor = txt($1(".username a.text-muted.font-weight-bold", mainCard));
        const mainDate = txt($1(".date.text-grey", mainCard));
        const mainLike = txt($1(".haya-post-like-thread-user-count", mainCard));
        rows.push({
            content: mainContent,
            author: mainAuthor,
            date: mainDate,
            like: mainLike
        });
    }
    // --- 回复列表 ---
    $$("li.media.post").forEach((li) => {
        const postContent = $1(".message.break-all", li)?.innerHTML || "";
        const postAuthor = txt($1(".username a.text-muted.font-weight-bold", li));
        const postDate = txt($1(".text-right .date.text-grey", li));
        const postLike = txt($1(".haya-post-like-post-user-count", li));
        rows.push({
            content: postContent,
            author: postAuthor,
            date: postDate,
            like: postLike
        });
        // --- 楼中楼回复 ---
        $$("div[id^='reply_'] .text-left.media", li).forEach(sub => {
            const subContentSpan = $1("span", sub);
            const subContent = subContentSpan ? subContentSpan.innerHTML : "";
            const subAuthor = txt($1("a.text-muted.font-weight-bold", sub));
            const subDateDiv = $1(".text-muted.text-right", sub);
            const subDate = subDateDiv ? txt(subDateDiv) : "";
            const subLike = "";
            rows.push({
                content: `<em>// ${subContent}</em>`,
                author: subAuthor,
                date: subDate,
                like: subLike
            });
        });
    });
    return rows;
  }

  function renderThreadTableHTML(rows) {
    const trs = rows.map(r => `
      <tr>
        <td>${r.content}</td>
        <td>${r.author}</td>
        <td>${r.date}</td>
        <td>${r.like}</td>
      </tr>`).join("");
    return `
      <table id="sc-main-table">
        <colgroup>
           <col><col><col><col> <!-- 由 CSS 定义宽度 -->
        </colgroup>
        <thead>
          <tr>
             <th>内容</th>
             <th>作者</th>
             <th>时间</th>
             <th>点赞</th>
          </tr>
        </thead>
        <tbody>${trs}</tbody>
      </table>
    `;
  }

  function reformatThreadPage() {
    const rows = parseThread();
    document.body.innerHTML = `<div id="scboy-root">${renderNavHTML()}${renderThreadTableHTML(rows)}</div>`;
    // --- 修改详情页标题 ---
    document.title = "工作簿2 - SCBoy 详情页";
  }

  /****************** 启动入口 ******************/
  if (isForumListPage()) {
    reformatForumPage();
  } else if (isThreadPage()) {
    reformatThreadPage();
  }

})();