jasmineamber / x1080x-extract

// ==UserScript==
// @name               x1080x-extract
// @license            MIT
// @version            3.18
// @description        提取压缩包下载链接
// @updateURL          https://openuserjs.org/meta/jasmineamber/x1080x-extract.meta.js
// @downloadURL        https://openuserjs.org/install/jasmineamber/x1080x-extract.user.js
// @copyright          2023, jasmineamber (https://openuserjs.org/users/jasmineamber)
// @author             jasmine
// @match              *://x999x.me/forum.php?mod=viewthread&tid=*
// @match              *://*.x999x.me/forum.php?mod=viewthread&tid=*
// @match              *://x567x.me/forum.php?mod=viewthread&tid=*
// @match              *://*.x567x.me/forum.php?mod=viewthread&tid=*
// @icon               https://www.google.com/s2/favicons?domain=www.x999x.me
// @require            https://lib.baomitu.com/jquery/1.7.2/jquery.min.js
// @require            https://unpkg.com/hotkeys-js@3.8.7/dist/hotkeys.min.js
// @require            https://gitee.com/jasmineamber/tampermonkey/raw/master/qrcode.js
// @require            https://gitee.com/jasmineamber/tampermonkey/raw/master/unrar.min.js
// @grant              GM_xmlhttpRequest
// @grant              GM_notification
// @grant              GM_download
// @grant              GM_addStyle
// @grant              GM_setValue
// @grant              GM_getValue
// @grant              GM_registerMenuCommand
// @grant              GM_unregisterMenuCommand
// @connect            unrar.d2cool.com
// ==/UserScript==

