// ==UserScript==
// @name bilibili视频下载
// @namespace https://github.com/injahow
// @version 1.9.0
// @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://cdnjs.cloudflare.com/ajax/libs/flv.js/1.6.2/flv.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/dplayer/1.26.0/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
const UserStatus = {
is_login: false,
vip_status: 0,
mid: '',
uname: '',
has_init: false,
need_replace: function () {
return !this.is_login || (this.is_login && !this.vip_status && VideoStatus.base().need_vip());
},
lazy_init: function (last_init = false) {
if (!this.has_init) {
if (window.__BILI_USER_INFO__) {
this.is_login = window.__BILI_USER_INFO__.isLogin;
this.vip_status = window.__BILI_USER_INFO__.vipStatus;
this.mid = window.__BILI_USER_INFO__.mid || '';
this.uname = window.__BILI_USER_INFO__.uname || '';
} else if (window.__BiliUser__) {
this.is_login = window.__BiliUser__.isLogin;
if (window.__BiliUser__.cache) {
this.vip_status = window.__BiliUser__.cache.data.vipStatus;
this.mid = window.__BiliUser__.cache.data.mid || '';
this.uname = window.__BiliUser__.cache.data.uname || '';
} else {
this.vip_status = 0;
this.mid = '';
this.uname = '';
}
}
this.has_init = last_init;
}
}
};
// 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(`https://api.bilibili.com/x/space/myinfo?access_key=${access_key}`, {
type: 'GET',
dataType: 'json',
success: res => {
if (res.code) {
utils.MessageBox.alert('授权已过期,准备重新授权', () => {
localStorage.setItem('bp_auth_id', '');
localStorage.setItem('bp_auth_sec', '');
localStorage.setItem('bp_access_key', '');
localStorage.setItem('bp_auth_time', '0');
window.bp_show_login();
});
} else {
localStorage.setItem('bp_auth_time', Date.now());
$.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.Message.warning('授权检查失败:' + res.message);
}
},
error: () => {
utils.Message.danger('授权检查出现网络异常');
}
});
}
},
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/',
request_type: 'auto',
format: 'flv',
host_key: '0',
replace_force: '0',
auth: '0',
download_type: 'web',
rpc_domain: 'http://localhost',
rpc_port: '16800',
rpc_token: '',
rpc_dir: 'D:/',
auto_download: '0'
};
const hostMap = {
ks3: 'upos-sz-mirrorks3.bilivideo.com',
ks3b: 'upos-sz-mirrorks3b.bilivideo.com',
ks3c: 'upos-sz-mirrorks3c.bilivideo.com',
ks32: 'upos-sz-mirrorks32.bilivideo.com',
kodo: 'upos-sz-mirrorkodo.bilivideo.com',
kodob: 'upos-sz-mirrorkodob.bilivideo.com',
cos: 'upos-sz-mirrorcos.bilivideo.com',
cosb: 'upos-sz-mirrorcosb.bilivideo.com',
bos: 'upos-sz-mirrorbos.bilivideo.com',
wcs: 'upos-sz-mirrorwcs.bilivideo.com',
wcsb: 'upos-sz-mirrorwcsb.bilivideo.com',
/* 不限CROS, 限制UA */
hw: 'upos-sz-mirrorhw.bilivideo.com',
hwb: 'upos-sz-mirrorhwb.bilivideo.com',
upbda2: 'upos-sz-upcdnbda2.bilivideo.com',
upws: 'upos-sz-upcdnws.bilivideo.com',
uptx: 'upos-sz-upcdntx.bilivideo.com',
uphw: 'upos-sz-upcdnhw.bilivideo.com',
js: 'upos-tf-all-js.bilivideo.com',
hk: 'cn-hk-eq-bcache-01.bilivideo.com',
akamai: 'upos-hz-mirrorakam.akamaized.net'
};
// 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;
}
}
if (config.host_key !== old_config.host_key) {
Check.refresh();
$('#video_url').attr('href', '#');
$('#video_url_2').attr('href', '#');
}
// 判断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不是localhost本地接口,即将跳转到AriaNG网页控制台页面;' +
'请查看控制台RPC接口参数是否正确,第一次加载会比较慢请耐心等待;' +
'配置好后即可使用脚本进行远程下载
使用期间不用关闭控制台页面!', () => {
utils.open_ariang(true);
});
}
}
};
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 host_keys = Object.keys(hostMap);
let host_key_option = '';
for (const key of host_keys) {
host_key_option += ``;
}
const config_css = '' +
'';
const config_html = '' +
`