Greasy Fork

Greasy Fork is available in English.

哔哩哔哩链接识别助手

AI智能识别选中文字中的哔哩哔哩链接【视频】【动态】等。

当前为 2022-01-15 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name              哔哩哔哩链接识别助手
// @namespace         https://github.com/yalwolf/bilibiliurl
// @version           1.2.5
// @author            一只阿狼哒
// @icon              https://js.alwolf.cn/images/bilibiliurl.png
// @icon64            https://js.alwolf.cn/images/bilibiliurl.png
// @description       AI智能识别选中文字中的哔哩哔哩链接【视频】【动态】等。
// @license           AGPL
// @homepage          https://js.alwolf.cn/
// @supportURL        https://github.com/yalwolf/bilibiliurl
// @match             *://*/*
// @require           https://unpkg.com/[email protected]/dist/sweetalert2.min.js
// @resource          swalStyle https://unpkg.com/[email protected]/dist/sweetalert2.min.css
// @run-at            document-end
// @grant             GM_openInTab
// @grant             unsafeWindow
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_registerMenuCommand
// @grant             GM_getResourceText
// ==/UserScript==

(function () {
    'use strict';

    const fixedStyle = ['www.baidu.com']; //弹出框错乱的网站css插入到<html>而非<head>
    const customClass = {
        container: 'panai-container',
        popup: 'panai-popup',
        header: 'panai-header',
        title: 'panai-title',
        closeButton: 'panai-close',
        icon: 'panai-icon',
        image: 'panai-image',
        content: 'panai-content',
        htmlContainer: 'panai-html',
        input: 'panai-input',
        inputLabel: 'panai-inputLabel',
        validationMessage: 'panai-validation',
        actions: 'panai-actions',
        confirmButton: 'panai-confirm',
        denyButton: 'panai-deny',
        cancelButton: 'panai-cancel',
        loader: 'panai-loader',
        footer: 'panai-footer'
    };

    let util = {
        clog(c) {
            console.group('[哔哩哔哩链接识别助手]');
            console.log(c);
            console.groupEnd();
        },

        parseQuery(name) {
            let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
            let r = location.search.substr(1).match(reg);
            if (r != null) return (r[2]);
            return null;
        },

        getValue(name) {
            return GM_getValue(name);
        },

        setValue(name, value) {
            GM_setValue(name, value);
        },

        include(str, arr) {
            for (let i = 0, l = arr.length; i < l; i++) {
                let val = arr[i];
                if (val !== '' && str.toLowerCase().indexOf(val.toLowerCase()) > -1) {
                    return true;
                }
            }
            return false;
        },

        sleep(time) {
            return new Promise((resolve) => setTimeout(resolve, time));
        },

        addStyle(id, tag, css) {
            tag = tag || 'style';
            let doc = document, styleDom = doc.getElementById(id);
            if (styleDom) return;
            let style = doc.createElement(tag);
            style.rel = 'stylesheet';
            style.id = id;
            tag === 'style' ? style.innerHTML = css : style.href = css;
            let root = this.include(location.href, fixedStyle);
            root ? doc.documentElement.appendChild(style) : doc.getElementsByTagName('head')[0].appendChild(style);
        },

        isHidden(el) {
            try {
                return el.offsetParent === null;
            } catch (e) {
                return false;
            }
        }
    };

    let opt = {
        bilibilisp: {
            reg: /((?:https?:\/\/)?www\.bilibili\.com\/video\/[A-Za-z0-9]+)/,
            host: /www\.bilibili\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili视频',
            storage: 'hash'
        },
        bilibiliau: {
            reg: /((?:https?:\/\/)?www\.bilibili\.com\/audio\/[A-Za-z0-9]+)/,
            host: /www\.bilibili\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili音频',
            storage: 'hash'
        },
        bilibilird: {
            reg: /((?:https?:\/\/)?www\.bilibili\.com\/read\/[A-Za-z0-9]+)/,
            host: /www\.bilibili\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili专栏',
            storage: 'hash'
        },
        bilibiliup: {
            reg: /((?:https?:\/\/)?space\.bilibili\.com\/[0-9]+)/,
            host: /space\.bilibili\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili用户',
            storage: 'hash'
        },
        bilibilidt: {
            reg: /((?:https?:\/\/)?t\.bilibili\.com\/[0-9]+)/,
            host: /t\.bilibili\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili动态',
            storage: 'hash'
        },
        bilibiligame: {
            reg: /((?:https?:\/\/)?game\.bilibili\.com\/[A-Za-z_\-]+)/,
            host: /game\.bilibili\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili游戏',
            storage: 'hash'
        },
        biligame: {
            reg: /((?:https?:\/\/)?www\.biligame\.com\/(?:detail\/\?id=)?[0-9]+)/,
            host: /www\.biligame\.com/,
            input: ['#accessCode'],
            button: ['#submitBtn'],
            name: 'bilibili游戏',
            storage: 'hash'
        },
    };

    let main = {
        lastText: "lorem&",

        //初始化配置数据
        initValue() {
            let value = [{
                name: 'setting_success_times',
                value: 0
            }, {
                name: 'setting_auto_click_btn',
                value: true
            }, {
                name: 'setting_active_in_front',
                value: true
            }, {
                name: 'setting_timer_open',
                value: false
            }, {
                name: 'setting_timer',
                value: 5000
            }];

            value.forEach((v) => {
                if (util.getValue(v.name) === undefined) {
                    util.setValue(v.name, v.value);
                }
            });
        },

        // 监听选择事件
        addPageListener() {
            document.addEventListener("mouseup", this.smartIdentify.bind(this), true);
        },

        smartIdentify() {
            let selection = unsafeWindow.getSelection();
            let text = selection.toString();
            if (text !== this.lastText && text !== '') { //选择相同文字或空不识别
                let start = performance.now();
                this.lastText = text;
                //util.clog(`当前选中文字:${text}`);
                let linkObj = this.parseLink(text);
                let link = linkObj.link;
                let name = linkObj.name;
                let pwd = this.parsePwd(text);
                if (!link) {
                    linkObj = this.parseParentLink(selection);
                    link = linkObj.link;
                    name = linkObj.name;
                }
                if (link) {
                    if (!/https?:\/\//.test(link)) {
                        link = 'https://' + link;
                    }
                    let end = performance.now();
                    let time = (end - start).toFixed(3);
                    util.clog(`文本识别结果:${name} 链接:${link} 密码:${pwd} 耗时:${time}毫秒`);
                    let option = {
                        toast: true,
                        showCancelButton: true,
                        position: 'top',
                        title: `发现<span style="color: #2778c4;margin: 0 5px;">${name}</span>链接`,
                        html: `<span style="font-size: 0.8em;">${!!pwd ? '密码:' + pwd : '是否打开?'}</span>`,
                        confirmButtonText: '打开',
                        cancelButtonText: '关闭',
                        customClass
                    };
                    if (util.getValue('setting_timer_open')) {
                        option.timer = util.getValue('setting_timer');
                        option.timerProgressBar = true;
                    }
                    util.setValue('setting_success_times', util.getValue('setting_success_times') + 1);

                    Swal.fire(option).then((res) => {
                        this.lastText = 'lorem&';
                        selection.empty();
                        if (res.isConfirmed || res.dismiss === 'timer') {
                            if (name === '和彩云') {  //和彩云无法携带参数和Hash
                                util.setValue('tmp_caiyun_pwd', pwd);
                            }
                            if (pwd) {
                                let extra = `${link}?pwd=${pwd}#${pwd}`;
                                if (~link.indexOf('?')) {
                                    extra = `${link}&pwd=${pwd}#${pwd}`;
                                }
                                GM_openInTab(extra, {active: util.getValue('setting_active_in_front')});
                            } else {
                                GM_openInTab(`${link}`, {active: util.getValue('setting_active_in_front')});
                            }
                        }
                    });
                }
            }
        },

        //正则解析网盘链接
        parseLink(text = '') {
            let obj = {name: '', link: ''};
            if (text) {
                text = text.replace(/[\u4e00-\u9fa5\u200B()(),,]/g, '');
                text = text.replace(/lanzous/g, 'lanzouw'); //修正lanzous打不开的问题
                for (let name in opt) {
                    let val = opt[name];
                    if (val.reg.test(text)) {
                        let matches = text.match(val.reg);
                        obj.name = val.name;
                        obj.link = matches[0];
                        return obj;
                    }
                }
            }
            return obj;
        },

        //正则解析超链接类型网盘链接
        parseParentLink(selection) {
            let anchorNode = selection.anchorNode.parentElement.href;
            let focusNode = selection.focusNode.parentElement.href;
            if (anchorNode) return this.parseLink(anchorNode);
            if (focusNode) return this.parseLink(focusNode);
            return this.parseLink()
        },

        //正则解析提取码
        parsePwd(text) {
            text = text.replace(/\u200B/g, '');
            let reg = /(?<=\s*(密|提取|访问|訪問|key|password|pwd|#)[码碼]?[::=]?\s*)[A-Za-z0-9]{3,8}/i;
            if (reg.test(text)) {
                let match = text.match(reg);
                return match[0];
            }
            return '';
        },

        //根据域名检测网盘类型
        panDetect() {
            let hostname = location.hostname;
            for (let name in opt) {
                let val = opt[name];
                if (val.host.test(hostname)) {
                    return name;
                }
            }
            return '';
        },

        //自动填写密码
        autoFillPassword() {
            let url = location.href;
            let query = util.parseQuery('pwd');
            let hash = location.hash.slice(1);
            let pwd = query || hash;
            let panType = this.panDetect();

            for (let name in opt) {
                let val = opt[name];
                if (panType === name) {
                    if (val.storage === 'local') {
                        pwd = util.getValue(val.storagePwdName) ? util.getValue(val.storagePwdName) : '';
                        pwd && this.doFillAction(val.input, val.button, pwd);
                    }
                    if (val.storage === 'hash') {
                        if (!/^[A-Za-z0-9]{3,8}$/.test(pwd)) { //过滤掉不正常的Hash
                            return;
                        }
                        pwd && this.doFillAction(val.input, val.button, pwd);
                    }
                }
            }
        },

        doFillAction(inputSelector, buttonSelector, pwd) {
            let maxTime = 10;
            let ins = setInterval(async () => {
                maxTime--;
                let input = document.querySelector(inputSelector[0]) || document.querySelector(inputSelector[1]);
                let button = document.querySelector(buttonSelector[0]) || document.querySelector(buttonSelector[1]);

                if (input && !util.isHidden(input)) {
                    clearInterval(ins);
                    Swal.fire({
                        toast: true,
                        position: 'top',
                        showCancelButton: false,
                        showConfirmButton: false,
                        title: 'AI已识别到密码!正自动帮您填写',
                        icon: 'success',
                        timer: 2000,
                        customClass
                    });

                    let lastValue = input.value;
                    input.value = pwd;
                    //Vue & React 触发 input 事件
                    let event = new Event('input', {bubbles: true});
                    let tracker = input._valueTracker;
                    if (tracker) {
                        tracker.setValue(lastValue);
                    }
                    input.dispatchEvent(event);

                    if (util.getValue('setting_auto_click_btn')) {
                        await util.sleep(1000); //1秒后点击按钮
                        button.click();
                    }
                } else {
                    maxTime === 0 && clearInterval(ins);
                }
            }, 800);
        },

        registerMenuCommand() {
            GM_registerMenuCommand('已识别:' + util.getValue('setting_success_times') + '次', () => {
                Swal.fire({
                    showCancelButton: true,
                    title: '确定要重置识别次数吗?',
                    icon: 'warning',
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    customClass
                }).then((res) => {
                    this.lastText = 'lorem&';
                    if (res.isConfirmed) {
                        util.setValue('setting_success_times', 0);
                        history.go(0);
                    }
                });
            });
            GM_registerMenuCommand('介绍', () => {
                let html = `<div style="font-size: 1em;">
                              <label class="panai-setting-label"><a href="https://js.alwolf.cn"title="脚本丨主页" target="_blank" class="links" style="text-decoration:none;">主页</a></label>
                              <label class="panai-setting-label"><a href="https://js.alwolf.cn/js/updates/bilibiliurl"title="脚本丨检查更新" target="_blank" class="links" style="text-decoration:none;">检查更新</label>
                              <label class="panai-setting-label"><a href="https://js.alwolf.cn/tutorial"title="脚本丨教程" target="_blank" class="links" style="text-decoration:none;">使用教程</label>
                            </div>`;
                Swal.fire({
                    title: '识别助手介绍',
                    html,
                    icon: 'info',
                    showCloseButton: true,
                    confirmButtonText: '关闭',
                    footer: '<div id="footerBox">©2021&nbsp;Copyright:<a href="http://alwolf.cn"  title="一只阿狼哒丨主页" target="_blank" class="links" style="text-decoration:none;">一只阿狼哒</a></div>',
                    customClass
                }).then((res) => {
                    res.isConfirmed && history.go(0);
                });

                document.getElementById('S-Auto').addEventListener('change', (e) => {
                    util.setValue('setting_auto_click_btn', e.currentTarget.checked);
                });
                document.getElementById('S-Active').addEventListener('change', (e) => {
                    util.setValue('setting_active_in_front', e.currentTarget.checked);
                });
                document.getElementById('S-Timer-Open').addEventListener('change', (e) => {
                    util.setValue('setting_timer_open', e.currentTarget.checked);
                });
                document.getElementById('S-Timer').addEventListener('change', (e) => {
                    util.setValue('setting_timer', e.target.value);
                    document.getElementById('Timer-Value').innerText = `(${e.target.value / 1000}秒)`;
                });
            });
        },

        addPluginStyle() {
            let style = `
                .panai-container { z-index: 99999!important }
                .panai-popup { font-size: 14px !important }
                .panai-setting-label { display: flex;align-items: center;justify-content: space-between;padding-top: 20px; text-align: center; }
                .panai-setting-checkbox { width: 16px;height: 16px; }
                .links{color:#000;}
            `;
            util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
            util.addStyle('panai-style', 'style', style);
        },

        isTopWindow() {
            return window.self === window.top;
        },

        init() {
            this.initValue();
            this.addPluginStyle();
            this.autoFillPassword();
            this.addPageListener();
            this.isTopWindow() && this.registerMenuCommand();
        },
    };

    main.init();
})();