[E/Ex-Hentai] AutoLogin

E/Ex - 共享帳號登入、自動獲取 Cookies、手動輸入 Cookies、本地備份以及查看備份,自動檢測登入

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         [E/Ex-Hentai] AutoLogin
// @name:zh-TW   [E/Ex-Hentai] 自動登入
// @name:zh-CN   [E/Ex-Hentai] 自动登入
// @name:ja      [E/Ex-Hentai] 自動ログイン
// @name:ko      [E/Ex-Hentai] 자동 로그인
// @name:ru      [E/Ex-Hentai] Автоматический вход
// @name:en      [E/Ex-Hentai] AutoLogin
// @version      2025.08.23-Beta
// @author       Canaan HS
// @description         E/Ex - 共享帳號登入、自動獲取 Cookies、手動輸入 Cookies、本地備份以及查看備份,自動檢測登入
// @description:zh-TW   E/Ex - 共享帳號登入、自動獲取 Cookies、手動輸入 Cookies、本地備份以及查看備份,自動檢測登入
// @description:zh-CN   E/Ex - 共享帐号登录、自动获取 Cookies、手动输入 Cookies、本地备份以及查看备份,自动检测登录
// @description:ja      E/Ex - 共有アカウントでのログイン、クッキーの自動取得、クッキーの手動入力、ローカルバックアップおよびバックアップの表示、自動ログイン検出
// @description:ko      E/Ex - 공유 계정 로그인, 자동으로 쿠키 가져오기, 쿠키 수동 입력, 로컬 백업 및 백업 보기, 자동 로그인 감지
// @description:ru      E/Ex - Вход в общий аккаунт, автоматическое получение cookies, ручной ввод cookies, локальное резервное копирование и просмотр резервных копий, автоматическое определение входа
// @description:en      E/Ex - Shared account login, automatic cookie retrieval, manual cookie input, local backup, and backup viewing, automatic login detection

// @noframes
// @connect      *
// @match        *://e-hentai.org/*
// @match        *://exhentai.org/*
// @icon         https://e-hentai.org/favicon.ico

// @license      MPL-2.0
// @namespace    https://greasyfork.org/users/989635
// @supportURL   https://github.com/Canaan-HS/MonkeyScript/issues

// @require      https://update.greasyfork.org/scripts/487608/1647211/SyntaxLite_min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js

// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-jgrowl/1.5.1/jquery.jgrowl.min.js
// @resource     jgrowl-css https://cdnjs.cloudflare.com/ajax/libs/jquery-jgrowl/1.5.1/jquery.jgrowl.min.css

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_getResourceText
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_addValueChangeListener

// @run-at       document-start
// ==/UserScript==

