// ==UserScript== // @name bilibili视频下载 // @namespace https://github.com/injahow // @version 1.6.10 // @description 支持Web、RPC、Blob、Aria等下载方式;支持flv、dash、mp4视频格式;支持下载港区番剧;支持会员下载;支持换源播放,自动切换为高清视频源 // @author injahow // @source https://github.com/injahow/bilibili-parse // @copyright 2021, injahow (https://github.com/injahow) // @include *://www.bilibili.com/video/av* // @include *://www.bilibili.com/video/BV* // @include *://www.bilibili.com/medialist/play/* // @include *://www.bilibili.com/bangumi/play/ep* // @include *://www.bilibili.com/bangumi/play/ss* // @include *://www.bilibili.com/cheese/play/ep* // @include *://www.bilibili.com/cheese/play/ss* // @include https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png* // @require https://static.hdslb.com/js/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 // @downloadURL 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, uname, need_replace }; let _is_login = false, _vip_status = 0, _mid = '', _uname = ''; 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 || ''; _uname = window.__BILI_USER_INFO__.uname || ''; } 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 || ''; _uname = window.__BiliUser__.cache.data.uname || ''; } else { _vip_status = 0; _mid = ''; _uname = ''; } } else { _is_login = false; _vip_status = 0; _mid = ''; _uname = ''; } is_init = last_init; } } function is_login() { return _is_login; } function vip_status() { return _vip_status; } function mid() { return _mid; } function uname() { return _uname; } 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') && window !== window.parent) { window.stop(); window.parent.postMessage('bilibili-parse-login-credentials: ' + location.href, '*'); } Auth = null; return; } Auth = { check_login_status }; let auth_clicked = false; 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 (auto = '1') { if (auth_clicked) { utils.Message.info('(^・ω・^)~喵喵喵~'); return; } auth_clicked = true; if (localStorage.getItem('bp_access_key')) { utils.MessageBox.confirm('发现授权记录,是否重新授权?', () => { if (auto === '1') { login(); } else { login_manual(); } }, () => { auth_clicked = false; }); } else { if (auto === '1') { login(); } else { login_manual(); } } } function login() { $.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) { $('body').append(``); } else { utils.MessageBox.confirm('必须登录B站才能正常授权,是否登陆?', () => { location.href = 'https://passport.bilibili.com/login'; }, () => { auth_clicked = false; }); } }, error: () => { utils.Message.danger('授权请求异常'); auth_clicked = false; } }); } function login_manual() { $.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) { const msg = '' + `请点击授权地址打开一个新窗口,正常情况新窗口应该显示一个用户头像,请将该窗口地址栏的URL链接复制到当前文本框中

