Greasy Fork

Greasy Fork is available in English.

2048增强插件

2048论坛帖子加载预览图和链接,内容独立显示在标题下方,悬浮图片根据宽高比自动缩放,并智能去除翻页时的重复帖子。悬浮标题1.5秒可加载全部图片。新增付费贴检测。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         2048增强插件
// @namespace    none
// @version      0.4.9
// @description  2048论坛帖子加载预览图和链接,内容独立显示在标题下方,悬浮图片根据宽高比自动缩放,并智能去除翻页时的重复帖子。悬浮标题1.5秒可加载全部图片。新增付费贴检测。
// @author       nonono
// @match        *://2048.cc/*
// @grant        none
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @license      MIT
// ==/UserScript==

(function ($) {
  "use strict";

  $(function () {
    const styles = `
            .userscript-content-cell { padding: 5px 0 10px !important; border: none !important; }
            .userscript-image-gallery { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 8px; }
            .userscript-preview-image { height: 200px; width: auto; cursor: pointer; display: block; transition: opacity 0.2s ease; border-radius: 4px; background-color: #f0f0f0; }
            .userscript-preview-image:hover { opacity: 0.8; }
            .userscript-link, .userscript-magnet-link { padding: 5px 0; font-size: 12px; font-weight: normal; word-break: break-all; cursor: pointer; }
            .userscript-no-content-notice { color: #999; font-size: 12px; padding: 10px 0; }
            .userscript-image-count { font-size: 12px; color: #008000; margin-left: 8px; font-weight: normal; }
            #userscript-floating-image { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: auto; height: auto; max-width: 95vw; max-height: 95vh; z-index: 99999; border: 5px solid white; box-shadow: 0 10px 30px rgba(0,0,0,0.5); pointer-events: none; border-radius: 5px; }
        `;
    $("head").append(`<style>${styles}</style>`);
    $("body").append(`<img id="userscript-floating-image" src="" />`);

    $(document).on("mouseenter", ".userscript-preview-image", function () {
      const $this = $(this);
      const imageUrl = $this.attr("src");
      const naturalWidth = $this.data("naturalWidth");
      const naturalHeight = $this.data("naturalHeight");
      if (!imageUrl || !naturalWidth || !naturalHeight) return;
      const $floatingImage = $("#userscript-floating-image");
      if (naturalWidth > naturalHeight) {
        $floatingImage.css({ width: "50vw", height: "auto" });
      } else {
        $floatingImage.css({ height: "80vh", width: "auto" });
      }
      $floatingImage.attr("src", imageUrl).stop(true, true).fadeIn(100);
    });

    $(document).on("mouseleave", ".userscript-preview-image", () => {
      $("#userscript-floating-image").stop(true, true).fadeOut(100);
    });

    const ads = [".promo-container", ".nav-container", ".movie-banner", "#ads"];
    $.each(ads, (i, e) => $(e).hide());

    function copyToClipboard(text, element) {
      navigator.clipboard
        .writeText(text)
        .then(() => {
          if (element) {
            const originalText = $(element).text();
            $(element).text("已复制!").css("color", "darkred");
            setTimeout(() => {
              $(element).text(originalText).css("color", "");
            }, 1500);
          }
        })
        .catch((err) => console.error("[2048增强插件] 复制失败:", err));
    }

    if (window.location.href.includes("read.php")) {
      const readObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === "childList") {
            $(mutation.addedNodes)
              .find('.f14 a[href*="name="]')
              .addBack('.f14 a[href*="name="]')
              .each(function () {
                const $link = $(this),
                  href = $link.attr("href");
                if (href && href.includes("name=")) {
                  const st = href.substring(href.indexOf("=") + 1);
                  $link.attr(
                    "href",
                    `https://down.dataaps.com/down.php/${st}.torrent`
                  );
                }
              });
          }
        });
      });
      readObserver.observe(document.body, { childList: true, subtree: true });
    }
    const patterns = [".subject", 'th a[href*="tid"]'];
    const pattern = patterns.find((p) => $(p).length > 0);

    if (pattern) {
      function createImageElement(src, container) {
        const MIN_IMG_WIDTH = 100;
        const MIN_IMG_HEIGHT = 100;
        const $previewImg = $('<img class="userscript-preview-image" />');
        $previewImg
          .on("load", function () {
            if (
              this.naturalWidth < MIN_IMG_WIDTH ||
              this.naturalHeight < MIN_IMG_HEIGHT
            ) {
              $(this).remove();
              return;
            }
            $(this).data({
              naturalWidth: this.naturalWidth,
              naturalHeight: this.naturalHeight,
            });
          })
          .on("error", function () {
            $(this).remove();
          });
        container.append($previewImg);
        $previewImg.attr("src", src);
      }

      function expandAndShowAllImages(linkElement) {
        const $linkElement = $(linkElement);
        if ($linkElement.data("all-images-expanded")) {
          return;
        }

        const allImageUrls = $linkElement.data("allImageUrls");
        const $insertionPoint = $linkElement.closest("tr").length
          ? $linkElement.closest("tr")
          : $linkElement.closest("th, .subject");
        const $galleryContainer = $insertionPoint
          .next()
          .find(".userscript-image-gallery");

        if (
          allImageUrls &&
          allImageUrls.length > 0 &&
          $galleryContainer.length > 0
        ) {
          if (allImageUrls.length > $galleryContainer.children("img").length) {
            console.log(
              `[2048增强插件] 悬浮加载全部 ${allImageUrls.length} 张图片...`
            );
            $galleryContainer.empty();
            allImageUrls.forEach((src) => {
              createImageElement(src, $galleryContainer);
            });
            $linkElement.data("all-images-expanded", true);
          }
        }
      }

      function processPostLink(linkElement) {
        const $linkElement = $(linkElement);
        if ($linkElement.hasClass("userscript-processed")) return;

        $linkElement.addClass("userscript-processed");

        $.ajax({
          url: linkElement.href,
          method: "get",
          success: function (data) {
            const $data = $(data);
            const $titleRow = $linkElement.closest("tr");
            const $insertionPoint = $titleRow.length
              ? $titleRow
              : $linkElement.closest("th, .subject");

            // ===== 新增功能:检测付费内容 =====
            const $postContentForCheck = $data.find(".f14");
            if ($postContentForCheck.text().includes("本内容需向作者支付")) {
              $linkElement.before(
                '<span style="color: red; font-weight: bold;">【付费】</span>'
              );
            }
            // =====================================

            const $contentCell = $(
              '<td colspan="5" class="userscript-content-cell"></td>'
            );
            let contentAdded = false;

            const IGNORED_PATHS = [
              "/smilies/",
              "/faces/",
              "/rank/",
              "/level/",
              "thumb-ing.gif",
            ];

            const allImageUrls = $data
              .find('.f14 img, img[iyl-data="adblo_ck.jpg"]')
              .map(function () {
                const $imgNode = $(this);
                let imgSrc = $imgNode.data("original") || this.src;
                if (!imgSrc) return null;
                try {
                  imgSrc = new URL(imgSrc, linkElement.href).href;
                } catch (e) {
                  return null;
                }
                if ($imgNode.closest(".signature, .user-pic").length > 0)
                  return null;
                if (IGNORED_PATHS.some((path) => imgSrc.includes(path)))
                  return null;
                return imgSrc;
              })
              .get();

            const uniqueImageUrls = [...new Set(allImageUrls)];
            const totalImageCount = uniqueImageUrls.length;

            if (totalImageCount > 0) {
              $linkElement.data("allImageUrls", uniqueImageUrls);
            }

            if (totalImageCount > 0) {
              $linkElement.after(
                `<span class="userscript-image-count">[共${totalImageCount}张图片]</span>`
              );
            }

            const magnetText = (() => {
              const $postContent = $data.find(".f14");
              if (!$postContent.length) return null;
              const contentText = $postContent.text();
              const magnetMatch = contentText.match(
                /(magnet:\?xt=urn:btih:[a-f0-9]{32,40})/i
              );
              if (magnetMatch && magnetMatch[0]) return magnetMatch[0];
              const seedCodeMatch = contentText.match(/([A-F0-9]{40})/i);
              if (seedCodeMatch && seedCodeMatch[1])
                return `magnet:?xt=urn:btih:${seedCodeMatch[1]}`;
              return null;
            })();

            const downloadUrl = (() => {
              if (magnetText) return null;

              const $postContent = $data.find(".f14");
              if (!$postContent.length) return null;

              const $downloadLink = $postContent
                .find(
                  'a[href*="bt.bxmho.cn/list.php?name="], a[href*=".torrent"], a[href*="down.php/"]'
                )
                .first();
              if ($downloadLink.length > 0) {
                try {
                  return new URL($downloadLink.attr("href"), linkElement.href)
                    .href;
                } catch (e) {
                  console.warn(
                    "[2048增强插件] 解析下载链接时出错:",
                    $downloadLink.attr("href"),
                    e
                  );
                }
              }

              const contentText = $postContent.text();
              let urlMatch = contentText.match(
                /https?:\/\/bt\.bxmho\.cn\/list\.php\?name=[a-f0-9]+/i
              );
              if (urlMatch && urlMatch[0]) {
                return urlMatch[0];
              }
              const textNodes = $postContent.contents().filter(function () {
                return (
                  this.nodeType === 3 && /下载[网地]址/.test(this.nodeValue)
                );
              });
              if (textNodes.length > 0) {
                const parentText = textNodes.first().parent().text();
                const genericUrlMatch = parentText.match(/https?:\/\/[^\s<]+/);
                if (genericUrlMatch) return genericUrlMatch[0];
              }

              return null;
            })();

            if (magnetText) {
              const $magnetElement = $(
                `<div class="userscript-magnet-link">${magnetText}</div>`
              );
              $magnetElement.on("click", function () {
                copyToClipboard(magnetText, this);
              });
              $contentCell.append($magnetElement);
              contentAdded = true;
            }

            if (downloadUrl) {
              const $link = $(
                `<div class="userscript-link">${downloadUrl}</div>`
              );
              $link.on("click", function () {
                window.open(downloadUrl, "_blank");
                copyToClipboard(downloadUrl, this);
              });
              $contentCell.append($link);
              contentAdded = true;
            }

            if (totalImageCount > 0) {
              const imageLimit = 3;
              const $galleryContainer = $(
                '<div class="userscript-image-gallery"></div>'
              );
              uniqueImageUrls.slice(0, imageLimit).forEach((src) => {
                createImageElement(src, $galleryContainer);
              });

              $contentCell.append($galleryContainer);
              contentAdded = true;
            }

            if (!contentAdded) {
              $contentCell.append(
                '<div class="userscript-no-content-notice">[未在本帖中找到有效的图片或下载链接]</div>'
              );
            }

            const $finalContainer = $insertionPoint.is("tr")
              ? $("<tr></tr>").append($contentCell)
              : $contentCell;
            $insertionPoint.after($finalContainer);
          },
          error: function () {
            $linkElement.addClass("userscript-processed-error");
          },
        });
      }

      const observerOptions = {
        root: null,
        rootMargin: "300px 0px",
        threshold: 0.01,
      };
      const observer = new IntersectionObserver((entries, obs) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            processPostLink(entry.target);
            obs.unobserve(entry.target);
          }
        });
      }, observerOptions);

      const SESSION_KEY = "processedTids";
      const getUrlParam = (url, param) => {
        try {
          const urlObj = new URL(url);
          const page = urlObj.searchParams.get(param);
          return page ? parseInt(page, 10) : 1;
        } catch (e) {
          return 1;
        }
      };

      let processedTids;
      try {
        const storedTids = sessionStorage.getItem(SESSION_KEY);
        processedTids = new Set(storedTids ? JSON.parse(storedTids) : []);
      } catch (e) {
        processedTids = new Set();
      }

      const currentPage = getUrlParam(window.location.href, "page");
      const referrerPage = getUrlParam(document.referrer, "page");

      if (
        (!document.referrer.includes("thread.php") &&
          !document.referrer.includes("search.php")) ||
        currentPage <= referrerPage
      ) {
        processedTids.clear();
        console.log("[2048增强插件] 检测到后退或新会话,TID记录已重置。");
      }

      $(window).on("beforeunload", function () {
        try {
          sessionStorage.setItem(
            SESSION_KEY,
            JSON.stringify([...processedTids])
          );
        } catch (e) {
          console.error("[2048增强插件] 保存TID记录失败:", e);
        }
      });

      const $allPostLinks = $(pattern);
      const totalPostCount = $allPostLinks.length;
      let hiddenPostCount = 0;
      const hiddenPostElements = [];

      $allPostLinks.each(function () {
        const $link = $(this);
        const href = $link.attr("href");
        const tidMatch = href ? href.match(/tid=(\d+)/) : null;

        if (tidMatch) {
          const tid = tidMatch[1];
          if (processedTids.has(tid)) {
            $link.closest("tr").hide();
            hiddenPostCount++;
            hiddenPostElements.push(this);
            return;
          }
          processedTids.add(tid);
        }

        if (!$link.hasClass("userscript-processed")) {
          observer.observe(this);
        }

        let hoverTimer;
        $link
          .on("mouseenter", () => {
            if (!$link.hasClass("userscript-processed")) {
              return;
            }
            hoverTimer = setTimeout(() => {
              expandAndShowAllImages($link[0]);
            }, 1000);
          })
          .on("mouseleave", () => {
            clearTimeout(hoverTimer);
          });
      });

      if (hiddenPostCount > 0) {
        console.log(
          `[2048增强插件] 当前页面存在 ${hiddenPostCount} 个重复帖子,已隐藏。`
        );
      }

      const visiblePostCount = totalPostCount - hiddenPostCount;
      if (
        totalPostCount > 0 &&
        (hiddenPostCount === totalPostCount || visiblePostCount < 5)
      ) {
        console.warn(
          `[2048增强插件] 异常去重检测! 总帖:${totalPostCount}, 隐藏:${hiddenPostCount}。触发恢复机制。`
        );
        processedTids.clear();
        sessionStorage.removeItem(SESSION_KEY);
        console.log("[2048增强插件] 已清空所有TID记录。");
        $(hiddenPostElements).each(function () {
          const linkElement = this;
          $(linkElement).closest("tr").show();
          observer.observe(linkElement);
        });
        console.log(
          `[2048增强插件] 已恢复显示 ${hiddenPostElements.length} 个帖子并为其启用内容加载。`
        );
      }
    }
  });
})(jQuery);