(async function () {
    'use strict';
    // 菜单列表
    var menu_ALL = [
        ['menu_download', '下载附件', '下载附件', false],
        ['menu_qr', '识别百度二维码', '识别百度二维码', true]
    ],
        menu_ID = [];
    for (let i = 0; i < menu_ALL.length; i++) { // 如果读取到的值为 null 就写入默认值
        if (GM_getValue(menu_ALL[i][0]) == null) {
            GM_setValue(menu_ALL[i][0], menu_ALL[i][3])
        };
    }
    registerMenuCommand();

    // 注册脚本菜单
    function registerMenuCommand() {
        if (menu_ID.length >= menu_ALL.length) { // 如果菜单ID数组多于菜单数组,说明不是首次添加菜单,需要卸载所有脚本菜单
            for (let i = 0; i < menu_ID.length; i++) {
                GM_unregisterMenuCommand(menu_ID[i]);
            }
        }
        for (let i = 0; i < menu_ALL.length; i++) { // 循环注册脚本菜单
            menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]);
            menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][3]?'✅':'❌'} ${menu_ALL[i][1]}`, function () {
                menu_switch(`${menu_ALL[i][3]}`, `${menu_ALL[i][0]}`, `${menu_ALL[i][2]}`)
            });
        }
    }

    // 菜单开关
    function menu_switch(menu_status, Name, Tips) {
        if (menu_status == 'true') {
            GM_setValue(`${Name}`, false);
            GM_notification({text: `已关闭 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
        }
        else {
            GM_setValue(`${Name}`, true);
            GM_notification({text: `已开启 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
        }
        registerMenuCommand(); // 重新注册脚本菜单
    };

    // 返回菜单值
    function menu_value(menuName) {
        for (let menu of menu_ALL) {
            if (menu[0] == menuName) {
                return menu[3]
            }
        }
    }

    async function get_valid_link() {
        const urls = ['https://unrar.d2cool.com:2443']
        for(let i = 0;i <= urls.length;i++){
            let url = urls[i];
            const res = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `${url}`,
                    responseType: "blob",
                    timeout: 5000,
                    onload: response => {
                        resolve(response)
                    },
                    onerror: response => {
                        reject(response)
                    },
                });
            });
            let blob = res.response
            let text = await blob.text();
            if(text == 'Hello World!') {
                return url
            }
        }

    }

    function add_element(type, attributes) {
        const ele = document.createElement(type)
        if (attributes) {
            for (const [key, value] of Object.entries(attributes)) {
                ele.setAttribute(key, value);
            }
        }
        return ele;
    }

    function download_attach(blobData) {
        let a = document.createElement("a")
        let url = URL.createObjectURL(blobData);
        a.href = url;
        a.download = attach_name;
        document.body.appendChild(a);
        a.click();
        setTimeout(function () {
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);
        }, 0);
    }

    function blobToBase64(blob) {
        return new Promise((resolve, _) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result);
            reader.readAsDataURL(blob);
        });
    }

    function BlobToImageData(blob) {
        let blobUrl = URL.createObjectURL(blob);

        return new Promise((resolve, reject) => {
            let img = new Image();
            img.onload = () => resolve(img);
            img.onerror = err => reject(err);
            img.src = blobUrl;
        }).then(img => {
            URL.revokeObjectURL(blobUrl);
            let [w, h] = [img.width, img.height]

            let canvas = document.createElement("canvas");
            canvas.width = w;
            canvas.height = h;
            let ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0);

            return ctx.getImageData(0, 0, w, h); // some browsers synchronously decode image here
        })
    }

    function toUnicode(str) {
        return str.split('').map(function (value, index, array) {
            var temp = value.charCodeAt(0).toString(16).toUpperCase();
            if (temp.length > 2) {
                return '\\u' + temp;
            }
            return value;
        }).join('');
    }

    function extractLink(text) {
        let link;
        text = text.replace(/^\uFEFF/gm, "").replace(/^\u00BB\u00BF/gm, "");
        let reg_ed2k_mp4 = /^ed2k:\/\/.*mp4.*/gm
        let reg_ed2k_rar = /^ed2k:\/\/.*\.rar.*/gm
        let reg_115 = /^hhd800\.com@.*/gm
        let reg_ip = /http:\/\/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).*/gm
        let reg_baidu = /https:\/\/pan\.baidu\.com\/s\/\w+|密码:\w{4}$/gm
        let reg_1fichier = /https:\/\/1fichier\.com\/\?\w+/gm
        let result = {
            reg: [reg_ed2k_mp4, reg_ed2k_rar, reg_115, reg_ip, reg_baidu, reg_1fichier],
            data: []
        }
        result.reg.forEach(reg => {
            result.data.push(text.match(reg))
        })
        link = result.data.filter(item => item);
        return link
    }
    async function extract_attch() {
        document.querySelector(".extract svg").outerHTML = icon_loading;
        [...document.querySelectorAll(".link_copy")].map(item => item.parentNode.removeChild(item))
        let download_url;
        if (GM_getValue("tid", null) === tid && GM_getValue("attach_data", null)) {
            download_url = GM_getValue("attach_data")
        }
        else {
            download_url = attach_url
        }
        const res_attach = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: download_url,
                responseType: "blob",
                onload: response => {
                    resolve(response)
                },
                onerror: response => {
                    reject(response)
                },
            });
        });
        let blobData = res_attach.response;
        if (res_attach.responseText.indexOf("DOCTYPE") !== -1) {
            alert("附件下载失败")
            document.querySelector(".extract svg").outerHTML = icon_extract
            return
        }
        GM_setValue("tid", tid)
        GM_setValue("attach_data", await blobToBase64(blobData))
        if (menu_value("menu_download")) {
            download_attach(blobData)
        }
        try {
            let link;
            if (attach_name.endsWith(".txt")) {
                let text = await blobData.text();
                link = extractLink(text)
            } else if (attach_name.endsWith(".rar")) {
                let rarArrayBuffer = await blobData.arrayBuffer();
                const unrar = new Unrar(rarArrayBuffer);
                var files = unrar.getEntries();
                for (var i = 0, len = files.length; i < len; ++i) {
                    let file = files[i];
                    if (file.unpackSize > 400 && file.name.endsWith(".txt")) {
                        let data = unrar.decompress(file.name);
                        let blob = new Blob([data]);
                        let text = await blob.text();
                        text = text.replace(/^\uFEFF/gm, "").replace(/^\u00BB\u00BF/gm, "");
                        let reg_ed2k_mp4 = /^(?!.*(?:8K))ed2k:\/\/.*mp4.*/gm
                        let reg_ed2k_8k_mp4 = /^ed2k:\/\/.*8K\.mp4.*/gm
                        let reg_ed2k_rar = /^ed2k:\/\/.*\.rar.*/gm
                        let reg_115 = /^hhd800\.com@.*/gm
                        let reg_ip = /http:\/\/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).*/gm
                        let reg_baidu = /https:\/\/pan\.baidu\.com\/s\/\w+|密码:\w{4}$/gm
                        let reg_1fichier = /https:\/\/1fichier\.com\/\?\w+/gm
                        let result = {
                            reg: [reg_ed2k_mp4, reg_ed2k_8k_mp4, reg_ed2k_rar, reg_115, reg_ip, reg_baidu, reg_1fichier],
                            data: []
                        }
                        result.reg.forEach(reg => {
                            result.data.push(text.match(reg))
                        })
                        link = result.data.filter(item => item);
                    }
                    if (file.name.endsWith(".png") && menu_value("menu_qr")) {
                        let data = unrar.decompress(file.name);
                        let blob = new Blob([data]);
                        let imageData = await BlobToImageData(blob);
                        let result = new QRCode.Decoder().setOptions({
                            canOverwriteImage: false
                        }).decode(imageData.data, imageData.width, imageData.height);
                        let baidu_link = [];
                        baidu_link[0] = result.data.trim();
                        var rar_file = new File([blobData], "temp.rar");
                        let post_data = new FormData();
                        post_data.append("file", rar_file);
                        let unrar_link = await get_valid_link()
                        const res_code = await new Promise((resolve, reject) => {
                            GM_xmlhttpRequest({
                                method: "POST",
                                url: `${unrar_link}/unrar`,
                                data: post_data,
                                responseType: "blob",
                                onload: response => {
                                    resolve(response)
                                },
                                onerror: response => {
                                    reject(response)
                                },
                            });
                        });
                        let code_blob = res_code.response
                        let baidu_code = await code_blob.text();
                        baidu_link[0] = `${baidu_link[0]}?pwd=${baidu_code}`
                        link.push(baidu_link);
                    }
                }
            } else {
                alert("不支持的附件格式")
                return
            }
            if (link.length === 0) {
                throw new Error('提取不到链接');
            }
            link.sort(
                function(x, y) {
                    return y[0].indexOf('_8K') - x[0].indexOf('_8K')
                }
            )
            link.forEach(item => {
                const node_copy = document.querySelector(".pattl")
                const div_copy = add_element('div', {
                    class: "link_copy"
                })
                const span_txt = add_element('span', {
                    class: "txt"
                })
                const span_icon = add_element('span', {
                    class: "icon"
                })
                const svg_copy = add_element('svg', null)
                node_copy.appendChild(div_copy)
                div_copy.appendChild(span_txt)
                div_copy.appendChild(span_icon)
                span_icon.appendChild(svg_copy)
                svg_copy.outerHTML = `<svg xmlns="http://www.w3.org/2000/svg"viewBox="0 0 512 512"><title>点击复制</title><!--!Font Awesome Pro 6.0.0 by @fontawesome-https:License-https:2022 Fonticons,Inc.--><path d="M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z"></path></svg>`
                span_txt.innerText = item.join(`\n`)
            })
            $('.link_copy .icon').click(function (event) {
                let addrsField = $(event.target).parents("span").first().prev()
                if (addrsField.length === 0) {
                    addrsField = $(event.target).prev()
                }
                copyToClipboard(addrsField);
                addrsField.addClass('flashBG')
                    .delay('1000').queue(function () {
                    addrsField.removeClass('flashBG').dequeue();
                });
            });
        }
        catch (e) {
            alert("提取失败,请手动操作")
            download_attach(blobData)
        }
        finally {
            document.querySelector(".extract svg").outerHTML = icon_extract
        }
    }

    const ele_attach = document.querySelector(".attnm a")
    if (!ele_attach) {
        return
    }
    const attach_name = ele_attach.text
    const attach_url = ele_attach.getAttribute("href")

    GM_addStyle(`
        .extract button {
          font: inherit;
          margin: 5px 0px 0px 10px;
          background: none;
          border: 1px solid#aaa;
          border-radius: 4px;
          cursor: pointer;
          padding: 5px 10px;
          transition: all 0.3s ease;
          display: flex;
        }

        .extract button:hover {
          background: #08c;
          color: #fff;
          transform: scale(1.1);
        }

        .extract svg {
          height: 20px;
        }

        .extract span {
          padding-left: 3px;
        }

        .tattl dd {
          margin: 0px;
        }

        .tattl {
          display: flex;
          float: none;
        }

        .link_copy {
            padding: 5px 10px;
            background: #F7F7F7;
            border: 2px solid #aaa;
            color: #666;
            font-size: .8em;
            display: flex;
            align-items: center;
            width: 500px;
            margin-top: 5px;
        }

        .link_copy .icon {
            display: block;
            max-width: 25px;
            cursor: pointer;
            margin-left: auto;
        }

        .link_copy .txt {
            width: 90%;
            display: inline-block;
            overflow: hidden;
            word-break: break-all;
            white-space: pre-line;
        }

        .link_copy svg {
            width: 20px;
        }

        /* click animation */
        .flashBG {
            animation-name: flash;
            animation-timing-function: ease-out;
            animation-duration: 1s;
        }

        @keyframes flash {
            0% {
                background: #28a745;
            }
            100% {
                background: transparent;
            }
        }

    `)

    let tid_match = /tid=(\d+)/.exec(window.location.href)
    if (!tid_match) {
        return
    }
    let tid = tid_match[1]
    const node = document.querySelector(".tattl")
    const div_extract = add_element('div', {
        class: "extract"
    })
    const button = add_element('button', null)
    const span = add_element('span', null)
    const svg = add_element('svg', null)
    button.appendChild(svg)
    button.appendChild(span)
    div_extract.appendChild(button)
    node.appendChild(div_extract)
    const icon_extract = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M75.23 33.4L320 63.1L564.8 33.4C571.5 32.56 578 36.06 581.1 42.12L622.8 125.5C631.7 143.4 622.2 165.1 602.9 170.6L439.6 217.3C425.7 221.2 410.8 215.4 403.4 202.1L320 63.1L236.6 202.1C229.2 215.4 214.3 221.2 200.4 217.3L37.07 170.6C17.81 165.1 8.283 143.4 17.24 125.5L58.94 42.12C61.97 36.06 68.5 32.56 75.23 33.4H75.23zM321.1 128L375.9 219.4C390.8 244.2 420.5 255.1 448.4 248L576 211.6V378.5C576 400.5 561 419.7 539.6 425.1L335.5 476.1C325.3 478.7 314.7 478.7 304.5 476.1L100.4 425.1C78.99 419.7 64 400.5 64 378.5V211.6L191.6 248C219.5 255.1 249.2 244.2 264.1 219.4L318.9 128H321.1z"/></svg>`
    const icon_loading = `<svg version="1.1"id="L9"xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"x="0px"y="0px"viewBox="0 0 100 100"enable-background="new 0 0 0 0"xml:space="preserve"><path fill="black"d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"><animateTransform attributeName="transform"attributeType="XML"type="rotate"dur="1s"from="0 50 50"to="360 50 50"repeatCount="indefinite"></animateTransform></path></svg>`
    svg.outerHTML = icon_extract
    span.innerText = "提取链接"

    button.onclick = extract_attch

    const checkElement = async selector => {
        while (document.querySelector(selector) === null) {
            await new Promise(resolve => requestAnimationFrame(resolve))
        }
        return document.querySelector(selector);
    };

    hotkeys('alt+1,alt+2,alt+3,alt+4', function (event, handler) {
        event.preventDefault();
        if (!document.querySelector(".link_copy .icon")) {
            button.click();
        }
        checkElement(".link_copy .icon").then((selector) => {
            let span = document.querySelectorAll(".link_copy .icon");
            switch (handler.key) {
                case 'alt+1':
                    span = span[0]
                    break;
                case 'alt+2':
                    if (span.length >= 2) {
                        span = span[1]
                    }
                    else {
                        span = span[0]
                    };
                    break;
                case 'alt+3':
                    if (span.length >= 3) {
                        span = span[2]
                    }
                    else {
                        span = span[0]
                    }
                    break;
                case 'alt+4':
                    if (span.length >= 4) {
                        span = span[3]
                    }
                    else {
                        span = span[0]
                    };
                    break;
            }
            span.click();
        });
    });

    function copyToClipboard(element) {
        var $temp = $("<textarea>");
        $("body").append($temp);
        $temp.val(element.html().replace(/<br>/gi, "\n")).select();
        document.execCommand("copy");
        $temp.remove();
    }
})();