然后点击确定即可`; utils.MessageBox.alert(msg, () => { const auth_url = $('#auth_url').val(); const [auth_id, auth_sec] = [ localStorage.getItem('bp_auth_id') || '', localStorage.getItem('bp_auth_sec') || '' ]; $.ajax(auth_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}&`), { 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(auth_url).searchParams.get('access_key')); localStorage.setItem('bp_auth_time', Date.now()); $('#auth').val('1'); config.auth = '1'; } else { utils.Message.warning('授权失败'); } auth_clicked = false; }, error: () => { utils.Message.danger('请求异常'); auth_clicked = false; } }); }); } else { utils.MessageBox.confirm('必须登录B站才能正常授权,是否登陆?', () => { location.href = 'https://passport.bilibili.com/login'; }, () => { auth_clicked = false; }); } }, error: () => { utils.Message.danger('授权请求异常'); auth_clicked = false; } }); } window.bp_show_logout = function () { const [auth_id, auth_sec] = [ localStorage.getItem('bp_auth_id') || '', localStorage.getItem('bp_auth_sec') || '' ]; if (auth_clicked) { utils.Message.info('(^・ω・^)~喵喵喵~'); return; } auth_clicked = true; if (!auth_id) { utils.MessageBox.alert('没有发现授权记录'); 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('取消失败'); } auth_clicked = false; }, error: () => { utils.Message.danger('请求异常'); auth_clicked = false; } }); } window.bp_show_login_help = function () { utils.MessageBox.confirm('进行授权之后将能在请求地址时享有用户账号原有的权益,例如能够请求用户已经付费或承包的番剧,是否需要授权?', () => { window.bp_show_login(); }); } window.addEventListener('message', function (e) { if (typeof e.data !== 'string') return; if (e.data.split(':')[0] === 'bilibili-parse-login-credentials') { $('iframe#auth_iframe').remove(); 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}&`), { 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('授权失败'); } auth_clicked = false; }, error: () => { utils.Message.danger('请求异常'); 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:/', auto_download: '0' }; // 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.bp_save_config = () => { // set config by form for (const key in config) { 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(); utils.Scroll.show(); // 判断是否需要重新请求 for (const key of ['base_api', 'format', 'auth']) { if (config[key] !== old_config[key]) { $('#video_download').hide(); $('#video_download_2').hide(); break; } } // 判断RPC配置情况 if (config['rpc_domain'] !== old_config['rpc_domain']) { if (!(config.rpc_domain.match('https://') || config.rpc_domain.match(/(localhost|127\.0\.0\.1)/))) { utils.MessageBox.alert('' + '检测到当前RPC为非本机的http接口,即将跳转到AriaNG网页控制台页面;' + '请查看控制台RPC接口参数是否正确,第一次加载会比较慢请耐心等待;' + '配置好后即可使用脚本进行远程下载
使用期间不用关闭控制台页面!', () => { utils.open_ariang(); }); } } }; window.onbeforeunload = () => { window.bp_save_config(); const bp_aria2_window = window.bp_aria2_window; if (bp_aria2_window && !bp_aria2_window.closed) { bp_aria2_window.close(); } }; let help_clicked = false; window.bp_show_help = () => { if (help_clicked) { utils.Message.info('(^・ω・^)~喵喵喵~'); return; } 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('获取失败'); } help_clicked = false; }, error: (e) => { utils.Message.danger('请求异常'); help_clicked = false; console.log('error', e); } }); }; !window.bp_reset_config && (window.bp_reset_config = () => { for (const key in default_config) { if (key === 'auth') { continue; } $(`#${key}`).val(default_config[key]); } }); const config_css = '' + ''; const config_html = '' + ``; $('body').append(config_html + config_css); // 初始化配置页面 for (const key in config) { $(`#${key}`).val(config[key]); } })(); // components const utils = { Video: {}, Player: {}, Message: {}, MessageBox: {}, Scroll: {} }; // components_init (function () { utils.open_ariang = open_ariang; // 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, rpc_type()); } }, download_all, }; function rpc_type() { if (config.rpc_domain.match('https://') || config.rpc_domain.match(/localhost|127.0.0.1/)) { return 'post'; } else { return 'ariang'; } } 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] = [ video_base.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 += '' + ``; } 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': '1080P 60帧', '112': '1080P 高码率', '80': '1080P 高清', '74': '720P 60帧', '64': '720P 高清', '48': '720P 高清(MP4)', '32': '480P 清晰', '16': '360P 流畅' }; const quality_support = VideoStatus.get_quality_support(); let option_support_html = ''; for (const item of quality_support) { option_support_html += ``; } const msg = '' + `
为避免请求被拦截,设置了延时且不支持下载无法播放的视频;请勿频繁下载过多视频,可能触发风控导致不可再下载!!!
${video_html}
${VideoStatus.type() === 'medialist' ? '不支持多页视频,若需要请到视频原播放页面下载' : ''}
`; utils.MessageBox.confirm(msg, () => { // 获取参数 let _q = $('#dl_quality').val() || q; 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}&s`, filename: filename }); } get_url(videos, 0, []); }); // 初始化参数 $('#dl_quality').val(q); 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}:获取中...`); setTimeout(function () { $.ajax(video.url, { type: 'GET', dataType: 'json', success: (res) => { if (!res.code) { utils.Message.success('请求成功' + (res.times ? `
今日剩余请求次数${res.times}` : '')); utils.MessageBox.alert(`${msg}:获取成功!`); let video_format = ''; if (res.url.match('.flv')) { video_format = '.flv'; } else if (res.url.match('.mp4')) { video_format = '.mp4'; } const type = rpc_type(); if (type === 'post') { video_urls.push({ url: res.url, filename: video.filename + video_format }); if (video_urls.length > 3) { download_rpc_all(video_urls) video_urls.length = 0; } } else if (type === 'ariang') { download_rpc_ariang_one({ url: res.url, filename: video.filename + video_format }); } } else { utils.Message.warning(`第${i + 1}个视频请求失败:` + res.message); } setTimeout(function () { get_url(videos, ++i, video_urls); }, 1000); }, error: () => { utils.Message.danger(`第${i + 1}个视频请求异常`); get_url(videos, ++i, video_urls); } }); }, 2000); } else { utils.MessageBox.alert('视频地址请求完成!'); if (rpc_type() === 'post') { if (video_urls.length > 0) { download_rpc_all(video_urls); video_urls.length = 0; } } // one by one -> null } } } 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_ariang_one(video) { const bp_aria2_window = window.bp_aria2_window; let time = 100; if (!bp_aria2_window || bp_aria2_window.closed) { open_ariang(); time = 3000; } setTimeout(() => { const bp_aria2_window = window.bp_aria2_window; const aria2_header = `header=User-Agent:${window.navigator.userAgent}&header=Referer:${window.location.href}`; if (bp_aria2_window && !bp_aria2_window.closed) { const task_hash = `#!/new/task?url=${window.btoa(video.url)}&out=${encodeURIComponent(video.filename)}&${aria2_header}`; bp_aria2_window.location.href = `http://ariang.injahow.com/${task_hash}`; utils.Message.success('RPC请求成功'); } else { utils.Message.warning('RPC请求失败'); } }, time); } let download_rpc_clicked = false; function download_rpc(url, filename, type = 'post') { if (download_rpc_clicked) { utils.Message.info('(^・ω・^)~喵喵喵~'); return; } 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下载请求'); if (type === 'post') { $.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请求失败'); } download_rpc_clicked = false; }, error: () => { utils.Message.danger('RPC请求异常,请确认RPC服务配置及软件运行状态'); download_rpc_clicked = false; } }); } else if (type === 'ariang') { const bp_aria2_window = window.bp_aria2_window; let time = 100; if (!bp_aria2_window || bp_aria2_window.closed) { open_ariang(); time = 3000; } setTimeout(() => { const bp_aria2_window = window.bp_aria2_window; const aria2_header = `header=User-Agent:${window.navigator.userAgent}&header=Referer:${window.location.href}`; const task_hash = `#!/new/task?url=${window.btoa(url)}&out=${encodeURIComponent(filename)}&${aria2_header}`; if (bp_aria2_window && !bp_aria2_window.closed) { bp_aria2_window.location.href = `http://ariang.injahow.com/${task_hash}`; utils.Message.success('RPC请求成功'); } else { utils.Message.warning('RPC请求失败'); } download_rpc_clicked = false; }, time); } } function open_ariang() { const a = document.createElement('a'); const rpc = { domain: config.rpc_domain, port: config.rpc_port, token: config.rpc_token }; const url = `http://ariang.injahow.com/#!/settings/rpc/set/${rpc.domain.replace('://', '/')}/${rpc.port}/jsonrpc/${window.btoa(rpc.token)}`; a.setAttribute('target', '_blank'); a.setAttribute('onclick', `window.bp_aria2_window=window.open('${url}');`); document.body.appendChild(a); a.click(); a.remove(); } let download_blob_clicked = false, need_show_progress = true; function show_progress({ total, loaded, percent }) { if (need_show_progress) { utils.MessageBox.alert(`文件大小:${Math.floor(total / (1024 * 1024))}MB(${total}Byte)
` + `已经下载:${Math.floor(loaded / (1024 * 1024))}MB(${loaded}Byte)
` + `当前进度:${percent}%
下载中请勿操作浏览器!`, () => { need_show_progress = false; utils.MessageBox.alert('注意:刷新或离开页面会导致下载取消!
再次点击下载按钮可查看下载进度。'); }); } if (total === loaded) { utils.MessageBox.alert('下载完成,请等待浏览器保存!'); download_blob_clicked = false; } } function download_blob(url, filename) { if (download_blob_clicked) { utils.Message.info('(^・ω・^)~喵喵喵~'); need_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(); a.remove(); URL.revokeObjectURL(blob_url); } }; need_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(); download_blob_clicked = true; // locked utils.Message.info('准备开始下载'); } // Player utils.Player = { replace: replace_player, recover: recover_player, tag: bili_video_tag }; 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(); // 暂停原视频 const bili_video = $(bili_video_tag())[0]; bili_video_stop(); !!bili_video && bili_video.addEventListener('play', bili_video_stop, false); if (!!$('#bilibiliPlayer')[0]) { bili_player_id = '#bilibiliPlayer'; $(bili_player_id).before('
'); $(bili_player_id).hide(); } else if (!!$('#bilibili-player')[0]) { bili_player_id = '#bilibili-player'; $(bili_player_id).before('
'); $(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('
'); $(bili_player_id).hide(); } else { // 第一次 bili_player_id = '#pay-mask'; $(bili_player_id).html('
'); } } $('#player_mask_module').hide(); const dplayer_init = (subtitle_url = '') => { window.my_dplayer = new DPlayer({ container: $('#my_dplayer')[0], mutex: false, volume: 1, autoplay: true, video: { url: url, type: 'auto' }, subtitle: { url: subtitle_url, type: 'webvtt', fontSize: '35px', bottom: '5%', color: '#fff', }, 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(''); window.my_dplayer_2 = new DPlayer({ container: $('#my_dplayer_2')[0], mutex: false, volume: 1, autoplay: true, 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_2.seek(my_dplayer.video.currentTime); }); my_dplayer.on('pause', function () { my_dplayer_2.pause(); my_dplayer_2.seek(my_dplayer.video.currentTime); }); 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; }); } } // 判断是否需要字幕 if (VideoStatus.base().is_limited()) { get_subtitle_url(dplayer_init); } else { dplayer_init(); } } function get_subtitle_url(dplayer_init) { const video_base = VideoStatus.base(); const [aid, cid, epid] = [ video_base.aid(), video_base.cid(), video_base.epid() ]; $.ajax(`https://api.bilibili.com/x/player/v2?aid=${aid}&cid=${cid}&ep_id=${epid}`, { dataType: 'json', success: res => { if (!res.code && res.data.subtitle.subtitles[0]) { $.ajax(`${res.data.subtitle.subtitles[0].subtitle_url}`, { dataType: 'json', success: res => { // json -> webvtt -> blob_url const datas = res.body || [{ from: 0, to: 0, content: '' }]; let webvtt = 'WEBVTT\n\n'; for (let data of datas) { const a = new Date((parseInt(data.from) - 8 * 60 * 60) * 1000).toTimeString().split(' ')[0] + '.' + (data.from.toString().split('.')[1] || '000').padEnd(3, '0'); const b = new Date((parseInt(data.to) - 8 * 60 * 60) * 1000).toTimeString().split(' ')[0] + '.' + (data.to.toString().split('.')[1] || '000').padEnd(3, '0'); webvtt += `${a} --> ${b}\n${data.content}\n`; } dplayer_init(URL.createObjectURL(new Blob([webvtt], { type: 'text/vtt' }))); }, error: _ => { dplayer_init(); } }); } else { dplayer_init(); } }, error: _ => { dplayer_init(); } }); } function bili_video_tag() { if (!!$('bwp-video')[0]) { return 'bwp-video'; } else if (!!$('video[class!="dplayer-video dplayer-video-current"]')[0]) { return 'video[class!="dplayer-video dplayer-video-current"]'; } } function bili_video_stop() { // listener const bili_video = $(bili_video_tag())[0]; if (bili_video) { 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 = '' + ''; const components_html = '' + '
' + ''; 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(`
${ctx.html}
`); } else { $('div#message_box_context').html('
╰( ̄▽ ̄)╮
'); } $('#message_box').show(); hide_scroll(); $('div#message_box').animate({ 'opacity': '1' }, 300); $('div.message_box_btn button[name="affirm"]')[0].onclick = () => { $('div#message_box').hide(); show_scroll(); if (ctx.callback && ctx.callback.affirm) { ctx.callback.affirm(); } }; $('div.message_box_btn button[name="cancel"]')[0].onclick = () => { $('div#message_box').hide(); show_scroll(); if (ctx.callback && ctx.callback.cancel) { ctx.callback.cancel(); } }; } let id = 0; function message(html, type) { id += 1; messageEnQueue(`

