减少 ChatGPT 官方网页需要频繁刷新的简单办法

77 min read
 (function() {
const manifestScript = document.querySelector('script[src*="_ssgManifest.js"]');
if (!manifestScript) return;

const heartbeat = Object.assign(document.createElement('iframe'), { style: 'display:none' });
document.head.prepend(heartbeat);

setInterval(() => heartbeat.src = `${manifestScript.src}?${Date.now()}`, 30000);
})();
  1. 首先,它通过查询选择器 document.querySelector('script[src*="_ssgManifest.js"]') 查找 DOM 中包含 "_ssgManifest.js" 的 script 元素。如果找到这个 script 元素,它将其赋值给 manifestScript 变量。
  2. 如果没有找到符合条件的 script 元素(manifestScript 为 null 或 undefined),则函数立即返回并终止执行。
  3. 接下来,它创建一个新的 iframe 元素并将其样式设置为 "display:none",这样 iframe 不会在页面上显示。然后将这个 iframe 元素添加到文档的 head 中。
  4. 函数使用 setInterval 创建一个定时器,每隔 30 秒(30000 毫秒)执行一次。在定时器的回调函数中,它将 iframe 的 src 属性设置为 manifestScript 的 src 属性加上当前时间戳。这样,每隔 30 秒,iframe 会加载一个新的 URL,其中包含最新的时间戳。

这个自执行函数的主要作用是在客户端上定期重新加载 "_ssgManifest.js" 文件,以确保获取到最新的内容。在使用静态站点生成器(如 Next.js)构建的站点中,这种方法可以在不刷新整个页面的情况下,实时更新内容。

// ==UserScript==
// @name         ChatGPT HeartBeat
// @namespace    http://tampermonkey.net/
// @version      0.2.5
// @license      GPLv3
// @description  USE AT YOUR OWN RISK!
// @author       https://v2ex.com/t/926890
// @homepage     https://v2ex.com/t/926890
// @homepageURL  https://v2ex.com/t/926890
// @match        https://chat.openai.com
// @match        https://chat.openai.com/
// @match        https://chat.openai.com/c/*
// @match        https://chat.openai.com/chat
// @match        https://chat.openai.com/chat/*
// @icon         https://chat.openai.com/favicon.ico
// @require      https://greasyfork.org/scripts/395037-monkeyconfig-modern/code/MonkeyConfig%20Modern.js?version=764968
// @run-at       document-start
// @noframes
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_addStyle
// ==/UserScript==
 
/*
  需要保持非常久的,可以额外尝试在 chrome://discards 里禁用 `Auto Discardable`,
  或者用 https://github.com/WorldLanguages/DoNotDiscard
  否则就算保持了 Cookies 有效,Chrome 也有可能自动休眠标签页。
*/
(function () {
    function isWindow(obj) {
        return obj instanceof Window;
    }
 
    // 防止页面通过监听事件强制刷新
    // https://gist.github.com/fuzmish/bd444b1aadc2d22aada7c9b1a6de56ba
    const rawAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (...args) {
        const [eventName] = args;
        if (
            isWindow(this) &&
            ["focus", "focusin", "visibilitychange"].includes(eventName)
        ) {
            return;
        }
        return rawAddEventListener.apply(this, args);
    };
 
    const cfg = new MonkeyConfig({
        title: "Configuration",
        menuCommand: true,
        params: {
            refreshInterval: {
                type: "number",
                default: 30,
            },
            refreshURL: {
                type: "text",
                default:
                "https://chat.openai.com/_next/static/k9OKjvwgjWES7JT3k-6g9/_ssgManifest.js",
            },
        },
    });
 
    function getRefreshURL () {
        var refreshURL = cfg.get("refreshURL");
        // 如果手动配置了 _ssgManifest.js 以外的 URL,就不尝试获取最新的
        if (!refreshURL.endsWith("_ssgManifest.js")) {
            return refreshURL;
        }
        // 获取最新的 _ssgManifest.js 链接
        // https://v2ex.com/t/926890#r_12897849
        const manifestScript = document.querySelector(
            'script[src*="_ssgManifest.js"]'
        );
        if (manifestScript) {
            cfg.set("refreshURL", manifestScript.src);
            return manifestScript.src;
        }
        return refreshURL;
    };
 z
    const heartbeat = document.createElement("iframe");
    heartbeat.style.display = "none";
    document.head.prepend(heartbeat);
 
    let count = 0;
    function refresh() {
        count = 0;
        heartbeat.src = `${getRefreshURL()}?${Date.now()}`;
    }
    setInterval(function () {
        try {
            let current = new URL(heartbeat.contentWindow.location.href);
            let expect = new URL(getRefreshURL());
            if ( heartbeat.contentWindow.location.href === '' ||
                heartbeat.contentWindow.location.href === 'about:blank' ||
                current.pathname === expect.pathname ||
                count++ * cfg.get("refreshInterval") >= 2 * 60) {
                refresh();
            }
        } catch (error) {
            // https://v2ex.com/t/926890#r_12935587
            console.error(error);
            refresh();
        }
    }, cfg.get("refreshInterval") * 1000);
})();
  1. 定义 isWindow 函数,用于检查给定对象是否是一个 Window 对象。
  2. 覆盖 EventTarget.prototype.addEventListener 方法,以防止页面通过监听事件强制刷新。这样做是为了阻止聊天网站在某些情况下自动刷新页面。
  3. 使用 MonkeyConfig 创建一个配置对象,使用户能够自定义刷新间隔和刷新 URL。
  4. 定义 getRefreshURL 函数,用于获取最新的 _ssgManifest.js 链接。如果用户手动配置了其他 URL,将使用用户提供的 URL。
  5. 创建一个隐藏的 iframe 元素,用于在后台定期刷新聊天网站的内容。
  6. 定义 refresh 函数,用于更新 iframe 的 src 属性以刷新聊天网站内容。
  7. 使用 setInterval 设置一个定时器,每隔一段时间检查 iframe 内容是否需要刷新。如果需要,调用 refresh 函数进行刷新。