Facebook & Instagram - Chặn "Đã xem" Story

Chặn "Đã xem" story (Facebook, Instagram). Có menu để bật/tắt.

// ==UserScript==
// @name           Facebook & Instagram - Chặn "Đã xem" Story
// @name:en        Facebook & Instagram - Block "Seen" Story
// @namespace      http://tampermonkey.net/
// @version        2025.08.15
// @description    Chặn "Đã xem" story (Facebook, Instagram). Có menu để bật/tắt.
// @description:en Blocks "Seen" for stories (Facebook, Instagram). With a toggle menu.
// @author         King1x32
// @match          https://*.facebook.com/*
// @match          https://*.messenger.com/*
// @match          https://*.instagram.com/*
// @grant          unsafeWindow
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_registerMenuCommand
// @run-at         document-start
// @license        MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- PHẦN 1: CẤU HÌNH VÀ LƯU/TẢI TRẠNG THÁI ---

    // Đối tượng để lưu trữ tất cả các cài đặt
    let config = {};

    // Hàm tải cấu hình từ bộ nhớ của userscript
    function loadConfig() {
        config = {
            // Mặc định bật tất cả khi người dùng cài lần đầu
            blockFbStorySeen: GM_getValue('blockFbStorySeen', true),
            blockInstaStorySeen: GM_getValue('blockInstaStorySeen', true),
        };
    }

    // Hàm lưu một cài đặt cụ thể
    function saveConfig(key, value) {
        GM_setValue(key, value);
        loadConfig(); // Tải lại cấu hình sau khi lưu
    }

    // Tải cấu hình ngay khi script bắt đầu
    loadConfig();

    // --- PHẦN 2: ĐĂNG KÝ MENU CÀI ĐẶT ---

    // Hàm tạo các nút trong menu của Violentmonkey/Tampermonkey
    function registerMenuCommands() {
        const toggle = (key, name) => {
            const currentState = config[key];
            const stateIcon = currentState ? '✅' : '❌';
            GM_registerMenuCommand(`${stateIcon} ${name}`, () => {
                saveConfig(key, !currentState);
                alert(`Đã ${!currentState ? 'bật' : 'tắt'} tính năng "${name}".\nVui lòng tải lại trang để áp dụng thay đổi.`);
                location.reload();
            });
        };

        toggle('blockFbStorySeen', 'Chặn "Đã xem" Story Facebook');
        toggle('blockInstaStorySeen', 'Chặn "Đã xem" Story Instagram');
    }

    registerMenuCommands();


    // --- PHẦN 3: LOGIC CHẶN (ÁP DỤNG CẤU HÌNH) ---

    // --- AJAX-HOOK (Dành cho Story Facebook & Instagram) ---
    const hook = function(configs = []) {
        const unsubFn = [];
        for (const { fn, arr } of configs) {
            if (typeof fn !== "function" || !Array.isArray(arr)) continue;
            const id = Math.random().toString(36).slice(2);
            arr.push({ fn, id });
            unsubFn.push(() => {
                const index = arr.findIndex((e) => e.id === id);
                if (index !== -1) arr.splice(index, 1);
            });
        }
        return () => {
            unsubFn.forEach((fn) => fn?.());
        };
    }

    const onBeforeOpenXHRFn = [];
    const onBeforeSendXHRFn = [];
    const onAfterSendXHRFn = [];
    let readyXhr = false;

    function initXhr() {
        const orig = unsafeWindow.XMLHttpRequest;
        unsafeWindow.XMLHttpRequest = new Proxy(orig, {
            construct(target, args) {
                const instance = new target(...args);
                let p;

                const open = instance.open;
                instance.open = async function(method, url, async, user, password) {
                    p = { method, url, async, user, password };
                    for (const { fn } of onBeforeOpenXHRFn) {
                        try {
                            const res = await fn?.(p);
                            if (res) p = res;
                            if (res === null) return;
                        } catch (e) {
                            console.error("Userscript Error in onBeforeOpenXHR:", e);
                        }
                    }
                    return open.apply(this, [p.method, p.url, p.async, p.user, p.password]);
                };

                const send = instance.send;
                instance.send = async function(dataSend) {
                    for (const { fn } of onBeforeSendXHRFn) {
                        try {
                            const res = await fn?.(p, dataSend);
                            if (res !== undefined) dataSend = res;
                            if (dataSend === null) return;
                        } catch (e) {
                            console.error("Userscript Error in onBeforeSendXHR:", e);
                        }
                    }

                    instance.addEventListener("load", function() {
                        for (const { fn } of onAfterSendXHRFn)
                            try {
                                fn?.(p, dataSend, instance.response);
                            } catch (e) {
                                console.error("Userscript Error in onAfterSendXHR:", e);
                            }
                    });

                    return send.apply(this, arguments);
                };
                return instance;
            },
        });
    }

    const hookXHR = function({ onBeforeOpen, onBeforeSend, onAfterSend } = {}) {
        if (!readyXhr) {
            initXhr();
            readyXhr = true;
        }
        return hook([
            { fn: onBeforeOpen, arr: onBeforeOpenXHRFn },
            { fn: onBeforeSend, arr: onBeforeSendXHRFn },
            { fn: onAfterSend, arr: onAfterSendXHRFn },
        ]);
    }

    try {
        // Kích hoạt chặn cho Story Facebook và Instagram
        hookXHR({
            onBeforeSend: (p, dataSend) => {
                if (p.method !== "POST") return dataSend;
                const payloadString = dataSend?.toString?.();
                if (!payloadString) return dataSend;

                // Logic cho Facebook Story
                if (config.blockFbStorySeen && payloadString.includes("storiesUpdateSeenStateMutation")) {
                    console.log("ĐÃ CHẶN YÊU CẦU 'ĐÃ XEM' STORY FACEBOOK (theo cài đặt).");
                    return null;
                }

                // Logic cho Instagram Story
                if (config.blockInstaStorySeen && (payloadString.includes("viewSeenAt") || payloadString.includes("SeenMutation"))) {
                    console.log("ĐÃ CHẶN YÊU CẦU 'ĐÃ XEM' STORY INSTAGRAM (theo cài đặt).");
                    return null;
                }

                return dataSend;
            },
        });

    } catch (error) {
        console.error("Userscript đã gặp lỗi:", error);
    }

})();