${type}:

${html}

`, 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); // Scroll 滚动条 utils.Scroll = { show: show_scroll, hide: hide_scroll }; function show_scroll() { if ($('div#my_config').is(':hidden') && $('div#message_box').is(':hidden')) { $('body').css('overflow', 'auto'); } } function hide_scroll() { $('body').css('overflow', 'hidden'); } })(); // error page redirect -> ss / ep if ($('.error-text')[0]) { return; } // video let VideoStatus; (function () { VideoStatus = { type, base, get_quality, get_quality_support }; function type() { if (location.pathname.match('/cheese/play/')) { return 'cheese'; } else if (location.pathname.match('/medialist/play/')) { // -/ml*/* or -/watchlater/* return 'medialist'; } else if (!window.__INITIAL_STATE__) { // todo return '?'; } else 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 { type: 'video', 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; }, is_limited: () => { return false; } }; } else if (_type === 'medialist') { const medialist = $('div.player-auxiliary-playlist-item'); const _id = $('div.player-auxiliary-playlist-item.player-auxiliary-playlist-item-active').index(); const collect_name = $('.player-auxiliary-playlist-top .player-auxiliary-filter-title').html(); let owner_name; if (location.pathname.match('/medialist/play/watchlater/')) { owner_name = UserStatus.uname(); } else { owner_name = $('.player-auxiliary-playlist-user .player-auxiliary-playlist-ownerName').html(); } return { type: 'video', total: () => { return medialist.length; }, title: (_p) => { let id = _p ? (_p - 1) : _id; const title = medialist.eq(id).find('.player-auxiliary-playlist-item-title').attr('title') || 'unknown'; return title.replace(/[\/\\:*?"<>|]+/g, ''); }, filename: (_p) => { let id = _p ? (_p - 1) : _id; const title = medialist.eq(id).find('.player-auxiliary-playlist-item-title').attr('title') || 'unknown'; return (`${owner_name}-${collect_name} P${id + 1} (${title})`).replace(/[\/\\:*?"<>|]+/g, ''); }, aid: (_p) => { let id = _p ? (_p - 1) : _id; return medialist.eq(id).attr('data-aid'); }, p: () => { return _id + 1; }, cid: (_p) => { let id = _p ? (_p - 1) : _id; return medialist.eq(id).attr('data-cid'); }, epid: (_p) => { return ''; }, need_vip: () => { return false; }, vip_need_pay: () => { return false; }, is_limited: () => { return false; } }; } else if (_type === 'bangumi') { const state = window.__INITIAL_STATE__; return { type: 'bangumi', 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; }, is_limited: () => { return !!state.mediaInfo.season_title.match(/((僅|仅)限.*地(區|区))/g); } }; } else if (_type === 'cheese') { const episodes = window.PlayerAgent.getEpisodes(); const _id = $('li.on.list-box-li').index(); return { type: 'cheese', total: () => { return episodes.length; }, title: (_p) => { let id = _p ? (_p - 1) : _id; return (episodes[id].title || 'unknown').replace(/[\/\\:*?"<>|]+/g, ''); }, filename: (_p) => { let id = _p ? (_p - 1) : _id; return (`${$('div.season-info h1').html()} P${id + 1} (${episodes[id].title || 'unknown'})`).replace(/[\/\\:*?"<>|]+/g, ''); }, aid: (_p) => { let id = _p ? (_p - 1) : _id; return episodes[id].aid; }, p: () => { return _id + 1; }, cid: (_p) => { let id = _p ? (_p - 1) : _id; return episodes[id].cid; }, epid: (_p) => { let id = _p ? (_p - 1) : _id; return episodes[id].id; }, need_vip: () => { return false; }, vip_need_pay: () => { return false; }, is_limited: () => { return false; } }; } else { // error return { type: '?', total: () => { return 0; }, title: (_p) => { return ''; }, filename: (_p) => { return ''; }, aid: (_p) => { return ''; }, p: () => { return 1; }, cid: (_p) => { return ''; }, epid: (_p) => { return ''; }, need_vip: () => { return false; }, vip_need_pay: () => { return false; }, is_limited: () => { 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 }; } function get_quality_support() { let list, quality_list = []; if (!!$('ul.squirtle-select-list')[0]) { list = $('li.squirtle-select-item'); } else if (!!$('ul.bui-select-list')[0]) { list = $('li.bui-select-item'); } if (list && list.length) { list.each(function () { const q = `${$(this).attr('data-value')}`; if (q === '0') { return false; } quality_list.push(q); }); return quality_list; } return ['80', '64', '32', '16']; } })(); // check let Check; (function () { Check = { aid: '', cid: '', q: '', epid: '', refresh }; 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(); }); const bili_video_tag = utils.Player.tag(); !!$(bili_video_tag)[0] && ($(bili_video_tag)[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(''); $('body').append(''); // 暂且延迟处理... setTimeout(function () { let my_toolbar; if (!!$('#arc_toolbar_report')[0]) { my_toolbar = '' + '
' + '脚本设置' + '请求地址' + '' + '' + '批量下载' + '
'; $('#arc_toolbar_report').after(my_toolbar); } else if (!!$('#toolbar_module')[0]) { my_toolbar = '' + '
' + '' + '' + '' + '' + '' + '
'; $('#toolbar_module').after(my_toolbar); } else if (!!$('div.video-toolbar')[0]) { my_toolbar = '' + '
' + '脚本设置' + '请求地址' + '' + '' + '批量下载' + '
'; $('div.video-toolbar').after(my_toolbar); } UserStatus.lazy_init(); Auth.check_login_status(); Check.refresh(); }, 3000); $('body').on('click', '#setting_btn', function () { UserStatus.lazy_init(true); // init // set form by config for (const key in config) { $(`#${key}`).val(config[key]); } $('#my_config').show(); utils.Scroll.hide(); }); $('body').on('click', '#video_download_all', function () { UserStatus.lazy_init(true); // init 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等软件安装其浏览器插件后,鼠标右键点击链接下载~

