Greasy Fork

bilibili视频下载

支持Web、RPC、Blob、Aria等下载方式;支持flv、dash、mp4视频格式;支持下载港区番剧;支持会员下载;支持换源播放,自动切换为高清视频源

目前为 2021-08-17 提交的版本。查看 最新版本

// ==UserScript==
// @name         bilibili视频下载
// @namespace    https://github.com/injahow
// @version      1.4.5
// @description  支持Web、RPC、Blob、Aria等下载方式;支持flv、dash、mp4视频格式;支持下载港区番剧;支持会员下载;支持换源播放,自动切换为高清视频源
// @author       injahow
// @homepage     https://github.com/injahow/bilibili-parse
// @copyright    2021, injahow (https://github.com/injahow)
// @match        *://www.bilibili.com/video/av*
// @match        *://www.bilibili.com/video/BV*
// @match        *://www.bilibili.com/bangumi/play/ep*
// @match        *://www.bilibili.com/bangumi/play/ss*
// @match        *://www.bilibili.com/cheese/play/ep*
// @match        *://www.bilibili.com/cheese/play/ss*
// @match        https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js
// @require      https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js
// @compatible   chrome
// @compatible   firefox
// @license      MIT
// @grant        none
// ==/UserScript==
/* globals $, DPlayer waitForKeyElements */
(function () {
    'use strict';

    if (window.bp_fun_locked) return;
    window.bp_fun_locked = true;

    // user
    let UserStatus;
    (function () {
        UserStatus = {
            lazy_init,
            is_login, vip_status, mid, need_replace
        };
        let _is_login = false, _vip_status = 0, _mid = '';
        let is_init = false;

        function lazy_init(last_init = false) {
            if (!is_init) {
                if (window.__BILI_USER_INFO__) {
                    _is_login = window.__BILI_USER_INFO__.isLogin;
                    _vip_status = window.__BILI_USER_INFO__.vipStatus;
                    _mid = window.__BILI_USER_INFO__.mid || '';
                } else if (window.__BiliUser__) {
                    _is_login = window.__BiliUser__.isLogin;
                    if (window.__BiliUser__.cache) {
                        _vip_status = window.__BiliUser__.cache.data.vipStatus;
                        _mid = window.__BiliUser__.cache.data.mid || '';
                    } else {
                        _vip_status = 0;
                        _mid = '';
                    }
                } else {
                    _is_login = false;
                    _vip_status = 0;
                    _mid = '';
                }
                is_init = last_init;
            }
        }

        function is_login() {
            return _is_login;
        }

        function vip_status() {
            return _vip_status;
        }

        function mid() {
            return _mid;
        }

        function need_replace() {
            return (!_is_login || (_is_login && !_vip_status && VideoStatus.base().need_vip()));
        }

    })();

    // auth
    let Auth;
    (function () {
        // https://greasyfork.org/zh-CN/scripts/25718-%E8%A7%A3%E9%99%A4b%E7%AB%99%E5%8C%BA%E5%9F%9F%E9%99%90%E5%88%B6/code
        if (location.href.match(/^https:\/\/www\.mcbbs\.net\/template\/mcbbs\/image\/special_photo_bg\.png/) != null) {
            if (location.href.match('access_key') != null && window.opener != null) {
                window.stop();
                document.children[0].innerHTML = '<title>bilibili-parse - 授权</title><meta charset="UTF-8" name="viewport" content="width=device-width">正在跳转……';
                window.opener.postMessage('bilibili-parse-login-credentials: ' + location.href, '*');
            }
            Auth = null;
            return;
        }

        Auth = {
            check_login_status
        };

        function check_login_status() {
            !localStorage.getItem('bp_remind_login') && localStorage.setItem('bp_remind_login', '1');
            const [auth_id, auth_sec, access_key, auth_time] = [
                localStorage.getItem('bp_auth_id') || '',
                localStorage.getItem('bp_auth_sec') || '',
                localStorage.getItem('bp_access_key') || '',
                localStorage.getItem('bp_auth_time') || '0'
            ];
            if (access_key && auth_time === '0') {
                localStorage.setItem('bp_auth_time', Date.now());
            }
            if (UserStatus.is_login()) {
                if (localStorage.getItem('bp_remind_login') === '1') {
                    if (!access_key) {
                        utils.MessageBox.confirm('当前脚本未进行账号授权,无法请求1080P以上的清晰度;如果你是大会员或承包过这部番,授权即可解锁全部清晰度;是否需要进行账号授权?', () => {
                            window.bp_show_login();
                        });
                    }
                    localStorage.setItem('bp_remind_login', '0');
                } else if (config.base_api !== localStorage.getItem('bp_pre_base_api') || (Date.now() - parseInt(auth_time) > 24 * 60 * 60 * 1000)) {
                    // check key
                    if (access_key) {
                        $.ajax(`${config.base_api}/auth/v2/?act=check&auth_id=${auth_id}&auth_sec=${auth_sec}&access_key=${access_key}`, {
                            type: 'GET',
                            dataType: 'json',
                            success: (res) => {
                                if (res.code) {
                                    utils.MessageBox.alert('授权已过期,准备重新授权', () => {
                                        localStorage.setItem('bp_access_key', '');
                                        localStorage.setItem('bp_auth_time', '');
                                        window.bp_show_login();
                                    });
                                } else {
                                    localStorage.setItem('bp_auth_time', Date.now());
                                }
                            },
                            error: () => {
                                utils.Message.danger('检查key请求异常');
                            }
                        });
                    }
                }
            }
            localStorage.setItem('bp_pre_base_api', config.base_api);
        }

        window.bp_show_login = function () {
            if (window.auth_clicked) {
                utils.Message.info('(^・ω・^)~喵喵喵~');
                return;
            }
            window.auth_clicked = true;
            if (localStorage.getItem('bp_access_key')) {
                utils.MessageBox.confirm('发现授权记录,是否重新授权?', () => {
                    login();
                }, () => {
                    window.auth_clicked = false;
                });
            } else {
                login();
            }
        }

        function login() {
            const auth_window = window.open('about:blank');
            auth_window.document.title = 'bilbili-parse - 授权';
            auth_window.document.body.innerHTML = '<meta charset="UTF-8" name="viewport" content="width=device-width">正在获取授权,请稍候……';
            window.auth_window = auth_window;
            $.ajax('https://passport.bilibili.com/login/app/third?appkey=27eb53fc9058f8c3&api=https%3A%2F%2Fwww.mcbbs.net%2Ftemplate%2Fmcbbs%2Fimage%2Fspecial_photo_bg.png&sign=04224646d1fea004e79606d3b038c84a', {
                xhrFields: { withCredentials: true },
                type: 'GET',
                dataType: 'json',
                success: (res) => {
                    if (res.data.has_login) {
                        auth_window.document.body.innerHTML = '<meta charset="UTF-8" name="viewport" content="width=device-width">正在跳转……';
                        auth_window.location.href = res.data.confirm_uri;
                    } else {
                        auth_window.close();
                        utils.MessageBox.confirm('必须登录B站才能正常授权,是否登陆?', () => {
                            location.href = 'https://passport.bilibili.com/login';
                        }, () => {
                            window.auth_clicked = false;
                        });
                    }
                },
                error: () => {
                    utils.Message.danger('授权请求异常');
                    window.auth_clicked = false;
                }
            });
        }

        window.bp_show_logout = function () {
            const [auth_id, auth_sec] = [
                localStorage.getItem('bp_auth_id') || '',
                localStorage.getItem('bp_auth_sec') || ''
            ];
            if (window.auth_clicked) {
                utils.Message.info('(^・ω・^)~喵喵喵~');
                return;
            }
            window.auth_clicked = true;
            if (!auth_id) {
                utils.MessageBox.alert('没有发现授权记录');
                window.auth_clicked = false;
                return;
            }
            $.ajax(`${config.base_api}/auth/v2/?act=logout&auth_id=${auth_id}&auth_sec=${auth_sec}`, {
                type: 'GET',
                dataType: 'json',
                success: (res) => {
                    if (!res.code) {
                        utils.Message.success('取消成功');
                        localStorage.setItem('bp_auth_id', '');
                        localStorage.setItem('bp_auth_sec', '');
                        localStorage.setItem('bp_auth_time', '');
                        localStorage.setItem('bp_access_key', '');
                        $('#auth').val('0');
                        config.auth = '0';
                    } else {
                        utils.Message.warning('取消失败');
                    }
                    window.auth_clicked = false;
                },
                error: () => {
                    utils.Message.danger('请求异常');
                    window.auth_clicked = false;
                }
            });
        }
        window.bp_show_login_help = function () {
            utils.MessageBox.confirm('进行授权之后将能在请求地址时享有用户账号原有的权益,例如能够请求用户已经付费或承包的番剧,是否需要授权?', () => {
                window.bp_show_login();
            });
        }
        window.addEventListener('message', function (e) {
            var _a;
            if (typeof e.data !== 'string') return;
            if (e.data.split(':')[0] === 'bilibili-parse-login-credentials') {
                (_a = window.auth_window) === null || _a === void 0 ? void 0 : _a.close();
                let url = e.data.split(': ')[1];
                const [auth_id, auth_sec] = [
                    localStorage.getItem('bp_auth_id') || '',
                    localStorage.getItem('bp_auth_sec') || ''
                ];
                $.ajax(url.replace('https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png?', `${config.base_api}/auth/v2/?act=login&auth_id=${auth_id}&auth_sec=${auth_sec}&vip_status=${UserStatus.vip_status()}&`), {
                    type: 'GET',
                    dataType: 'json',
                    success: (res) => {
                        if (!res.code) {
                            utils.Message.success('授权成功');
                            if (res.auth_id && res.auth_sec) {
                                localStorage.setItem('bp_auth_id', res.auth_id);
                                localStorage.setItem('bp_auth_sec', res.auth_sec);
                            }
                            localStorage.setItem('bp_access_key', new URL(url).searchParams.get('access_key'));
                            localStorage.setItem('bp_auth_time', Date.now());
                            $('#auth').val('1');
                            config.auth = '1';
                        } else {
                            utils.Message.warning('授权失败');
                        }
                        window.auth_clicked = false;
                    },
                    error: () => {
                        utils.Message.danger('请求异常');
                        window.auth_clicked = false;
                    }
                });
            }
        });
    })();
    if (!Auth) {
        return;
    }

    // config
    const config = {
        base_api: 'https://api.injahow.cn/bparse/',
        format: 'flv',
        replace_force: '0',
        auth: '0',
        download_type: 'web',
        rpc_domain: 'http://localhost',
        rpc_port: '16800',
        rpc_token: '',
        rpc_dir: 'D:/'
    };
    // config_init
    (function () {
        const default_config = Object.assign({}, config); // 浅拷贝
        const config_str = localStorage.getItem('my_config_str');
        if (!config_str) {
            localStorage.setItem('my_config_str', JSON.stringify(config));
        } else {
            // set config from cache
            const old_config = JSON.parse(config_str);
            for (const key in old_config) {
                if (Object.hasOwnProperty.call(config, key)) {
                    config[key] = old_config[key];
                }
            }
        }
        window.my_click_event = () => {
            // set config by form
            for (const key in config) {
                if (Object.hasOwnProperty.call(config, key)) {
                    config[key] = $(`#${key}`).val();
                }
            }
            const old_config = JSON.parse(localStorage.getItem('my_config_str'));
            localStorage.setItem('my_config_str', JSON.stringify(config));
            $('#my_config').hide();
            // 判断是否需要重新请求
            for (const key of ['base_api', 'format', 'auth']) {
                if (config[key] !== old_config[key]) {
                    $('#video_download').hide();
                    $('#video_download_2').hide();
                    break;
                }
            }
        };
        window.onbeforeunload = () => {
            window.my_click_event();
        };
        window.bp_show_help = () => {
            if (window.help_clicked) {
                utils.Message.info('(^・ω・^)~喵喵喵~');
                return;
            }
            window.help_clicked = true;
            $.ajax(`${config.base_api}/auth/v2/?act=help`, {
                dataType: 'text',
                success: (result) => {
                    if (result) {
                        utils.MessageBox.alert(result);
                    } else {
                        utils.Message.warning('获取失败');
                    }
                    window.help_clicked = false;
                },
                error: (e) => {
                    utils.Message.danger('请求异常');
                    window.help_clicked = false;
                    console.log('error', e);
                }
            });
        };
        !window.bp_reset_config && (window.bp_reset_config = () => {
            for (const key in default_config) {
                if (Object.hasOwnProperty.call(default_config, key)) {
                    if (key === 'auth') continue;
                    $(`#${key}`).val(default_config[key]);
                }
            }
        });
        const config_css = '' +
            '<style>' +
            '@keyframes settings-bg{from{background:rgba(0,0,0,0)}to{background:rgba(0,0,0,.7)}}' +
            '.setting-button{width:120px;height:40px;border-width:0px;border-radius:3px;background:#1E90FF;cursor:pointer;outline:none;color:white;font-size:17px;}.setting-button:hover{background:#5599FF;}' +
            'a.setting-context{margin:0 2%;color:blue;}a.setting-context:hover{color:red;}' +
            '</style>';
        const config_html = '' +
            `<div id="my_config"
                style="display:none;position:fixed;inset:0px;top:0px;left:0px;width:100%;height:100%;background:rgba(0,0,0,0.7);animation-name:settings-bg;animation-duration:0.3s;z-index:10000;cursor:default;">
                <div
                    style="position:absolute;background:rgb(255,255,255);border-radius:10px;padding:20px;top:50%;left:50%;width:600px;transform:translate(-50%,-50%);cursor:default;">
                    <span style="font-size:20px">
                        <b>bilibili视频下载 参数设置</b>
                        <b>
                            <a href="javascript:;" onclick="bp_reset_config()"> [重置配置] </a>
                            <a style="text-decoration:underline;" href="javascript:;" onclick="bp_show_help()">&lt;通知&帮助&gt;</a>
                        </b>
                    </span>
                    <div style="margin:2% 0;"><label>请求地址:</label>
                        <input id="base_api" value="..." style="width:50%;"><br />
                        <small>普通使用请勿修改,默认地址:https://api.injahow.cn/bparse/</small>
                    </div>
                    <div style="margin:2% 0;"><label>视频格式:</label>
                        <select name="format" id="format">
                            <option value="flv">FLV</option>
                            <option value="dash">DASH</option>
                            <option value="mp4">MP4</option>
                        </select><br />
                        <small>注意:番剧暂不支持MP4请求</small>
                    </div>
                    <div style="margin:2% 0;"><label>下载方式:</label>
                        <select name="download_type" id="download_type">
                            <option value="a">URL链接</option>
                            <option value="web">Web浏览器</option>
                            <option value="rpc">RPC接口</option>
                            <option value="blob">Blob请求</option>
                            <option value="aria">Aria命令</option>
                        </select><br />
                        <small>提示:web和url方式下载不会设置文件名</small>
                    </div>
                    <div style="margin:2% 0;"><label>RPC配置:[ 域名 : 端口 | 密钥 | 保存目录 ]</label><br />
                        <input id="rpc_domain" value="..." style="width:20%;"> :
                        <input id="rpc_port" value="..." style="width:10%;"> |
                        <input id="rpc_token" placeholder="没有密钥不用填" value="..." style="width:15%;"> |
                        <input id="rpc_dir" placeholder="留空使用默认目录" value="..." style="width:20%;"><br />
                        <small>注意:RPC默认使用Motrix(需要安装并运行)下载,其他软件请修改参数</small>
                    </div>
                    <div style="margin:2% 0;"><label>强制换源:</label>
                        <select name="replace_force" id="replace_force">
                            <option value="0">关闭</option>
                            <option value="1">开启</option>
                        </select><br />
                        <small>说明:强制使用请求到的视频地址和第三方播放器进行播放</small>
                    </div>
                    <div style="margin:2% 0;"><label>授权状态:</label>
                        <select name="auth" id="auth" disabled>
                            <option value="0">未授权</option>
                            <option value="1">已授权</option>
                        </select>
                        <a class="setting-context" href="javascript:;" onclick="bp_show_login()">账号授权</a>
                        <a class="setting-context" href="javascript:;" onclick="bp_show_logout()">取消授权</a>
                        <a class="setting-context" href="javascript:;" onclick="bp_show_login_help()">这是什么?</a>
                    </div>
                    <div style="text-align:right"><br />
                        <button class="setting-button" onclick="my_click_event()">确定</button>
                    </div>
                </div>
            </div>`;
        $('body').append(config_html + config_css);
        // 初始化配置页面
        for (const key in config) {
            if (Object.hasOwnProperty.call(config, key)) {
                $(`#${key}`).val(config[key]);
            }
        }
    })();

    // components
    const utils = {
        Video: {},
        Player: {},
        Message: {},
        MessageBox: {}
    };
    // components_init
    (function () {
        // Video
        utils.Video = {
            download: (url, name, type) => {
                const filename = name.replace(/[\/\\:*?"<>|]+/g, '');
                if (type === 'blob') {
                    download_blob(url, filename);
                } else if (type === 'rpc') {
                    download_rpc(url, filename);
                }
            },
            download_all
        };

        function download_all() {

            const [auth_id, auth_sec] = [
                localStorage.getItem('bp_auth_id'), localStorage.getItem('bp_auth_sec'),
            ];
            const video_base = VideoStatus.base();
            const [type, q, total] = [
                VideoStatus.type(),
                VideoStatus.get_quality().q,
                video_base.total()
            ];

            $('body').on('click', 'input[name="dl_video"]', function () {
                // 后触发
                if ($(this).is(':checked')) {
                    $(this).parent().css('color', 'rgba(0,0,0,1)');
                } else {
                    $(this).parent().css('color', 'rgba(0,0,0,0.5)');
                }
            });
            let video_html = '';
            for (let i = 0; i < total; i++) {
                video_html += '' +
                    `<label for="option_${i}"><div style="color:rgba(0,0,0,0.5);">
                        <input type="checkbox" id="option_${i}" name="dl_video" value="${i}">
                        P${i + 1} ${video_base.title(i + 1) || 'unknown'}
                    </div></label>`;
            }

            let all_checked = false;
            $('body').on('click', 'button#checkbox_btn', function () {
                if (all_checked) {
                    all_checked = false;
                    $('input[name="dl_video"]').prop('checked', all_checked);
                    $('input[name="dl_video"]').parent().css('color', 'rgba(0,0,0,0.5)');
                } else {
                    all_checked = true;
                    $('input[name="dl_video"]').prop('checked', all_checked);
                    $('input[name="dl_video"]').parent().css('color', 'rgb(0,0,0)');
                }
            });

            const q_map = {
                '120': '超清 4K',
                '116': '高清 1080P60',
                '112': '高清 1080P+',
                '80': '高清 1080P',
                '74': '高清 720P60',
                '64': '高清 720P',
                '48': '高清 720P (MP4)',
                '32': '清晰 480P',
                '16': '流畅 360P'
            };
            const msg = '' +
                `<div style="margin:2% 0;">
                    <label>视频格式:</label>
                    <select name="format" id="dl_format">
                        <option value="flv" selected>FLV</option>
                        <option value="mp4">MP4</option>
                    </select><br/>
                    <span>番剧等视频类型不支持MP4请求</span>
                    </div>
                    <div style="margin:2% 0;">
                        <label>视频质量:${q_map[`${q}`]}</label><br/>
                        <span>播放器选择的视频质量,不存在则默认1080P</span>
                    </div>
                    <font color="red">请不要选择过多视频,视频地址有效时间不长,下载可能失败</font><br />
                    <div style="height:240px;width:100%;overflow:auto;background:rgba(0,0,0,0.1);">
                        ${video_html}
                    </div>
                    <button id="checkbox_btn">全选</button>
                </div>`;
            utils.MessageBox.confirm(msg, () => {
                const videos = [];
                for (let i = 0; i < total; i++) {
                    if (!$(`input#option_${i}`).is(':checked')) {
                        continue;
                    }
                    const p = i + 1;
                    const [aid, cid, epid, filename] = [
                        video_base.aid(p),
                        video_base.cid(p),
                        video_base.epid(p),
                        video_base.filename(p)
                    ];
                    const format = $('#dl_format').val();
                    videos.push({
                        url: `${config.base_api}?av=${aid}&p=${p}&cid=${cid}&ep=${epid}&q=${q}&type=${type}&format=${format}&otype=json&auth_id=${auth_id}&auth_sec=${auth_sec}`,
                        filename: filename
                    });
                }
                const video_urls = [];
                get_url(videos, 0, video_urls);

            });

            function get_url(videos, i, video_urls) {
                // 单线递归处理
                if (videos.length) {
                    if (i < videos.length) {
                        const video = videos[i];
                        const msg = `第${i + 1}(${i + 1}/${videos.length})个视频`;
                        utils.MessageBox.alert(`${msg}:获取中...`);
                        $.ajax(video.url, {
                            type: 'GET',
                            dataType: 'json',
                            success: (res) => {
                                if (!res.code) {
                                    utils.MessageBox.alert(`${msg}:获取成功!`);
                                    let video_format = '';
                                    if (res.url.match('.flv')) {
                                        video_format = '.flv';
                                    } else if (res.url.match('.mp4')) {
                                        video_format = '.mp4';
                                    }
                                    video_urls.push({
                                        url: res.url,
                                        filename: video.filename + video_format
                                    });
                                } else {
                                    utils.Message.warning(`第${i + 1}个视频请求失败:` + res.message);
                                }
                                get_url(videos, ++i, video_urls);
                            },
                            error: () => {
                                utils.Message.danger(`第${i + 1}个视频请求异常`);
                                get_url(videos, ++i, video_urls);
                            }
                        });
                    } else {
                        utils.MessageBox.alert('视频地址获取完成,发送RPC下载请求');
                        download_rpc_all(video_urls);
                    }
                }
            }

            function download_rpc_all(video_urls) {
                const rpc = {
                    domain: config.rpc_domain,
                    port: config.rpc_port,
                    token: config.rpc_token,
                    dir: config.rpc_dir,
                };
                const json_rpc = [];
                for (const video of video_urls) {
                    json_rpc.push({
                        id: window.btoa(`BParse_${Date.now()}_${Math.random()}`),
                        jsonrpc: '2.0',
                        method: 'aria2.addUri',
                        params: [`token:${rpc.token}`, [video.url], {
                            dir: rpc.dir,
                            out: video.filename,
                            header: [
                                `User-Agent: ${window.navigator.userAgent}`,
                                `Referer: ${window.location.href}`
                            ]
                        }]
                    });
                }
                utils.Message.info('发送RPC下载请求');
                $.ajax(`${rpc.domain}:${rpc.port}/jsonrpc`, {
                    type: 'POST',
                    dataType: 'json',
                    data: JSON.stringify(json_rpc),
                    success: (res) => {
                        if (res.length === json_rpc.length) {
                            utils.Message.success('RPC请求成功');
                        } else {
                            utils.Message.warning('RPC请求失败');
                        }
                    },
                    error: () => {
                        utils.Message.danger('RPC请求异常,请确认RPC服务配置及软件运行状态');
                    }
                });
            }

        }

        function download_rpc(url, filename) {
            if (window.bp_download_rpc_clicked) {
                utils.Message.warning('(^・ω・^)~喵喵喵~');
                return;
            }
            window.bp_download_rpc_clicked = true;
            const rpc = {
                domain: config.rpc_domain,
                port: config.rpc_port,
                token: config.rpc_token,
                dir: config.rpc_dir,
            };
            const json_rpc = {
                id: window.btoa(`BParse_${Date.now()}_${Math.random()}`),
                jsonrpc: '2.0',
                method: 'aria2.addUri',
                params: [`token:${rpc.token}`, [url], {
                    dir: rpc.dir,
                    out: filename,
                    header: [
                        `User-Agent: ${window.navigator.userAgent}`,
                        `Referer: ${window.location.href}`
                    ]
                }]
            };
            utils.Message.info('发送RPC下载请求');
            $.ajax(`${rpc.domain}:${rpc.port}/jsonrpc`, {
                type: 'POST',
                dataType: 'json',
                data: JSON.stringify(json_rpc),
                success: (res) => {
                    if (res.result) {
                        utils.Message.success('RPC请求成功');
                    } else {
                        utils.Message.warning('RPC请求失败');
                    }
                    window.bp_download_rpc_clicked = false;
                },
                error: () => {
                    utils.Message.danger('RPC请求异常,请确认RPC服务配置及软件运行状态');
                    window.bp_download_rpc_clicked = false;
                }
            });
        }

        function show_progress({ total, loaded, percent }) {
            if (window.bp_show_progress) {
                utils.MessageBox.alert(`文件大小:${Math.floor(total / (1024 * 1024))}MB(${total}Byte)<br/>` +
                    `已经下载:${Math.floor(loaded / (1024 * 1024))}MB(${loaded}Byte)<br/>` +
                    `当前进度:${percent}%<br/>下载中请勿操作浏览器!`, () => {
                        window.bp_show_progress = false;
                        utils.MessageBox.alert('注意:刷新或离开页面会导致下载取消!<br/>再次点击下载按钮可查看下载进度。');
                    });
            }
            if (total === loaded) {
                utils.MessageBox.alert('下载完成,请等待浏览器保存!');
                window.bp_download_blob_clicked = false;
            }
        }

        function download_blob(url, filename) {
            if (window.bp_download_blob_clicked) {
                utils.Message.warning('(^・ω・^)~喵喵喵~');
                window.bp_show_progress = true;
                return;
            }
            const xhr = new XMLHttpRequest();
            xhr.open('get', url);
            xhr.responseType = 'blob';
            xhr.onload = function () {
                if (this.status === 200 || this.status === 304) {
                    if ('msSaveOrOpenBlob' in navigator) {
                        navigator.msSaveOrOpenBlob(this.response, filename);
                        return;
                    }
                    const blob_url = URL.createObjectURL(this.response);
                    const a = document.createElement('a');
                    a.style.display = 'none';
                    a.href = blob_url;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    URL.revokeObjectURL(blob_url);
                }
            };
            window.bp_show_progress = true;
            xhr.onprogress = function (evt) {
                if (this.state != 4) {
                    const loaded = evt.loaded;
                    const tot = evt.total;
                    show_progress({
                        total: tot,
                        loaded: loaded,
                        percent: Math.floor(100 * loaded / tot)
                    });
                }
            };
            xhr.send();
            window.bp_download_blob_clicked = true; // locked
            utils.Message.info('准备开始下载');
        }

        // Player
        utils.Player = {
            replace: replace_player,
            recover: recover_player
        };

        function request_danmaku(options, _cid) {
            if (!_cid) {
                options.error('cid未知,无法获取弹幕');
                return;
            }
            $.ajax(`https://api.bilibili.com/x/v1/dm/list.so?oid=${_cid}`, {
                dataType: 'text',
                success: (result) => {
                    const result_dom = $(result.replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/g, ''));
                    if (!result_dom) {
                        options.error('弹幕获取失败');
                        return;
                    }
                    if (!result_dom.find('d')[0]) {
                        options.error('未发现弹幕');
                    } else {
                        const danmaku_data = result_dom.find('d').map((i, el) => {
                            const item = $(el);
                            const p = item.attr('p').split(',');
                            let type = 0;
                            if (p[1] === '4') {
                                type = 2;
                            } else if (p[1] === '5') {
                                type = 1;
                            }
                            return [{ author: '', time: parseFloat(p[0]), type: type, color: parseInt(p[3]), id: '', text: item.text() }];
                        }).get();
                        options.success(danmaku_data);
                    }
                },
                error: () => {
                    options.error('弹幕请求异常');
                }
            });
        }

        let bili_player_id;

        function replace_player(url, url_2) {
            // 恢复原视频
            recover_player();
            // 暂停原视频
            bili_video_stop();
            const bili_video = $(bili_video_tag())[0];
            !!bili_video && bili_video.addEventListener('play', bili_video_stop, false);

            if (!!$('#bilibiliPlayer')[0]) {
                bili_player_id = '#bilibiliPlayer';
                $(bili_player_id).before('<div id="my_dplayer" class="bilibili-player relative bilibili-player-no-cursor">');
                $(bili_player_id).hide();
            } else if (!!$('#bilibili-player')[0]) {
                bili_player_id = '#bilibili-player';
                $(bili_player_id).before('<div id="my_dplayer" class="bilibili-player relative bilibili-player-no-cursor" style="width:100%;height:100%;"></div>');
                $(bili_player_id).hide();
            } else if (VideoStatus.type() === 'cheese') {
                if (!!$('div.bpx-player[data-injector="nano"]')[0]) {
                    $('#pay-mask').hide();
                    $('#bofqi').show();
                    bili_player_id = 'div.bpx-player[data-injector="nano"]';
                    $(bili_player_id).before('<div id="my_dplayer" style="width:100%;height:100%;"></div>');
                    $(bili_player_id).hide();
                } else { // 第一次
                    bili_player_id = '#pay-mask';
                    $(bili_player_id).html('<div id="my_dplayer" style="width:100%;height:100%;"></div>');
                }
            }
            $('#player_mask_module').hide();
            window.my_dplayer = new DPlayer({
                container: $('#my_dplayer')[0],
                mutex: false,
                video: {
                    url: url,
                    type: 'auto'
                },
                danmaku: true,
                apiBackend: {
                    read: function (options) {
                        request_danmaku(options, VideoStatus.base().cid());
                    },
                    send: function (options) { // ?
                        options.error('此脚本无法将弹幕同步到云端');
                    }
                },
                contextmenu: [
                    {
                        text: '脚本信息',
                        link: 'https://github.com/injahow/bilibili-parse'
                    },
                    {
                        text: '脚本作者',
                        link: 'https://injahow.com'
                    }
                ]
            });
            if (config.format === 'dash' && url_2 && url_2 !== '#') {
                $('body').append('<div id="my_dplayer_2" style="display:none"></div>');
                window.my_dplayer_2 = new DPlayer({
                    container: $('#my_dplayer_2')[0],
                    mutex: false,
                    video: {
                        url: url_2,
                        type: 'auto'
                    }
                });
                const my_dplayer = window.my_dplayer;
                const my_dplayer_2 = window.my_dplayer_2;
                my_dplayer.on('play', function () {
                    !my_dplayer.paused && my_dplayer_2.play();
                });
                my_dplayer.on('playing', function () {
                    !my_dplayer.paused && my_dplayer_2.play();
                });
                my_dplayer.on('timeupdate', function () {
                    if (Math.abs(my_dplayer.video.currentTime - my_dplayer_2.video.currentTime) > 1) {
                        my_dplayer_2.pause();
                        my_dplayer_2.seek(my_dplayer.video.currentTime);
                    }
                    !my_dplayer.paused && my_dplayer_2.play();
                });
                my_dplayer.on('seeking', function () {
                    my_dplayer_2.pause();
                    my_dplayer_2.seek(my_dplayer.video.currentTime);
                });
                my_dplayer.on('waiting', function () {
                    my_dplayer_2.pause();
                });
                my_dplayer.on('pause', function () {
                    my_dplayer_2.pause();
                });
                my_dplayer.on('suspend', function () {
                    my_dplayer_2.speed(my_dplayer.video.playbackRate);
                });
                my_dplayer.on('volumechange', function () {
                    my_dplayer_2.volume(my_dplayer.video.volume);
                    my_dplayer_2.video.muted = my_dplayer.video.muted;
                });
            }
        }

        function bili_video_tag() {
            if (!!$('video[crossorigin="anonymous"]')[0]) {
                return 'video[crossorigin="anonymous"]';
            } else if (!!$('bwp-video')[0]) {
                return 'bwp-video';
            }
            return '';
        }

        function bili_video_stop() { // listener
            let bili_video;
            if (bili_video = $(bili_video_tag())[0]) {
                bili_video.pause();
                bili_video.currentTime = 0;
            }
        }

        function recover_player() {
            if (window.my_dplayer) {
                utils.Message.info('恢复播放器');
                const bili_video = $(bili_video_tag())[0];
                !!bili_video && bili_video.removeEventListener('play', bili_video_stop, false);
                window.my_dplayer.destroy();
                window.my_dplayer = null;
                $('#my_dplayer').remove();
                if (window.my_dplayer_2) {
                    window.my_dplayer_2.destroy();
                    window.my_dplayer_2 = null;
                    $('#my_dplayer_2').remove();
                }
                $(bili_player_id).show();
                /*$('#player_mask_module').show();*/
            }
        }

        // Message & MessageBox
        utils.Message = {
            success: (html) => {
                message(html, 'success');
            },
            warning: (html) => {
                message(html, 'warning');
            },
            danger: (html) => {
                message(html, 'danger');
            },
            info: (html) => {
                message(html, 'info');
            }
        };
        utils.MessageBox = {
            alert: (html, affirm) => {
                messageBox({
                    html, callback: { affirm }
                }, 'alert');
            },
            confirm: (html, affirm, cancel) => {
                messageBox({
                    html, callback: {
                        affirm, cancel
                    }
                }, 'confirm');
            }
        };
        const components_css = '' +
            '<style>' +
            '.message-bg{position:fixed;float:right;right:0;top:2%;z-index:10001;}' +
            '.message{margin-bottom:15px;padding:4px 12px;width:300px;display:flex;margin-top:-70px;opacity:0;}' +
            '.message-danger{background-color:#ffdddd;border-left:6px solid #f44336;}' +
            '.message-success{background-color:#ddffdd;border-left:6px solid #4caf50;}' +
            '.message-info{background-color:#e7f3fe;border-left:6px solid #0c86de;}' +
            '.message-warning{background-color:#ffffcc;border-left:6px solid #ffeb3b;}' +
            '.message-context{font-size:21px;word-wrap:break-word;word-break:break-all;}' +
            '.message_box_btn{text-align:right;}.message_box_btn button{margin:0 5px;}' +
            '</style>';
        const components_html = '' +
            '<div class="message-bg"></div>' +
            '<div id="message_box" style="opacity:0;display:none;position:fixed;inset:0px;top:0px;left:0px;width:100%;height:100%;background:rgba(0,0,0,0.7);animation-name:settings-bg;animation-duration:0.3s;z-index:10000;cursor:default;">' +
            '<div style="position:absolute;background:rgb(255,255,255);border-radius:10px;padding:20px;top:50%;left:50%;width:400px;transform:translate(-50%,-50%);cursor:default;">' +
            '<span style="font-size:20px"><b>提示:</b></span>' +
            '<div id="message_box_context" style="margin:2% 0;">...</div><br/><br/>' +
            '<div class="message_box_btn">' +
            '<button class="setting-button" name="affirm">确定</button>' +
            '<button class="setting-button" name="cancel">取消</button></div>' +
            '</div></div>';

        function messageBox(ctx, type) {
            if (type === 'confirm') {
                $('div.message_box_btn button[name="cancel"]').show();
            } else if (type === 'alert') {
                $('div.message_box_btn button[name="cancel"]').hide();
            }
            if (ctx.html) {
                $('div#message_box_context').html(`<div style="font-size:18px">${ctx.html}</div>`);
            } else {
                $('div#message_box_context').html('<div style="font-size:18px">╰( ̄▽ ̄)╮</div>');
            }
            $('#message_box').show();
            $('div#message_box').animate({
                'opacity': '1'
            }, 300);
            $('div.message_box_btn button[name="affirm"]')[0].onclick = () => {
                $('div#message_box').hide();
                if (ctx.callback && ctx.callback.affirm) {
                    ctx.callback.affirm();
                }
            };
            $('div.message_box_btn button[name="cancel"]')[0].onclick = () => {
                $('div#message_box').hide();
                if (ctx.callback && ctx.callback.cancel) {
                    ctx.callback.cancel();
                }
            };
        }

        let id = 0;

        function message(html, type) {
            id += 1;
            messageEnQueue(`<div id="message-${id}" class="message message-${type}"><div class="message-context"><p><strong>${type}:</strong></p><p>${html}</p></div></div>`, id);
            messageDeQueue(id);
        }

        function messageEnQueue(message, id) {
            $('div.message-bg').append(message);
            $(`div#message-${id}`).animate({
                'margin-top': '+=70px',
                'opacity': '1',
            }, 300);
        }

        function messageDeQueue(id, time = 3000) {
            setTimeout(() => {
                const e = `div#message-${id}`;
                $(e).animate({
                    'margin-top': '-=70px',
                    'opacity': '0',
                }, 300, () => {
                    $(e).remove();
                });
            }, time);
        }

        $('body').append(components_html + components_css);
    })();

    // error page redirect -> ss / ep
    if ($('.error-text')[0]) {
        return;
    }

    // video
    let VideoStatus;
    (function () {
        VideoStatus = {
            type, base, get_quality
        };

        function type() {
            if (location.pathname.match('/cheese/play/')) {
                return 'cheese';
            }
            if (!!window.__INITIAL_STATE__.epInfo) {
                return 'bangumi';
            } else if (!!window.__INITIAL_STATE__.videoData) {
                return 'video';
            }
        }

        function base() {
            const _type = type();
            if (_type === 'video') {
                const state = window.__INITIAL_STATE__;
                return {
                    total: () => {
                        return state.videoData.pages.length || 1;
                    },
                    title: (_p) => {
                        const p = _p || state.p || 1;
                        return (state.videoData.pages[p - 1].part || 'unknown').replace(/[\/\\:*?"<>|]+/g, '');
                    },
                    filename: (_p) => {
                        const p = _p || state.p || 1;
                        const title = (state.videoData && state.videoData.title || 'unknown') + ` P${p} (${state.videoData.pages[p - 1].part || p})`;
                        return title.replace(/[\/\\:*?"<>|]+/g, '');
                    },
                    aid: (_p) => {
                        return state.videoData.aid;
                    },
                    p: () => {
                        return state.p || 1;
                    },
                    cid: (_p) => {
                        const p = _p || state.p || 1;
                        return state.videoData.pages[p - 1].cid;
                    },
                    epid: (_p) => {
                        return '';
                    },
                    need_vip: () => {
                        return false;
                    },
                    vip_need_pay: () => {
                        return false;
                    }
                };
            } else if (_type === 'bangumi') {
                const state = window.__INITIAL_STATE__;
                return {
                    total: () => {
                        return state.epList.length;
                    },
                    title: (_p) => {
                        let ep;
                        if (_p) {
                            ep = state.epList[_p - 1];
                        } else {
                            ep = state.epInfo;
                        }
                        return (`${ep.titleFormat} ${ep.longTitle}` || 'unknown').replace(/[\/\\:*?"<>|]+/g, '');
                    },
                    filename: (_p) => {
                        if (_p) {
                            const ep = state.epList[_p - 1];
                            return (`${state.mediaInfo.season_title}:${ep.titleFormat} ${ep.longTitle}` || 'unknown').replace(/[\/\\:*?"<>|]+/g, '');
                        }
                        return (state.h1Title || 'unknown').replace(/[\/\\:*?"<>|]+/g, '');
                    },
                    aid: (_p) => {
                        if (_p) {
                            return state.epList[_p - 1].aid;
                        }
                        return state.epInfo.aid;
                    },
                    p: () => {
                        return state.epInfo.i || 1;
                    },
                    cid: (_p) => {
                        if (_p) {
                            return state.epList[_p - 1].cid;
                        }
                        return state.epInfo.cid;
                    },
                    epid: (_p) => {
                        if (_p) {
                            return state.epList[_p - 1].id;
                        }
                        return state.epInfo.id;
                    },
                    need_vip: () => {
                        return state.epInfo.badge === '会员';
                    },
                    vip_need_pay: () => {
                        return state.epPayMent.vipNeedPay;
                    }
                };
            } else if (_type === 'cheese') {
                const episodes = window.PlayerAgent.getEpisodes();
                const _id = $('li.on.list-box-li').index() || 0;
                return {
                    total: () => {
                        return 3;
                    },
                    title: (_p) => {
                        let id = _id;
                        _p && (id = _p - 1);
                        return (episodes[id].title || 'unknown').replace(/[\/\\:*?"<>|]+/g, '');
                    },
                    filename: (_p) => {
                        let id = _id;
                        _p && (id = _p - 1);
                        return (`${$('div.season-info h1').html()} P${id + 1} (${episodes[id].title || 'unknown'})`).replace(/[\/\\:*?"<>|]+/g, '');
                    },
                    aid: (_p) => {
                        let id = _id;
                        _p && (id = _p - 1);
                        return episodes[id].aid;
                    },
                    p: () => {
                        return _id + 1;
                    },
                    cid: (_p) => {
                        let id = _id;
                        _p && (id = _p - 1);
                        return episodes[id].cid;
                    },
                    epid: (_p) => {
                        let id = _id;
                        _p && (id = _p - 1);
                        return episodes[id].id;
                    },
                    need_vip: () => {
                        return false;
                    },
                    vip_need_pay: () => {
                        return false;
                    }
                };
            }
        }

        function get_quality() {
            let _q = 0, _q_max = 0;
            if (!!$('li.bui-select-item')[0] && !!(_q_max = parseInt($('li.bui-select-item')[0].dataset.value))) {
                _q = parseInt($('li.bui-select-item.bui-select-item-active').attr('data-value')) || (_q_max > 80 ? 80 : _q_max);
            } else if (!!$('li.squirtle-select-item')[0] && !!(_q_max = parseInt($('li.squirtle-select-item')[0].dataset.value))) {
                _q = parseInt($('li.squirtle-select-item.active').attr('data-value')) || (_q_max > 80 ? 80 : _q_max);
            } else {
                _q = _q_max = 80;
            }
            if (!UserStatus.is_login()) {
                _q = _q_max > 80 ? 80 : _q_max;
            }
            return { q: _q, q_max: _q_max };
        }

    })();

    // check
    const check = {
        aid: '', cid: '', q: '', epid: ''
    };
    (function () {
        function refresh() {
            //utils.Message.info('refresh...');
            console.log('refresh...');
            $('#video_download').hide();
            $('#video_download_2').hide();
            utils.Player.recover();
            // 更新check
            const video_base = VideoStatus.base();
            [check.aid, check.cid, check.epid] = [
                video_base.aid(),
                video_base.cid(),
                video_base.epid()
            ];
            check.q = VideoStatus.get_quality().q;
        }

        // 监听p
        $('body').on('click', 'a.router-link-active', function () {
            if (this !== $('li[class="on"]').find('a')[0]) {
                refresh();
            }
        });
        $('body').on('click', 'li.ep-item', function () {
            refresh();
        });
        $('body').on('click', 'button.bilibili-player-iconfont-next', function () {
            refresh();
        });
        !!$('video[crossorigin="anonymous"]')[0] && ($('video[crossorigin="anonymous"]')[0].onended = function () {
            refresh();
        });
        // 监听q
        $('body').on('click', 'li.bui-select-item', function () {
            refresh();
        });
        setInterval(function () {
            if (check.q !== VideoStatus.get_quality().q) {
                refresh();
            } else if (VideoStatus.type() === 'cheese') {
                // epid for cheese
                if (check.epid !== VideoStatus.base().epid()) {
                    refresh();
                }
            }
        }, 1000);
        // 监听aid
        $('body').on('click', '.rec-list', function () {
            refresh();
        });
        $('body').on('click', '.bilibili-player-ending-panel-box-videos', function () {
            refresh();
        });
        // 定时检查 aid 和 cid
        setInterval(function () {
            const video_base = VideoStatus.base();
            if (check.aid !== video_base.aid() || check.cid !== video_base.cid()) {
                refresh();
            }
        }, 3000);

    })();

    // main
    (function () {
        $('body').append('<a id="video_url" style="display:none" target="_blank" referrerpolicy="origin" href="#"></a>');
        $('body').append('<a id="video_url_2" style="display:none" target="_blank" referrerpolicy="origin" href="#"></a>');
        // 暂且延迟处理...
        setTimeout(function () {
            let my_toolbar;
            if (!!$('#arc_toolbar_report')[0]) {
                my_toolbar = '' +
                    '<div id="arc_toolbar_report_2" class="video-toolbar report-wrap-module report-scroll-module" scrollshow="true"><div class="ops">' +
                    '<span id="setting_btn"><i class="van-icon-general_addto_s"></i>脚本设置</span>' +
                    '<span id="bilibili_parse"><i class="van-icon-floatwindow_custome"></i>请求地址</span>' +
                    '<span id="video_download" style="display:none"><i class="van-icon-download"></i>下载视频</span>' +
                    '<span id="video_download_2" style="display:none"><i class="van-icon-download"></i>下载音频</span>' +
                    '<span id="video_download_all"><i class="van-icon-download"></i>批量下载</span>' +
                    '</div></div>';
                $('#arc_toolbar_report').after(my_toolbar);
            } else if (!!$('#toolbar_module')[0]) {
                my_toolbar = '' +
                    '<div id="toolbar_module_2" class="tool-bar clearfix report-wrap-module report-scroll-module media-info" scrollshow="true">' +
                    '<div id="setting_btn" class="like-info"><i class="iconfont icon-add"></i><span>脚本设置</span></div>' +
                    '<div id="bilibili_parse" class="like-info"><i class="iconfont icon-customer-serv"></i><span>请求地址</span></div>' +
                    '<div id="video_download" class="like-info" style="display:none"><i class="iconfont icon-download"></i><span>下载视频</span></div>' +
                    '<div id="video_download_2" class="like-info" style="display:none"><i class="iconfont icon-download"></i><span>下载音频</span></div>' +
                    '<div id="video_download_all" class="like-info"><i class="iconfont icon-download"></i><span>批量下载</span></div>' +
                    '</div>';
                $('#toolbar_module').after(my_toolbar);
            } else if (!!$('div.video-toolbar')[0]) {
                my_toolbar = '' +
                    '<div id="arc_toolbar_report_2" class="video-toolbar report-wrap-module report-scroll-module" scrollshow="true"><div class="ops">' +
                    '<span id="setting_btn"><i class="van-icon-general_addto_s"></i>脚本设置</span>' +
                    '<span id="bilibili_parse"><i class="van-icon-floatwindow_custome"></i>请求地址</span>' +
                    '<span id="video_download" style="display:none"><i class="van-icon-download"></i>下载视频</span>' +
                    '<span id="video_download_2" style="display:none"><i class="van-icon-download"></i>下载音频</span>' +
                    '<span id="video_download_all"><i class="van-icon-download"></i>批量下载</span>' +
                    '</div></div>';
                $('div.video-toolbar').after(my_toolbar);
            }
            UserStatus.lazy_init();
            Auth.check_login_status();
        }, 3000);
        $('body').on('click', '#setting_btn', function () {
            // set form by config
            for (const key in config) {
                if (Object.hasOwnProperty.call(config, key)) {
                    $(`#${key}`).val(config[key]);
                }
            }
            $('#my_config').show();
        });
        $('body').on('click', '#video_download_all', function () {
            if (localStorage.getItem('bp_auth_id') && localStorage.getItem('bp_auth_sec')) {
                if (config.download_type === 'rpc') {
                    utils.Video.download_all();
                } else {
                    utils.MessageBox.confirm('仅支持使用RPC接口批量下载,请确保RPC环境正常,是否继续?', () => {
                        utils.Video.download_all();
                    });
                }
            } else {
                utils.MessageBox.confirm('批量下载仅支持授权用户使用RPC接口下载,是否进行授权?', () => {
                    window.bp_show_login();
                });
            }
        });
        $('body').on('click', '#video_download', function () {
            const type = config.download_type;
            if (type === 'web') {
                $('#video_url')[0].click();
            } else if (type === 'a') {
                const [video_url, video_url_2] = [
                    $('#video_url').attr('href'),
                    $('#video_url_2').attr('href')
                ];
                const msg = '建议使用IDM、FDM等软件安装其浏览器插件后,鼠标右键点击链接下载~<br/><br/>' +
                    `<a href="${video_url}" target="_blank">视频地址</a><br/><br/>` +
                    (config.format === 'dash' ? `<a href="${video_url_2}" target="_blank">音频地址</a>` : '');
                utils.MessageBox.alert(msg);
            } else if (type === 'aria') {
                const [video_url, video_url_2] = [
                    $('#video_url').attr('href'),
                    $('#video_url_2').attr('href')
                ];
                const video_title = VideoStatus.base().filename();
                let file_name, file_name_2;
                if (video_url.match('.flv')) {
                    file_name = video_title + '.flv';
                } else if (video_url.match('.m4s')) {
                    file_name = video_title + '_video.mp4';
                } else if (video_url.match('.mp4')) {
                    file_name = video_title + '.mp4';
                }
                file_name_2 = video_title + '_audio.mp4';
                const aria2_header = `--header "User-Agent: ${window.navigator.userAgent}" --header "Referer: ${window.location.href}"`;
                const [code, code_2] = [
                    `aria2c "${video_url}" --out "${file_name}" ${aria2_header}`,
                    `aria2c "${video_url_2}" --out "${file_name_2}" ${aria2_header}`
                ]
                const msg = '点击文本框即可复制下载命令!<br/><br/>' +
                    `视频:<br/><input id="aria2_code" value='${code}' onclick="bp_clip_btn('aria2_code')" style="width:100%;"></br></br>` +
                    (config.format === 'dash' ? `音频:<br/><input id="aria2_code_2" value='${code_2}' onclick="bp_clip_btn('aria2_code_2')" style="width:100%;"><br/><br/>` +
                        `全部:<br/><textarea id="aria2_code_all" onclick="bp_clip_btn('aria2_code_all')" style="min-width:100%;max-width:100%;min-height:100px;max-height:100px;">${code}\n${code_2}</textarea>` : '');
                !window.bp_clip_btn && (window.bp_clip_btn = (id) => {
                    $(`#${id}`).select();
                    if (document.execCommand('copy')) {
                        utils.Message.success('复制成功');
                    } else {
                        utils.Message.warning('复制失败');
                    }
                });
                utils.MessageBox.alert(msg);
            } else {
                const url = $('#video_url').attr('href');
                let file_name = VideoStatus.base().filename();
                if (url.match('.flv')) {
                    file_name += '.flv';
                } else if (url.match('.m4s')) {
                    file_name += '_video.mp4';
                } else if (url.match('.mp4')) {
                    file_name += '.mp4';
                } else {
                    return;
                }
                utils.Video.download(url, file_name, type);
            }
        });
        $('body').on('click', '#video_download_2', function () {
            const type = config.download_type;
            if (type === 'web') {
                $('#video_url_2')[0].click();
            } else if (type === 'a') {
                $('#video_download')[0].click();
            } else if (type === 'aria') {
                $('#video_download')[0].click();
            } else {
                const url = $('#video_url_2').attr('href');
                let file_name = VideoStatus.base().filename();
                if (url.match('.m4s')) {
                    file_name += '_audio.mp4';
                } else {
                    return;
                }
                utils.Video.download(url, file_name, type);
            }
        });
        let api_url, api_url_temp;
        $('body').on('click', '#bilibili_parse', function () {
            UserStatus.lazy_init(true); // init
            const video_base = VideoStatus.base();
            const [aid, p, cid, epid] = [
                video_base.aid(), video_base.p(), video_base.cid(), video_base.epid()
            ];
            const [type, q] = [
                VideoStatus.type(), VideoStatus.get_quality().q
            ];
            api_url = `${config.base_api}?av=${aid}&p=${p}&cid=${cid}&ep=${epid}&q=${q}&type=${type}&format=${config.format}&otype=json`;
            const [auth_id, auth_sec] = [
                localStorage.getItem('bp_auth_id') || '',
                localStorage.getItem('bp_auth_sec') || ''
            ];
            if (config.auth === '1' && auth_id && auth_sec) {
                api_url += `&auth_id=${auth_id}&auth_sec=${auth_sec}`;
            }
            if (api_url === api_url_temp) {
                utils.Message.info('(^・ω・^)~喵喵喵~');
                const url = $('#video_url').attr('href');
                const url_2 = $('#video_url_2').attr('href');
                if (url && url !== '#') {
                    $('#video_download').show();
                    config.format === 'dash' && $('#video_download_2').show();
                    if (UserStatus.need_replace() || config.replace_force === '1') {
                        !$('#my_dplayer')[0] && utils.Player.replace(url, url_2);
                    }
                }
                return;
            }
            $('#video_url').attr('href', '#');
            $('#video_url_2').attr('href', '#');
            api_url_temp = api_url;
            utils.Message.info('开始请求');
            $.ajax(api_url, {
                dataType: 'json',
                success: (result) => {
                    if (result && result.code === 0) {
                        utils.Message.success('请求成功');
                        const url = config.format === 'dash' ? result.video.replace(/^https?\:\/\//i, 'https://') : result.url.replace(/^https?\:\/\//i, 'https://');
                        const url_2 = config.format === 'dash' ? result.audio.replace(/^https?\:\/\//i, 'https://') : '#';
                        $('#video_url').attr('href', url);
                        $('#video_download').show();
                        if (config.format === 'dash') {
                            $('#video_url_2').attr('href', url_2);
                            $('#video_download_2').show();
                        }
                        if (UserStatus.need_replace() || config.replace_force === '1') {
                            utils.Player.replace(url, url_2);
                        }
                    } else {
                        utils.Message.warning('请求失败:' + result.message);
                    }
                },
                error: (e) => {
                    utils.Message.danger('请求异常');
                    console.log('error', e);
                }
            });
        });
    })();

})();