심야식당 도우미

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

目前為 2023-06-30 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         심야식당 도우미
// @description  심야식당을 좀 더 편하고 내 입맛에 맞게 이용
// @version      0.1
// @author       Yoonu
// @match        https://arca.live/b/*
// @match        https://kioskloud.io/e/*
// @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[3] || "");
                        postText = (args[2] || "") + (args[3] || "");
                        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" 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 setPassword = async (container, inputSelector) => {
        const clipboard = await navigator.clipboard.readText();
        const password = clipboard?.toLowerCase().indexOf(RULE_PASSWORD) > -1 ? clipboard : RULE_PASSWORD;

        // Callback
        const callback = (mutationList, observer) => {
            const inputBox = inputSelector();
            if(!inputBox) return;

            inputBox.value = password;
            observer.disconnect();
        };

        // Mutation observing
        const observer = new MutationObserver(callback);
        observer.observe(container, {
            attributes: true,
            childList: true,
            subtree: true
        });
    }

    // Execute function by host
    switch(document.URL.split("/")[2]) {
        case "arca.live":
            arcalive();
            break;

        case "mega.nz":
            setPassword(document.getElementById("bodyel"), () => document.getElementById("password-decrypt-input"));
            break;

        case "kioskloud.io":
            setPassword(document.getElementById("app"), () => document.getElementById("swal2-title")?.parentNode.querySelector("input"));
            break;
    }
})();