(async () => {
    const domain = Lib.$domain;
    const {
        Transl
    } = Language();
    (async function ImportStyle() {
        let show_style, button_style, button_hover, jGrowl_style, acc_style;
        if (domain === "e-hentai.org") {
            button_hover = "color: #8f4701;";
            jGrowl_style = "background-color: #5C0D12; color: #fefefe;";
            show_style = "background-color: #fefefe; border: 3px ridge #34353b;";
            acc_style = "color: #5C0D12; background-color: #fefefe; border: 2px solid #B5A4A4;";
            button_style = "color: #5C0D12; border: 2px solid #B5A4A4; background-color: #fefefe;";
        } else if (domain === "exhentai.org") {
            button_hover = "color: #989898;";
            jGrowl_style = "background-color: #fefefe; color: #5C0D12;";
            show_style = "background-color: #34353b; border: 2px ridge #5C0D12;";
            acc_style = "color: #f1f1f1; background-color: #34353b; border: 2px solid #8d8d8d;";
            button_style = "color: #fefefe; border: 2px solid #8d8d8d; background-color: #34353b;";
            Lib.addStyle(`
                body {
                    padding: 2px;
                    color: #f1f1f1;
                    text-align: center;
                    background: #34353b;
                }
            `);
        }
        Lib.addStyle(`
            ${GM_getResourceText("jgrowl-css")}
            .jGrowl {
                ${jGrowl_style}
                top: 2rem;
                left: 50%;
                width: auto;
                z-index: 9999;
                font-size: 1.3rem;
                border-radius: 2px;
                text-align: center;
                white-space: nowrap;
                transform: translateX(-50%);
            }
            .modal-background {
                top: 50%;
                left: 50%;
                opacity: 0;
                width: 100%;
                height: 100%;
                z-index: 8888;
                overflow: auto;
                position: fixed;
                transition: 0.6s ease;
                background-color: rgba(0,0,0,0);
                transform: translate(-50%, -50%) scale(0.3);
            }
            .acc-modal {
                ${show_style}
                width: 18%;
                overflow: auto;
                margin: 11rem auto;
                border-radius: 10px;
            }
            .acc-select-flex {
                display: flex;
                align-items: center;
                flex-direction: initial;
                justify-content: space-around;
            }
            .acc-button-flex {
                display: flex;
                padding: 0 0 15px 0;
                justify-content: center;
            }
            .acc-select {
                ${acc_style}
                padding: 4px;
                min-width: 10rem;
                margin: 1.1rem 1.4rem 1.5rem 1.4rem;
                font-weight: bold;
                cursor: pointer;
                font-size: 1.2rem;
                text-align: center;
                border-radius: 5px;
            }
            .show-modal {
                ${show_style}
                width: 25%;
                padding: 1.5rem;
                overflow: auto;
                margin: 5rem auto;
                text-align: left;
                border-radius: 10px;
                border-collapse: collapse;
            }
            .modal-button {
                ${button_style}
                top: 0;
                margin: 3% 2%;
                font-size: 14px;
                font-weight: bold;
                border-radius: 3px;
            }
            .modal-button:hover, .modal-button:focus {
                ${button_hover}
                cursor: pointer;
                text-decoration: none;
            }
            .set-modal {
                ${show_style}
                width: 30%;
                padding: 0.3rem;
                overflow: auto;
                border-radius: 10px;
                text-align: center;
                border-collapse: collapse;
                margin: 2% auto 8px auto;
            }
            .set-box {
                display: flex;
                margin: 0.6rem;
                font-weight: bold;
                flex-direction: column;
                align-items: flex-start;
            }
            .set-list {
                width: 95%;
                font-weight: 550;
                font-size: 1.1rem;
                text-align: center;
            }
            hr {
                width: 98%;
                opacity: 0.2;
                border: 1px solid;
                margin-top: 1.3rem;
            }
            label {
                margin: 0.4rem;
                font-size: 0.9rem;
            }
            .cancelFavorite {
                float: left;
                cursor: pointer;
                font-size: 1.7rem;
                padding: 10px 0 0 20px;
            }
            .cancelFavorite:hover {
                opacity: 0.5;
            }
            .addFavorite {
                float: left;
                cursor: pointer;
                font-size: 1.7rem;
                padding: 10px 0 0 20px;
                transition: transform 0.2s ease;
            }
            .addFavorite:hover {
                animation: heartbeat 1.5s infinite;
            }
            @keyframes heartbeat {
                0% {
                    transform: scale(1);
                }
                25% {
                    transform: scale(1.1);
                }
                50% {
                    transform: scale(1);
                }
                75% {
                    transform: scale(1.1);
                }
                100% {
                    transform: scale(1);
                }
            }
            .lc {
                padding: 1rem 0 !important;
            }
            .unFavorite {
                font-size: 2rem;
                position: relative;
                display: inline-block;
                transition: transform 0.2s ease;
            }
            .unFavorite:hover {
                animation: shake 0.8s ease-in-out infinite;
            }
            @keyframes shake {
                0% {
                    left: 0;
                }
                25% {
                    left: -5px;
                }
                50% {
                    left: 5px;
                }
                75% {
                    left: -5px;
                }
                100% {
                    left: 0;
                }
            }
        `, "AutoLogin-Style");
    })();
    (async function Main($Cookie, $Shared) {
        let Share = Lib.getV("Share", {});
        if (typeof Share === "string") {
            Share = JSON.parse(Share);
        }
        const url = Lib.$url;
        const Post_Page = /https:\/\/[^\/]+\/g\/\d+\/[a-zA-Z0-9]+/;
        const Favorites_Page = /https:\/\/[^\/]+\/favorites.php/;
        const CreateMenu = async Modal => {
            Lib.$q(".modal-background")?.remove();
            $("body").append(Modal.replace(/>\s+</g, "><"));
            requestAnimationFrame(() => {
                $(".modal-background").css({
                    opacity: "1",
                    "background-color": "rgba(0,0,0,0.7)",
                    transform: "translate(-50%, -50%) scale(1)"
                });
            });
        };
        const DeleteMenu = async () => {
            const modal = $(".modal-background");
            modal.css({
                opacity: "0",
                "pointer-events": "none",
                "background-color": "rgba(0,0,0,0)",
                transform: "translate(-50%, -50%) scale(0)"
            });
            setTimeout(() => {
                modal.remove();
            }, 1300);
        };
        const Expand = async () => {
            Lib.regMenu({
                [Transl("📜 自動獲取")]: AutoGetCookie,
                [Transl("📝 手動輸入")]: ManualSetting,
                [Transl("🔍 查看保存")]: ViewSaveCookie,
                [Transl("🔃 手動注入")]: CookieInjection,
                [Transl("🗑️ 清除登入")]: ClearLogin
            }, {
                name: "Expand"
            });
        };
        const Collapse = async () => {
            for (let i = 1; i <= 5; i++) {
                Lib.unMenu("Expand-" + i);
            }
        };
        const MenuToggle = async () => {
            const state = Lib.getV("Expand", false), disp = state ? Transl("📁 摺疊菜單") : Transl("📂 展開菜單");
            Lib.regMenu({
                [disp]: {
                    func: () => {
                        state ? Lib.setV("Expand", false) : Lib.setV("Expand", true);
                        MenuToggle();
                    },
                    hotkey: "c",
                    close: false
                }
            }, {
                name: "Switch"
            });
            state ? Expand() : Collapse();
        };
        const LoginToggle = async () => {
            const cookie = Boolean(Lib.getJV("E/Ex_Cookies"));
            const state = Lib.getV("Login", cookie);
            const disp = state ? Transl("🟢 啟用檢測") : Transl("🔴 禁用檢測");
            Lib.regMenu({
                [disp]: {
                    func: () => {
                        if (state) Lib.setV("Login", false); else if (cookie) Lib.setV("Login", true); else {
                            alert(Transl("無保存的 Cookie, 無法啟用自動登入"));
                            return;
                        }
                        LoginToggle();
                    },
                    close: false
                }
            }, {
                name: "Check"
            });
            Lib.regMenu({
                [Transl("🍪 共享登入")]: SharedLogin
            });
            MenuToggle();
        };
        const GlobalMenuToggle = async () => {
            Lib.storeListen(["Login", "Expand"], listen => {
                listen.far && LoginToggle();
            });
        };
        async function Injection() {
            const cookie = Lib.getJV("E/Ex_Cookies");
            const login = Lib.getV("Login", Boolean(cookie));
            if (login && cookie) {
                let CurrentTime = new Date();
                let DetectionTime = Lib.local("DetectionTime");
                DetectionTime = DetectionTime ? new Date(DetectionTime) : new Date(CurrentTime.getTime() + 11 * 60 * 1e3);
                const Conversion = Math.abs(DetectionTime - CurrentTime) / (1e3 * 60);
                if (Conversion >= 10) $Cookie.Verify(cookie);
            }
            if (Post_Page.test(url)) CreateFavoritesButton(); else if (Favorites_Page.test(url)) AddCustomFavorites();
            LoginToggle();
            GlobalMenuToggle();
        }
        async function SharedLogin() {
            const Igneous = $Cookie.Get().igneous;
            const AccountQuantity = Object.keys(Share).length;
            let Select = $(`<select id="account-select" class="acc-select"></select>`), Value;
            for (let i = 1; i <= AccountQuantity; i++) {
                if (Share[i][0].value === Igneous) Value = i;
                Select.append($("<option>").attr({
                    value: i
                }).text(`${Transl("帳戶")} ${i}`));
            }
            CreateMenu(`
                <div class="modal-background">
                    <div class="acc-modal">
                        <h1>${Transl("帳戶選擇")}</h1>
                        <div class="acc-select-flex">${Select.prop("outerHTML")}</div>
                        <div class="acc-button-flex">
                            <button class="modal-button" id="update">${Transl("更新")}</button>
                            <button class="modal-button" id="login">${Transl("登入")}</button>
                        </div>
                    </div>
                </div>
            `);
            if (AccountQuantity === 0) {
                Growl(Transl("首次使用請先更新"), "jGrowl", 2500);
                $("#account-select").append($("<option>")).prop("disabled", true);
            } else if (Value) $("#account-select").val(Value);
            $(".modal-background").on("click", function (click) {
                click.stopImmediatePropagation();
                const target = click.target;
                if (target.id === "login") {
                    $Cookie.ReAdd(Share[+$("#account-select").val()]);
                } else if (target.id === "update") {
                    $Shared.Update().then(Data => {
                        if (Data) {
                            Share = Data;
                            Lib.setJV("Share", Data);
                            setTimeout(SharedLogin, 600);
                        }
                    });
                } else if (target.className === "modal-background") {
                    DeleteMenu();
                }
            });
        }
        async function Cookie_Show(cookies) {
            CreateMenu(`
                <div class="modal-background">
                    <div class="show-modal">
                    <h1 style="text-align: center;">${Transl("確認選擇的 Cookies")}</h1>
                        <pre><b>${JSON.stringify(cookies, null, 4)}</b></pre>
                        <div style="text-align: right;">
                            <button class="modal-button" id="save">${Transl("確認保存")}</button>
                            <button class="modal-button" id="close">${Transl("取消退出")}</button>
                        </div>
                    </div>
                </div>
            `);
            $(".modal-background").on("click", function (click) {
                click.stopImmediatePropagation();
                const target = click.target;
                if (target.id === "save") {
                    Lib.setJV("E/Ex_Cookies", cookies);
                    Growl(Transl("保存成功!"), "jGrowl", 1500);
                    DeleteMenu();
                } else if (target.className === "modal-background" || target.id === "close") {
                    DeleteMenu();
                }
            });
        }
        async function AutoGetCookie() {
            let cookie_box = [];
            for (const [name, value] of Object.entries($Cookie.Get())) {
                cookie_box.push({
                    name: name,
                    value: value
                });
            }
            cookie_box.length > 1 ? Cookie_Show(cookie_box) : alert(Transl("未獲取到 Cookies !!\n\n請先登入帳戶"));
        }
        async function ManualSetting() {
            CreateMenu(`
                <div class="modal-background">
                    <div class="set-modal">
                    <h1>${Transl("設置 Cookies")}</h1>
                        <form id="set_cookies">
                            <div id="input_cookies" class="set-box">
                                <label>[igneous]:</label><input class="set-list" type="text" name="igneous" placeholder="${Transl("要登入 Ex 才需要填寫")}"><br>
                                <label>[ipb_member_id]:</label><input class="set-list" type="text" name="ipb_member_id" placeholder="${Transl("必填項目")}" required><br>
                                <label>[ipb_pass_hash]:</label><input class="set-list" type="text" name="ipb_pass_hash" placeholder="${Transl("必填項目")}" required><hr>
                                <h3>${Transl("下方選填 也可不修改")}</h3>
                                <label>[sl]:</label><input class="set-list" type="text" name="sl" value="dm_2"><br>
                                <label>[sk]:</label><input class="set-list" type="text" name="sk"><br>
                            </div>
                            <button type="submit" class="modal-button" id="save">${Transl("確認保存")}</button>
                            <button class="modal-button" id="close">${Transl("退出選單")}</button>
                        </form>
                    </div>
                </div>
            `);
            let cookie;
            const textarea = $("<textarea>").attr({
                style: "margin: 1.15rem auto 0 auto",
                rows: 18,
                cols: 40,
                readonly: true
            });
            $("#set_cookies").on("submit", function (submit) {
                submit.preventDefault();
                submit.stopImmediatePropagation();
                cookie = Array.from($("#set_cookies .set-list")).map(function (input) {
                    const value = $(input).val();
                    return value.trim() !== "" ? {
                        name: $(input).attr("name"),
                        value: value
                    } : null;
                }).filter(Boolean);
                textarea.val(JSON.stringify(cookie, null, 4));
                $("#set_cookies div").append(textarea);
                Growl(Transl("[確認輸入正確] 按下退出選單保存"), "jGrowl", 2500);
            });
            $(".modal-background").on("click", function (click) {
                click.stopImmediatePropagation();
                const target = click.target;
                if (target.className === "modal-background" || target.id === "close") {
                    click.preventDefault();
                    target.id === "close" && cookie && Lib.setJV("E/Ex_Cookies", cookie);
                    DeleteMenu();
                }
            });
        }
        async function ViewSaveCookie() {
            CreateMenu(`
                <div class="modal-background">
                    <div class="set-modal">
                    <h1>${Transl("當前設置 Cookies")}</h1>
                        <div id="view_cookies" style="margin: 0.6rem"></div>
                        <button class="modal-button" id="save">${Transl("更改保存")}</button>
                        <button class="modal-button" id="close">${Transl("退出選單")}</button>
                    </div>
                </div>
            `);
            const cookie = Lib.getJV("E/Ex_Cookies", {});
            const textarea = $("<textarea>").attr({
                rows: 20,
                cols: 50,
                id: "view_SC",
                style: "margin-top: 1.25rem;"
            });
            textarea.val(JSON.stringify(cookie, null, 4));
            $("#view_cookies").append(textarea);
            $(".modal-background").on("click", function (click) {
                click.stopImmediatePropagation();
                const target = click.target;
                if (target.id === "save") {
                    Lib.setJV("E/Ex_Cookies", JSON.parse($("#view_SC").val()));
                    Growl(Transl("已保存變更"), "jGrowl", 1500);
                    DeleteMenu();
                } else if (target.className === "modal-background" || target.id === "close") {
                    DeleteMenu();
                }
            });
        }
        async function CookieInjection() {
            try {
                const cookie = Lib.getJV("E/Ex_Cookies");
                if (cookie === null) throw new Error("No Cookies");
                $Cookie.ReAdd(cookie);
            } catch (error) {
                alert(Transl("未檢測到可注入的 Cookies !!\n\n請從選單中進行設置"));
            }
        }
        async function ClearLogin() {
            $Cookie.Delete();
            location.reload();
        }
        function CreateFavoritesButton() {
            Lib.waitEl(["#gd1 div", "#gd2", "#gmid"], ([thumbnail, container, info]) => {
                const path = location.pathname;
                const save_key = md5(path);
                const Favorites = Lib.getV("Favorites", {});
                const favorite = Favorites[save_key];
                const addfavorite = async Favorites => {
                    return new Promise((resolve, reject) => {
                        try {
                            const img = getComputedStyle(thumbnail);
                            const score = getComputedStyle(info.$q(".ir"));
                            const icon = info.$q("#gdc div");
                            const artist = info.$q("#gdn a");
                            const title = container.$q("#gj").$text() || container.$q("#gn").$text();
                            const [, gid, tid] = path.match(/\/g\/([^\/]+)\/([^\/]+)\//);
                            const detail = info.$q("#gdd");
                            const posted = detail.$q("tr:nth-child(1) .gdt2").$text();
                            const length = detail.$q("tr:nth-child(6) .gdt2").$text();
                            const tagData = new Map();
                            for (const a of info.$qa("#taglist tr a")) {
                                const tags = a.id.slice(3).replace(/[_]/g, " ").split(":");
                                if (!tagData.has(tags[0])) tagData.set(tags[0], []);
                                tagData.get(tags[0]).push(tags[1]);
                            }
                            const data = JSON.stringify({
                                gid: gid,
                                tid: tid,
                                domain: domain,
                                posted: posted,
                                length: length,
                                key: save_key,
                                tags: [...tagData],
                                score: score.backgroundPosition,
                                post_title: title,
                                artist_link: artist.href,
                                artist_text: artist.$text(),
                                icon_text: icon.$text(),
                                icon_class: icon.className,
                                img_width: img.width,
                                img_height: img.height,
                                img_url: img.background.match(/url\(["']?(.*?)["']?\)/)[1],
                                favorited_time: Lib.getDate("{year}-{month}-{date} {hour}:{minute}")
                            });
                            Lib.setV("Favorites", Object.assign(Favorites, {
                                [save_key]: LZString.compress(data, 9)
                            }));
                            resolve();
                        } catch (error) {
                            console.error(error);
                            reject();
                        }
                    });
                };
                favorite && addfavorite(Favorites);
                const favoriteButton = Lib.createElement(container, "div", {
                    class: favorite ? "cancelFavorite" : "addFavorite",
                    text: favorite ? Transl("💘 取消收藏") : Transl("💖 添加收藏"),
                    on: {
                        type: "click",
                        listener: () => {
                            const Favorites = Lib.getV("Favorites", {});
                            if (Favorites[save_key]) {
                                delete Favorites[save_key];
                                Lib.setV("Favorites", Favorites);
                                favoriteButton.$text(Transl("💖 添加收藏"));
                                favoriteButton.$replaceClass("cancelFavorite", "addFavorite");
                                return;
                            }
                            addfavorite(Favorites).then(() => {
                                favoriteButton.$text(Transl("💘 取消收藏"));
                                favoriteButton.$replaceClass("addFavorite", "cancelFavorite");
                            });
                        }
                    }
                });
            }, {
                raf: true
            });
        }
        function httpRequest(url, func) {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                responseType: "document",
                onload: response => {
                    if (response.status === 200) {
                        func(response.response);
                    }
                }
            });
        }
        function AddCustomFavorites() {
            const Favorites = Lib.getV("Favorites");
            if (Favorites && Object.keys(Favorites).length > 0) {
                Lib.waitEl(".ido", ido => {
                    let delete_object = "tr";
                    const select = ido.$q(".searchnav div:last-of-type select option[selected='selected']");
                    const usertags = {};
                    const favoritDB = Object.values(Favorites);
                    const mode = !select ? "t" : select.value;
                    if (!select) {
                        const newform = Lib.createElement("form", {
                            id: "favform",
                            name: "favform",
                            action: "",
                            method: "post",
                            innerHTML: `<input id="ddact" name="ddact" type="hidden" value=""><div class="itg gld"></div>`
                        });
                        ido.appendChild(newform);
                    }
                    if (mode === "t") delete_object = ".gl1t";
                    const RenderTags = async function () {
                        const nodes = [];
                        const tree = document.createTreeWalker(ido, NodeFilter.SHOW_TEXT, {
                            acceptNode: node => {
                                const parent = node.parentNode;
                                if (parent?.nodeName === "DIV" && parent.hasAttribute("title") && !parent.hasAttribute("id")) {
                                    return NodeFilter.FILTER_ACCEPT;
                                }
                                return NodeFilter.FILTER_REJECT;
                            }
                        });
                        while (tree.nextNode()) {
                            nodes.push(tree.currentNode.parentElement);
                        }
                        nodes.forEach(node => {
                            const tags = usertags[node.title];
                            tags && (node.style.cssText = tags.cssText);
                        });
                    };
                    const GetTags = async function () {
                        if (Object.keys(usertags).length > 0) {
                            RenderTags();
                            return;
                        }
                        httpRequest("https://exhentai.org/mytags", root => {
                            for (const user of root.$qa("div[id^='usertag_']:not(#usertag_0)")) {
                                const input = user.$q("div:nth-of-type(2) input");
                                if (input.checked) {
                                    const tag = user.$q("div.gt");
                                    usertags[tag.title] = tag.style;
                                }
                            }
                            RenderTags();
                        });
                    };
                    let count = 0;
                    const fragment = Lib.createFragment;
                    const RenderWait = requestIdleCallback || ((cb, _) => requestAnimationFrame(cb));
                    const RenderCard = async function () {
                        if (fragment.hasChildNodes()) {
                            ido.$q("tbody")?.prepend(fragment);
                            ido.$q("#favform .gld")?.prepend(fragment);
                            requestAnimationFrame(GetTags);
                        }
                    };
                    for (const data of favoritDB) {
                        const json = JSON.parse(LZString.decompress(data));
                        const Pages = `<div>${json.length}</div>`;
                        const PostUrl = `<a href="https://${json.domain}/g/${json.gid}/${json.tid}/">`;
                        const PostName = `<div class="glink">${json.post_title}</div>`;
                        const Glfnote = `<div class="glfnote" style="display:none" id="favnote_${json.gid}"></div>`;
                        const Thumbnail = `<div class="${json.icon_class}">${json.icon_text}</div>`;
                        const ThumbnailCN = Thumbnail.replace('class="cs', 'class="cn');
                        const Position = `<div class="ir" style="background-position:${json.score};opacity:1"></div>`;
                        const PreviewImg = `<img style="height:${json.img_height}; width:${json.img_width};" alt="${json.post_title}" title="${json.post_title}" src="${json.img_url}">`;
                        const FullPreview = `
                            <div class="glcut" id="ic${json.gid}"></div>
                                <div class="glthumb" id="it${json.gid}" style="top:-179px;height:400px">
                                <div>${PreviewImg}</div>
                        `;
                        const Posted = `
                            <div style="border-color:#000;background-color:rgba(0,0,0,.1)"
                                onclick="popUp('https://${json.domain}/gallerypopups.php?gid=${json.gid}&amp;t=${json.tid}&amp;act=addfav',675,415)"
                                id="posted_${json.gid}" title="Favorites 0">${json.posted}
                            </div>
                        `;
                        const Postedpop = Posted.replace("posted_", "postedpop_");
                        const Gldown = `
                            <div class="gldown">
                                <a href="https://${json.domain}/gallerytorrents.php?gid=${json.gid}&amp;t=${json.tid}"
                                    onclick="return popUp('https://${json.domain}/gallerytorrents.php?gid=${json.gid}&amp;t=${json.tid}',610,590)"
                                    rel="nofollow"><img src="https://${json.domain}/img/t.png" alt="T" title="Show torrents">
                                </a>
                            </div>
                        `;
                        const unFavorite = `
                            <div class="lc">
                                <div id="${json.key}" class="unFavorite">💔</div>
                            </div>
                        `;
                        if (mode === "m" || mode === "p") {
                            const tr = Lib.createElement("tr");
                            tr.$iHtml(`
                                <td class="gl1m glcat">${Thumbnail}</td>
                                <td class="gl2m">
                                    ${FullPreview}
                                        <div>
                                            <div>
                                                ${Thumbnail}
                                                ${Postedpop}
                                            </div>
                                            <div>
                                                ${Position}
                                                ${Pages}
                                            </div>
                                        </div>
                                    </div>
                                    ${Posted}
                                </td>
                                <td class="gl6m">${Gldown}</td>
                                <td class="gl3m glname" onmouseover="show_image_pane(${json.gid});preload_pane_image(0,0)" onmouseout="hide_image_pane()">
                                    ${PostUrl}
                                        ${PostName}
                                        ${Glfnote}
                                    </a>
                                </td>
                                <td class="gl4m">
                                    ${Position}
                                </td>
                                <td class="glfm glfav">${json.favorited_time}</td>
                                <td class="glfm" style="text-align:center; padding-left:3px">
                                    ${unFavorite}
                                </td>
                            `.replace(/>\s+</g, "><"));
                            fragment.prepend(tr);
                        } else if (mode === "l") {
                            const tr = Lib.createElement("tr");
                            const posted = json.posted.split(" ");
                            tr.$iHtml(`
                                <tr>
                                    <td class="gl1c glcat">${ThumbnailCN}</td>
                                    <td class="gl2c">
                                        ${FullPreview}
                                            <div>
                                                <div>
                                                    ${ThumbnailCN}
                                                    ${Postedpop}
                                                </div>
                                                <div>
                                                    ${Position}
                                                    ${Pages}
                                                </div>
                                            </div>
                                        </div>
                                        <div>
                                            ${Posted}
                                            ${Position}
                                            ${Gldown}
                                        </div>
                                    </td>
                                    <td class="gl3c glname" onmouseover="show_image_pane(${json.gid});preload_pane_image(0,0)" onmouseout="hide_image_pane()">
                                        ${PostUrl}
                                            ${PostName}
                                            <div>
                                                ${(() => {
                                    let count = 0;
                                    let result = "";
                                    for (const [tagCategory, tagList] of json.tags) {
                                        for (const tag of tagList) {
                                            if (count >= 10) break;
                                            result += `<div class="gt" title="${tagCategory}:${tag}">${tag}</div>`;
                                            count++;
                                        }
                                        if (count >= 10) break;
                                    }
                                    return result;
                                })()}
                                            </div>
                                            ${Glfnote}
                                        </a>
                                    </td>
                                    <td class="glfc glfav">
                                        <p>${posted[0]}</p>
                                        <p>${posted[1]}</p>
                                    </td>
                                    <td class="glfc" style="text-align:center; padding-left:3px">
                                        ${unFavorite}
                                    </td>
                                </tr>
                            `.replace(/>\s+</g, "><"));
                            fragment.prepend(tr);
                        } else if (mode === "e") {
                            const tr = Lib.createElement("tr");
                            tr.$iHtml(`
                                <tr>
                                    <td class="gl1e" style="width:250px">
                                        <div style="height: ${json.img_height}; width:250px">
                                            ${PostUrl}
                                                ${PreviewImg}
                                            </a>
                                        </div>
                                    </td>
                                    <td class="gl2e">
                                        <div>
                                            <div class="gl3e">
                                                ${ThumbnailCN}
                                                ${Posted}
                                                ${Position}
                                                <div><a href="${json.artist_link}">${json.artist_text}</a></div>
                                                ${Pages}
                                                ${Gldown}
                                            <div>
                                                <p>Favorited:</p><p>${json.favorited_time}</p>
                                            </div>
                                            </div>
                                            ${PostUrl}
                                                <div class="gl4e glname" style="min-height:${json.img_height}">
                                                    ${PostName}
                                                    <div>
                                                        <table>
                                                            <tbody>
                                                                ${json.tags.map(([tagCategory, tagList]) => {
                                return `
                                                                        <tr>
                                                                            <td class="tc">${tagCategory}</td>
                                                                            <td>
                                                                                ${tagList.map(tag => `<div class="gtl" title="${tagCategory}:${tag}">${tag}</div>`).join("")}
                                                                            </td>
                                                                        </tr>
                                                                    `;
                            }).join("")}
                                                            </tbody>
                                                        </table>
                                                    </div>
                                                    ${Glfnote}
                                                </div>
                                            </a>
                                        </div>
                                    </td>
                                    <td class="glfe" style="text-align:center; padding-left:8px">
                                        ${unFavorite}
                                    </td>
                                </tr>
                            `.replace(/>\s+</g, "><"));
                            fragment.prepend(tr);
                        } else if (mode === "t") {
                            const div = Lib.createElement("div", {
                                class: "gl1t"
                            });
                            div.$iHtml(`
                                <div class="gl4t glname glft">
                                    <div>
                                        ${PostUrl}
                                            <span class="glink">${json.post_title}</span>
                                        </a>
                                    </div>
                                    <div style="transform: translateY(-70%);">
                                        ${unFavorite}
                                    </div>
                                </div>
                                <div class="gl3t" style="height: ${json.img_height}; width:250px">
                                    ${PostUrl}
                                        ${PreviewImg}
                                    </a>
                                </div>
                                ${Glfnote}
                                <div class="gl5t">
                                    <div>
                                        ${Thumbnail}
                                        ${Posted}
                                    </div>
                                    <div>
                                        ${Position}
                                        ${Pages}
                                        ${Gldown}
                                    </div>
                                </div>
                            `.replace(/>\s+</g, "><"));
                            fragment.prepend(div);
                        }
                        ++count;
                        if (count === 50) {
                            count = 0;
                            RenderWait(RenderCard, {
                                timeout: 1e3
                            });
                        }
                    }
                    RenderCard();
                    Lib.onEvent(ido, "click", event => {
                        const target = event.target;
                        if (target.className === "unFavorite") {
                            const Favorites = Lib.getV("Favorites");
                            delete Favorites[target.id];
                            Lib.setV("Favorites", Favorites);
                            target.closest(delete_object).remove();
                        }
                    });
                });
            }
        }
        return {
            Injection: Injection
        };
    })(CookieFactory(), SharedFactory()).then(Main => {
        Main.Injection();
    });
    async function Growl(message, theme, life) {
        $.jGrowl(`&emsp;&emsp;${message}&emsp;&emsp;`, {
            theme: theme,
            life: life,
            speed: "slow"
        });
    }
    function SharedFactory() {
        async function Get() {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    responseType: "json",
                    url: "https://raw.githubusercontent.com/Canaan-HS/Script-DataBase/refs/heads/main/Share/ExShare.json",
                    onload: response => {
                        if (response.status === 200) {
                            const data = response.response;
                            if (typeof data === "object" && Object.keys(data).length > 0) {
                                resolve(data);
                            } else {
                                console.error(Transl("請求為空數據"));
                                resolve({});
                            }
                        } else {
                            console.error(Transl("連線異常,更新地址可能是錯的"));
                            resolve({});
                        }
                    },
                    onerror: error => {
                        console.error(Transl("請求錯誤: "), error);
                        resolve({});
                    }
                });
            });
        }
        async function Update() {
            const Shared = await Get();
            if (Object.keys(Shared).length > 0) {
                const localHash = md5(Lib.getV("Share", ""));
                const remoteHash = md5(JSON.stringify(Shared));
                if (localHash !== remoteHash) {
                    Growl(Transl("共享數據更新完成"), "jGrowl", 1500);
                    return Shared;
                } else {
                    Growl(Transl("共享數據無需更新"), "jGrowl", 1500);
                }
            } else {
                Growl(Transl("共享數據獲取失敗"), "jGrowl", 2500);
            }
            return false;
        }
        return {
            Update: Update
        };
    }
    function CookieFactory() {
        const Today = new Date();
        Today.setFullYear(Today.getFullYear() + 1);
        const Expires = Today.toUTCString();
        const UnixUTC = new Date(0).toUTCString();
        let RequiredCookie = ["ipb_member_id", "ipb_pass_hash"];
        if (domain == "exhentai.org") RequiredCookie.unshift("igneous");
        return {
            Get: () => {
                return Lib.cookie().split("; ").reduce((acc, cookie) => {
                    const [name, value] = cookie.split("=");
                    acc[decodeURIComponent(name)] = decodeURIComponent(value);
                    return acc;
                }, {});
            },
            Add: function (CookieObject) {
                Lib.local("DetectionTime", {
                    value: Lib.getDate()
                });
                for (const Cookie of CookieObject) {
                    Lib.cookie(`${encodeURIComponent(Cookie.name)}=${encodeURIComponent(Cookie.value)}; domain=.${domain}; path=/; expires=${Expires};`);
                }
                location.reload();
            },
            Delete: function () {
                Object.keys(this.Get()).forEach(Name => {
                    Lib.cookie(`${Name}=; expires=${UnixUTC}; path=/;`);
                    Lib.cookie(`${Name}=; expires=${UnixUTC}; path=/; domain=.${domain}`);
                });
            },
            ReAdd: function (Cookies) {
                this.Delete();
                this.Add(Cookies);
            },
            Verify: function (Cookies) {
                const Cookie = this.Get();
                const VCookie = new Set(Object.keys(Cookie));
                const Result = RequiredCookie.every(key => VCookie.has(key) && Cookie[key] !== "mystery");
                if (!Result) {
                    this.ReAdd(Cookies);
                } else {
                    Lib.local("DetectionTime", {
                        value: Lib.getDate()
                    });
                }
            }
        };
    }
    function Language() {
        const Word = Lib.translMatcher({
            Traditional: {},
            Simplified: {
                "🍪 共享登入": "🍪 共享登录",
                "🟢 啟用檢測": "🟢 启用检测",
                "🔴 禁用檢測": "🔴 禁用检测",
                "📂 展開菜單": "📂 展开菜单",
                "📁 摺疊菜單": "📁 折叠菜单",
                "📜 自動獲取": "📜 自动获取",
                "📝 手動輸入": "📝 手动输入",
                "🔍 查看保存": "🔍 查看已保存",
                "🔃 手動注入": "🔃 手动注入",
                "🗑️ 清除登入": "🗑️ 清除登录信息",
                "💖 添加收藏": "💖 添加收藏",
                "💘 取消收藏": "💘 取消收藏",
                "帳戶": "账号",
                "更新": "更新",
                "登入": "登录",
                "首次使用請先更新": "首次使用请先更新",
                "確認選擇的 Cookies": "确认所选 Cookies",
                "確認保存": "确认保存",
                "取消退出": "取消",
                "退出選單": "关闭菜单",
                "保存成功!": "保存成功!",
                "更改保存": "保存更改",
                "已保存變更": "更改已保存",
                "設置 Cookies": "设置 Cookies",
                "要登入 Ex 才需要填寫": "仅登录 Ex 时需要填写",
                "必填項目": "必填项",
                "下方選填 也可不修改": "以下为选填项,可不修改",
                "[確認輸入正確] 按下退出選單保存": "[确认输入无误] 点击关闭菜单保存",
                "當前設置 Cookies": "当前 Cookies 设置",
                "帳戶選擇": "选择账号",
                "未獲取到 Cookies !!\n\n請先登入帳戶": "未获取到 Cookies!\n\n请先登录账号",
                "未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "未检测到可注入的 Cookies!\n\n请在菜单中进行设置",
                "共享數據更新完成": "共享数据更新完成",
                "共享數據無需更新": "共享数据无需更新",
                "共享數據獲取失敗": "共享数据获取失败",
                "無保存的 Cookie, 無法啟用自動登入": "没有已保存的 Cookie,无法启用自动登录",
                "請求為空數據": "请求数据为空",
                "連線異常,更新地址可能是錯的": "连接异常,更新地址可能不正确",
                "請求錯誤: ": "请求错误:"
            },
            Japan: {
                "🍪 共享登入": "🍪 共有ログイン",
                "🟢 啟用檢測": "🟢 検出を有効化",
                "🔴 禁用檢測": "🔴 検出を無効化",
                "📂 展開菜單": "📂 メニュー展開",
                "📁 摺疊菜單": "📁 メニュー折りたたみ",
                "📜 自動獲取": "📜 自動取得",
                "📝 手動輸入": "📝 手動入力",
                "🔍 查看保存": "🔍 保存を表示",
                "🔃 手動注入": "🔃 手動注入",
                "🗑️ 清除登入": "🗑️ ログインをクリア",
                "💖 添加收藏": "💖 お気に入りに追加",
                "💘 取消收藏": "💘 お気に入りから削除",
                "帳戶": "アカウント",
                "更新": "更新",
                "登入": "ログイン",
                "首次使用請先更新": "初めてご利用の際は、先に更新してください",
                "確認選擇的 Cookies": "選択したCookieを確認",
                "確認保存": "保存を確認",
                "取消退出": "終了をキャンセル",
                "退出選單": "メニューを終了",
                "保存成功!": "保存に成功しました!",
                "更改保存": "変更を保存",
                "已保存變更": "変更が保存されました",
                "設置 Cookies": "Cookieを設定",
                "要登入 Ex 才需要填寫": "Exログインにのみ必要",
                "必填項目": "必須項目",
                "下方選填 也可不修改": "以下は任意、変更しなくても構いません",
                "[確認輸入正確] 按下退出選單保存": "[入力が正しいことを確認] メニュー終了を押して保存",
                "當前設置 Cookies": "現在のCookie設定",
                "帳戶選擇": "アカウント選択",
                "未獲取到 Cookies !!\n\n請先登入帳戶": "Cookieを取得できませんでした!\n\nまずアカウントにログインしてください",
                "未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "注入可能なCookieが検出されませんでした!\n\nメニューから設定してください",
                "共享數據更新完成": "共有データの更新が完了しました",
                "共享數據無需更新": "共有データの更新は不要です",
                "共享數據獲取失敗": "共有データの取得に失敗しました",
                "無保存的 Cookie, 無法啟用自動登入": "保存されたCookieがないため、自動ログインを有効にできません",
                "請求為空數據": "リクエストにデータがありません",
                "連線異常,更新地址可能是錯的": "接続エラー、更新アドレスが間違っている可能性があります",
                "請求錯誤: ": "リクエストエラー: "
            },
            Korea: {
                "🍪 共享登入": "🍪 공유 로그인",
                "🟢 啟用檢測": "🟢 감지 활성화",
                "🔴 禁用檢測": "🔴 감지 비활성화",
                "📂 展開菜單": "📂 메뉴 펼치기",
                "📁 摺疊菜單": "📁 메뉴 접기",
                "📜 自動獲取": "📜 자동 가져오기",
                "📝 手動輸入": "📝 수동 입력",
                "🔍 查看保存": "🔍 저장된 항목 보기",
                "🔃 手動注入": "🔃 수동 주입",
                "🗑️ 清除登入": "🗑️ 로그인 정보 삭제",
                "💖 添加收藏": "💖 즐겨찾기에 추가",
                "💘 取消收藏": "💘 즐겨찾기 제거",
                "確認選擇的 Cookies": "선택한 쿠키 확인",
                "帳戶": "계정",
                "更新": "업데이트",
                "登入": "로그인",
                "首次使用請先更新": "처음 사용하기 전에 먼저 업데이트해 주세요",
                "確認保存": "저장 확인",
                "取消退出": "종료 취소",
                "退出選單": "메뉴 종료",
                "保存成功!": "저장 성공!",
                "更改保存": "변경사항 저장",
                "已保存變更": "변경사항이 저장되었습니다",
                "設置 Cookies": "쿠키 설정",
                "要登入 Ex 才需要填寫": "Ex 로그인에만 필요",
                "必填項目": "필수 항목",
                "下方選填 也可不修改": "아래는 선택사항, 변경하지 않아도 됩니다",
                "[確認輸入正確] 按下退出選單保存": "[입력이 정확한지 확인] 메뉴 종료를 눌러 저장",
                "當前設置 Cookies": "현재 설정된 쿠키",
                "帳戶選擇": "계정 선택",
                "未獲取到 Cookies !!\n\n請先登入帳戶": "쿠키를 가져오지 못했습니다!\n\n먼저 계정에 로그인해 주세요",
                "未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "주입 가능한 쿠키가 감지되지 않았습니다!\n\n메뉴에서 설정해 주세요",
                "共享數據更新完成": "공유 데이터 업데이트 완료",
                "共享數據無需更新": "공유 데이터 업데이트 불필요",
                "共享數據獲取失敗": "공유 데이터 가져오기 실패",
                "無保存的 Cookie, 無法啟用自動登入": "저장된 쿠키가 없어 자동 로그인을 활성화할 수 없습니다",
                "請求為空數據": "요청에 데이터가 없습니다",
                "連線異常,更新地址可能是錯的": "연결 오류, 업데이트 주소가 잘못되었을 수 있습니다",
                "請求錯誤: ": "요청 오류: "
            },
            Russia: {
                "🍪 共享登入": "🍪 Общий вход",
                "🟢 啟用檢測": "🟢 Включить обнаружение",
                "🔴 禁用檢測": "🔴 Отключить обнаружение",
                "📂 展開菜單": "📂 Развернуть меню",
                "📁 摺疊菜單": "📁 Свернуть меню",
                "📜 自動獲取": "📜 Автоматическое получение",
                "📝 手動輸入": "📝 Ручной ввод",
                "🔍 查看保存": "🔍 Просмотр сохраненного",
                "🔃 手動注入": "🔃 Ручное внедрение",
                "🗑️ 清除登入": "🗑️ Очистить вход",
                "💖 添加收藏": "💖 Добавить в избранное",
                "💘 取消收藏": "💘 Удалить из избранного",
                "帳戶": "Аккаунт",
                "更新": "Обновить",
                "登入": "Войти",
                "首次使用請先更新": "Пожалуйста, обновите перед первым использованием",
                "確認選擇的 Cookies": "Подтвердить выбранные Cookies",
                "確認保存": "Подтвердить сохранение",
                "取消退出": "Отменить выход",
                "退出選單": "Выйти из меню",
                "保存成功!": "Сохранение успешно!",
                "更改保存": "Сохранить изменения",
                "已保存變更": "Изменения сохранены",
                "設置 Cookies": "Настройка Cookies",
                "要登入 Ex 才需要填寫": "Требуется только для входа в Ex",
                "必填項目": "Обязательное поле",
                "下方選填 也可不修改": "Необязательно ниже, изменения не требуются",
                "[確認輸入正確] 按下退出選單保存": "[Подтвердите правильность ввода] Нажмите Выйти из меню для сохранения",
                "當前設置 Cookies": "Текущие настройки Cookies",
                "帳戶選擇": "Выбор аккаунта",
                "未獲取到 Cookies !!\n\n請先登入帳戶": "Cookies не получены !!\n\nПожалуйста, сначала войдите в аккаунт",
                "未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "Не обнаружены Cookies для внедрения !!\n\nПожалуйста, настройте в меню",
                "共享數據更新完成": "Обновление общих данных завершено",
                "共享數據無需更新": "Обновление общих данных не требуется",
                "共享數據獲取失敗": "Ошибка получения общих данных",
                "無保存的 Cookie, 無法啟用自動登入": "Нет сохраненных cookies, невозможно включить автоматический вход",
                "請求為空數據": "Запрос содержит пустые данные",
                "連線異常,更新地址可能是錯的": "Ошибка соединения, адрес обновления может быть неверным",
                "請求錯誤: ": "Ошибка запроса: "
            },
            English: {
                "🍪 共享登入": "🍪 Shared Login",
                "🟢 啟用檢測": "🟢 Enable Detection",
                "🔴 禁用檢測": "🔴 Disable Detection",
                "📂 展開菜單": "📂 Expand Menu",
                "📁 摺疊菜單": "📁 Collapse Menu",
                "📜 自動獲取": "📜 Auto Retrieve",
                "📝 手動輸入": "📝 Manual Input",
                "🔍 查看保存": "🔍 View Saved",
                "🔃 手動注入": "🔃 Manual Injection",
                "🗑️ 清除登入": "🗑️ Clear Login",
                "💖 添加收藏": "💖 Add to Favorites",
                "💘 取消收藏": "💘 Remove from Favorites",
                "帳戶": "Account",
                "更新": "Update",
                "登入": "Login",
                "首次使用請先更新": "Please update before first use",
                "確認選擇的 Cookies": "Confirm Selected Cookies",
                "確認保存": "Confirm Save",
                "取消退出": "Cancel Exit",
                "退出選單": "Exit Menu",
                "保存成功!": "Save Successful!",
                "更改保存": "Save Changes",
                "已保存變更": "Changes Saved",
                "設置 Cookies": "Set Cookies",
                "要登入 Ex 才需要填寫": "Required for Ex Login Only",
                "必填項目": "Required Field",
                "下方選填 也可不修改": "Optional Fields Below - No Changes Required",
                "[確認輸入正確] 按下退出選單保存": "[Confirm Input is Correct] Press Exit Menu to Save",
                "當前設置 Cookies": "Current Cookie Settings",
                "帳戶選擇": "Account Selection",
                "未獲取到 Cookies !!\n\n請先登入帳戶": "No Cookies Retrieved!\n\nPlease Login First",
                "未檢測到可注入的 Cookies !!\n\n請從選單中進行設置": "No Injectable Cookies Detected!\n\nPlease Configure in Menu",
                "共享數據更新完成": "Shared Data Update Complete",
                "共享數據無需更新": "Shared Data Update Not Needed",
                "共享數據獲取失敗": "Shared Data Retrieval Failed",
                "無保存的 Cookie, 無法啟用自動登入": "No Saved Cookies - Unable to Enable Auto-Login",
                "請求為空數據": "Request Contains No Data",
                "連線異常,更新地址可能是錯的": "Connection Error - Update Address May Be Incorrect",
                "請求錯誤: ": "Request Error: "
            }
        });
        return {
            Transl: Str => Word[Str] ?? Str
        };
    }
})();