' + `>视频地址<

` + (config.format === 'dash' ? `>音频地址<` : ''); 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 = '点击文本框即可复制下载命令!

' + `视频:


` + (config.format === 'dash' ? `音频:


` + `全部:
` : ''); !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').click(); } else if (type === 'aria') { $('#video_download').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 [type, aid, p, cid, epid] = [ video_base.type, video_base.aid(), video_base.p(), video_base.cid(), video_base.epid() ]; const q = 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() || video_base.is_limited() || config.replace_force === '1') { !$('#my_dplayer')[0] && utils.Player.replace(url, url_2); } if (config.auto_download === '1') { $('#video_download').click(); } } return; } $('#video_url').attr('href', '#'); $('#video_url_2').attr('href', '#'); api_url_temp = api_url; utils.Message.info('开始请求'); $.ajax(api_url, { dataType: 'json', success: (res) => { if (res && !res.code) { utils.Message.success('请求成功' + (res.times ? `
今日剩余请求次数${res.times}` : '')); const url = config.format === 'dash' ? res.video.replace('http://', 'https://') : res.url.replace('http://', 'https://'); const url_2 = config.format === 'dash' ? res.audio.replace('http://', '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() || video_base.is_limited() || config.replace_force === '1') { utils.Player.replace(url, url_2); } if (config.auto_download === '1') { $('#video_download').click(); } } else { utils.Message.warning('请求失败:' + res.message); } }, error: (e) => { utils.Message.danger('请求异常'); console.log('error', e); } }); }); })(); })();