심야식당 도우미

심야식당을 좀 더 편하고 내 입맛에 맞게 이용

当前为 2024-05-05 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         심야식당 도우미
// @description  심야식당을 좀 더 편하고 내 입맛에 맞게 이용
// @version      0.3.2
// @author       Yoonu
// @match        https://arca.live/b/*
// @match        https://kioskloud.io/e/*
// @match        https://kiosk.ac/c/*
// @match        https://mega.nz/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=arca.live
// @grant        GM.setValue
// @grant        GM.getValue
// @license      MIT
// @namespace https://greasyfork.org/users/64556
// ==/UserScript==

(async function() {
    'use strict';
    const RULE_PASSWORD = atob("c21wZW9wbGU=");

    const arcalive = async () => {
        const settings = {
            switch: {
                passwordDecoder: await GM.getValue("passwordDecoder", true),
                base64Decoder: await GM.getValue("base64Decoder", true),
                imageOriginalizer: await GM.getValue("imageOriginalizer", true),
            },
            passwordDecoder: {
                id: "arca_eh_password",
                origin: "국룰",
                rulePassword: await GM.getValue("rulePassword"),
            },
            pageMaxWidth: await GM.getValue("pageMaxWidth", "1800px"),
        };

        const styles = {
            password: "color: rgb(61, 142, 185); cursor: pointer; font-weight: bold;",
            copiedPassword: "color: rgb(61, 142, 185); cursor: pointer;",
        };

        // Change content area width
        const wrapper = document.querySelector("div.content-wrapper");
        if(settings.pageMaxWidth) wrapper.style.maxWidth = settings.pageMaxWidth;

        // Get article
        const article = wrapper.querySelector("div.article-body > div.article-content");
        if(!article) return;

        let articleHtml = article.innerHTML;

        // Decode base64
        if(settings.switch.base64Decoder) {
            articleHtml = articleHtml.replace(/(YUhSMGN|aHR0c)[0-9A-Za-z+]{8,}[=]{0,2}/g, (matcher) => {
                let decoded;
                try {
                    decoded = atob(matcher.startsWith("Y") ? atob(matcher) : matcher);
                    return `<a href="${decoded}" target="_blank">${decoded}</a>`;
                } catch(e) {
                    return matcher + `<span style="font-size: 9pt; color: red;" title="${e}">코드 복호화 실패</span>`;
                }
            });
        }

        // Find password
        if(settings.switch.passwordDecoder) {
            if(settings.passwordDecoder.rulePassword !== RULE_PASSWORD) {
                const inputText = prompt("국룰 확인");

                if(inputText?.toLowerCase() === RULE_PASSWORD) {
                    GM.setValue("rulePassword", RULE_PASSWORD);
                    settings.passwordDecoder.rulePassword = RULE_PASSWORD;
                } else {
                    GM.setValue("passwordDecoder", false);
                    alert("국룰 비밀번호가 일치하지 않습니다. 국룰 해석 기능을 비활성화합니다.");
                }
            }

            if(settings.passwordDecoder.rulePassword === RULE_PASSWORD) {
                articleHtml = articleHtml.replace(/&nbsp;/g, " ").replace(new RegExp("[^\\s>]*" + settings.passwordDecoder.origin + "[^\\s\n<]*"), (matcher) => {
                    let preText = "", postText = "";

                    matcher = matcher.replace(/(비밀번호|암호|비번|패스워드)?(\s*[-:=]*\s*)([은|는])?국룰(과|은|을|이|이며|이고|임|입니다|임다|이다)?([\.,\(\[])?$/g, (matcher2, ...args) => {
                        preText = (args[0] || "") + (args[1] || "") + (args[2] || "");
                        postText = (args[3] || "") + (args[4] || "");
                        return settings.passwordDecoder.origin;
                    });

                    const id = settings.passwordDecoder.id;
                    const decodedPassword = matcher.replace(settings.passwordDecoder.origin, settings.passwordDecoder.rulePassword);

                    let result = preText + matcher + ` <a href="#" id="${id}" style="${styles.password}" data-password="${RULE_PASSWORD}" title="클릭 시 비밀번호가 복사됩니다.">🔑${RULE_PASSWORD}</a>`;
                    if(RULE_PASSWORD !== decodedPassword) result += ` / <a href="#" id="${id}_decoded" style="${styles.password}" data-password="${decodedPassword}" title="클릭 시 비밀번호가 복사됩니다.">🔑${decodedPassword}</a>`
                    result += postText;

                    return result
                });
            }
        }

        // Change original image
        if(settings.switch.imageOriginalizer) {
            articleHtml = articleHtml.replace(/<img[^>]+src="([^"]+)[^>]+loading="lazy"[^>]*>/g, (matcher, src) => `<img src="${src}&type=orig&dummy" loading="lazy">`);
        }

        // Apply article changed
        if(Object.values(settings.switch).some(Boolean))
            article.innerHTML = articleHtml;

        // Password copy event
        const copyPassword = async (e) => {
            // Prevent page move
            e.preventDefault();

            await navigator.clipboard.writeText(e.target.dataset.password);
            e.target.style = styles.copiedPassword;
        };

        // Set event password clicked
        document.getElementById(settings.passwordDecoder.id)?.addEventListener("click", copyPassword);
        document.getElementById(settings.passwordDecoder.id + "_decoded")?.addEventListener("click", copyPassword);
    }

    // Set password at inputbox
    const setMutationObserver = (hostObject) => {
        const copyPassword = async (callback) => {
            // Get password
            const clipboard = await navigator.clipboard.readText();
            const password = clipboard?.toLowerCase().indexOf(RULE_PASSWORD) > -1 ? clipboard : RULE_PASSWORD;

            hostObject.postAction(hostObject, password);
            window.removeEventListener("focus", copyPassword);
        }

        // Mutation observer
        const observer = new MutationObserver((mutationList, observer) => {
            // Returns if preAction is empty or the result of the action is true
            const preResult = !(hostObject.preAction && hostObject.preAction(hostObject));
            if(preResult === false)
                return false;

            // Copy password when focused
            if(document.hasFocus())
                copyPassword();
            else
                window.addEventListener("focus", copyPassword);

            // Mutation observer disconnect
            observer.disconnect();
        });

        // Observing start
        observer.observe(hostObject.container, {
            attributes: true,
            childList: true,
            subtree: true
        });
    }

    // Per-Host behavior
    const kiosk = {
        container: document.querySelector("body > div"),
        preAction: (hostObject) => {
            // Get password inputbox
            hostObject.inputBox = document.querySelector("#password-dialog input[type='password']");
            return !hostObject.inputBox;
        },
        postAction: (hostObject, password) => {
            // Input password
            hostObject.inputBox.value = password;
        },
    };
    const hosts = {
        "mega.nz": {
            container: document.getElementById("bodyel"),
            preAction: (hostObject) => {
                // Get password inputbox
                hostObject.inputBox = hostObject.container.querySelector("input#password-decrypt-input");
                return !hostObject.inputBox || !hostObject.inputBox.previousElementSibling;
            },
            postAction: (hostObject, password) => {
                // Disable password invisible protection
                hostObject.inputBox.previousElementSibling.remove();
                hostObject.inputBox.type = "text";

                // Input password
                hostObject.inputBox.value = password;
            },
        },
        "kiosk.ac": kiosk,
        "kioskloud.io": kiosk
    }

    // Execute function by host
    const currentHost = document.URL.split("/")[2];
    if(currentHost === "arca.live")
        arcalive();
    else
        setMutationObserver(hosts[currentHost]);
})();