PTT Web Image Preview (term.ptt.cc)

在 term.ptt.cc 預覽圖片

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         PTT Web Image Preview (term.ptt.cc)
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  在 term.ptt.cc 預覽圖片
// @author       Gemini
// @match        https://term.ptt.cc/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const PREVIEW_ID = 'ptt-center-preview-img';

    // 1. 標準圖片副檔名檢查
    const IMG_EXTENSIONS = /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i;

    // 2. Imgur 網址特徵 (針對沒有副檔名的連結)
    // 支援: imgur.com/ID, i.imgur.com/ID, m.imgur.com/ID
    const IMGUR_PATTERN = /^https?:\/\/(?:i\.|m\.)?imgur\.com\/([a-zA-Z0-9]{5,})(\/?)$/;

    let activeLinkElement = null;
    let watchDogTimer = null;

    /**
     * 判斷並取得真實圖片網址
     * @param {string} url 原始連結網址
     * @returns {string|null} 如果是圖片則回傳直連網址,否則回傳 null
     */
    function getImageUrl(url) {
        // Case A: 網址本身就有圖片副檔名 (例如 .jpg)
        if (IMG_EXTENSIONS.test(url)) {
            return url;
        }

        // Case B: Imgur 網址但沒有副檔名 (例如 https://imgur.com/PA3rQqo)
        const match = url.match(IMGUR_PATTERN);
        if (match) {
            // match[1] 是 ID (例如 PA3rQqo)
            // 強制轉為 i.imgur.com 並加上 .jpg,這樣 Imgur 才會給圖片而不是網頁
            return `https://i.imgur.com/${match[1]}.jpg`;
        }

        return null;
    }

    // 建立或取得預覽圖片元素
    function getPreviewElement() {
        let img = document.getElementById(PREVIEW_ID);
        if (!img) {
            img = document.createElement('img');
            img.id = PREVIEW_ID;

            // 解決 Imgur 403 Forbidden
            img.referrerPolicy = "no-referrer";

            // CSS 樣式
            img.style.position = 'fixed';
            img.style.top = '50%';
            img.style.left = '50%';
            img.style.transform = 'translate(-50%, -50%)';
            img.style.zIndex = '2147483647';
            img.style.maxWidth = '95vw';
            img.style.maxHeight = '95vh';
            img.style.objectFit = 'contain';
            img.style.border = '2px solid rgba(255, 255, 255, 0.5)';
            img.style.borderRadius = '8px';
            img.style.backgroundColor = '#000';
            img.style.boxShadow = '0 0 30px rgba(0,0,0,0.9)';
            img.style.pointerEvents = 'none';
            img.style.display = 'none';

            document.body.appendChild(img);
        }
        return img;
    }

    // 關閉預覽
    function removePreviewImage() {
        const img = document.getElementById(PREVIEW_ID);
        if (img) {
            img.style.display = 'none';
            img.src = '';
        }
        activeLinkElement = null;
        if (watchDogTimer) {
            clearInterval(watchDogTimer);
            watchDogTimer = null;
        }
    }

    // 滑鼠進入連結
    document.addEventListener('mouseover', function(e) {
        const target = e.target.closest('a');

        if (target && target.href) {
            // 透過 getImageUrl 檢查是否為支援的圖片連結
            const imageUrl = getImageUrl(target.href);

            if (imageUrl) {
                if (activeLinkElement === target) return;

                activeLinkElement = target;

                const img = getPreviewElement();
                img.src = imageUrl; // 使用處理過後的網址
                img.style.display = 'block';

                // WatchDog
                if (watchDogTimer) clearInterval(watchDogTimer);
                watchDogTimer = setInterval(() => {
                    if (activeLinkElement && !activeLinkElement.isConnected) {
                        removePreviewImage();
                    }
                }, 200);
            }
        }
    }, true);

    // 滑鼠離開連結
    document.addEventListener('mouseout', function(e) {
        const target = e.target.closest('a');
        if (target && target === activeLinkElement) {
            removePreviewImage();
        }
    }, true);

    // 安全機制
    window.addEventListener('keydown', function() {
        if (activeLinkElement) removePreviewImage();
    }, true);

    window.addEventListener('wheel', function() {
        if (activeLinkElement) removePreviewImage();
    }, true);

})();