// ==UserScript==
// @name Bilibili 旧播放页
// @namespace MotooriKashin
// @version 6.0.0
// @description 恢复Bilibili旧版页面,为了那些念旧的人。
// @author MotooriKashin,wly5556
// @homepage https://github.com/MotooriKashin/Bilibili-Old
// @supportURL https://github.com/MotooriKashin/Bilibili-Old/issues
// @icon https://static.hdslb.com/images/favicon.ico
// @match *://*.bilibili.com/*
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant GM_getResourceURL
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM.cookie
// @run-at document-start
// @license MIT
// @downloadURL none
// ==/UserScript==
GM.xmlHttpRequest = GM_xmlhttpRequest;
GM.getValue = GM_getValue;
GM.setValue = GM_setValue;
GM.deleteValue = GM_deleteValue;
GM.listValues = GM_listValues;
/**
* 脚本设置数据,关联设置项的key:value
*/
const CONFIG = {};
const config = new Proxy(CONFIG, {
set: (_target, p, value) => {
CONFIG[p] = value;
GM.setValue("config", CONFIG);
return true;
},
get: (_target, p) => CONFIG[p]
});
Object.entries(GM.getValue("config", {})).forEach(k => Reflect.set(config, k[0], k[1]));
const SETTING = [];
function modifyConfig(obj) {
Reflect.has(obj, "value") && !Reflect.has(config, Reflect.get(obj, "key")) && Reflect.set(config, Reflect.get(obj, "key"), Reflect.get(obj, "value"));
Reflect.get(obj, "type") == "sort" && Reflect.has(obj, "list") && Reflect.get(obj, "list").forEach(d => modifyConfig(d));
}
function registerSetting(obj) {
SETTING.push(obj);
modifyConfig(obj);
}
const MENU = {};
function registerMenu(obj) {
Reflect.set(MENU, Reflect.get(obj, "key"), obj);
}
function changeSettingMode(mode) {
const keys = Object.keys(mode);
SETTING.forEach(d => {
Reflect.has(d, "key") && keys.includes(Reflect.get(d, "key")) && Reflect.set(d, "hidden", Reflect.get(mode, Reflect.get(d, "key")));
});
}
class Xhr {
/**
* `XMLHttpRequest`的`Promise`封装
* @param details 以对象形式传递的参数,注意`onload`回调会覆盖Promise结果
* @returns `Promise`托管的请求结果或者报错信息,`async = false` 时除外,直接返回结果
*/
static xhr(details) {
details.method == "POST" && (details.headers = details.headers || {}, !details.headers["Content-Type"] && Reflect.set(details.headers, "Content-Type", "application/x-www-form-urlencoded"));
if (details.hasOwnProperty("async") && Boolean(details.async) === false) {
let xhr = new XMLHttpRequest();
xhr.open(details.method || 'GET', details.url, false);
details.responseType && (xhr.responseType = details.responseType);
details.credentials && (xhr.withCredentials = true);
details.headers && (Object.entries(details.headers).forEach(d => xhr.setRequestHeader(d[0], d[1])));
details.timeout && (xhr.timeout = details.timeout);
xhr.send(details.data);
return xhr.response;
}
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(details.method || 'GET', details.url);
details.responseType && (xhr.responseType = details.responseType);
details.headers && (Object.entries(details.headers).forEach(d => xhr.setRequestHeader(d[0], d[1])));
details.credentials && (xhr.withCredentials = true);
details.timeout && (xhr.timeout = details.timeout);
xhr.onabort = details.onabort || ((ev) => reject(ev));
xhr.onerror = details.onerror || ((ev) => reject(ev));
details.onloadstart && (xhr.onloadstart = details.onloadstart);
details.onprogress && (xhr.onprogress = details.onprogress);
details.onreadystatechange && (xhr.onreadystatechange = details.onreadystatechange);
xhr.ontimeout = details.ontimeout || ((ev) => reject(ev));
xhr.onload = details.onload || (() => resolve(xhr.response));
xhr.send(details.data);
});
}
/**
* `GM_xmlhttpRequest`的`Promise`封装,用于跨域`XMLHttpRequest`请求
* @param details 以对象形式传递的参数,注意`onload`回调会覆盖Promise结果
* @returns `Promise`托管的请求结果或者报错信息
*/
static GM(details) {
return new Promise((resolve, reject) => {
details.method = details.method || 'GET';
details.onload = details.onload || ((xhr) => resolve(xhr.response));
details.onerror = details.onerror || ((xhr) => reject(xhr.response));
GM.xmlHttpRequest(details);
});
}
}
const xhr = (details) => Xhr.xhr(details);
xhr.GM = (details) => Xhr.GM(details);
class Format {
/**
* 格式化时间
* @param time 时间戳
* @param type 是否包含年月日
* @returns 时:分:秒 | 年-月-日 时:分:秒
*/
static timeFormat(time = new Date().getTime(), type) {
let date = new Date(time), Y = date.getFullYear() + '-', M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-', D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ', h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':', m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':', s = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds());
return type ? Y + M + D + h + m + s : h + m + s;
}
/**
* 格式化字节
* @param size 字节/B
* @returns n B | K | M | G
*/
static sizeFormat(size = 0) {
let unit = ["B", "K", "M", "G"], i = unit.length - 1, dex = 1024 ** i, vor = 1000 ** i;
while (dex > 1) {
if (size >= vor) {
size = Number((size / dex).toFixed(2));
break;
}
dex = dex / 1024;
vor = vor / 1000;
i--;
}
return size ? size + unit[i] : "N/A";
}
/**
* 格式化进位
* @param num 实数
* @returns n 万 | 亿
*/
static unitFormat(num = 0) {
num = 1 * num || 0;
let unit = ["", "万", "亿"], i = unit.length - 1, dex = 10000 ** i;
while (dex > 1) {
if (num >= dex) {
num = Number((num / dex).toFixed(1));
break;
}
dex = dex / 10000;
i--;
}
return num + unit[i];
}
/**
* 冒泡排序
* @param arr 待排序数组
* @returns 排序结果
*/
static bubbleSort(arr) {
let temp;
for (let i = 0; i < arr.length - 1; i++) {
let bool = true;
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
bool = false;
}
}
if (bool)
break;
}
return arr;
}
/**
* 随机截取指定大小子数组
* @param arr 母数组
* @param num 子数组大小
* @returns 子数组
*/
static randomArray(arr, num) {
let out = [];
num = num || 1;
num = num < arr.length ? num : arr.length;
while (out.length < num) {
var temp = (Math.random() * arr.length) >> 0;
out.push(arr.splice(temp, 1)[0]);
}
return out;
}
/**
* search参数对象拼合回URL
* @param url URL主体,可含search参数
* @param obj search参数对象
* @returns 拼合的URL
*/
static objUrl(url, obj) {
let data = this.urlObj(url);
obj = typeof obj === "object" ? obj : {};
data = Object.assign(data, obj);
let arr = [], i = 0;
for (let key in data) {
if (data[key] !== undefined && data[key] !== null) {
arr[i] = key + "=" + data[key];
i++;
}
}
if (url)
url = url + "?" + arr.join("&");
else
url = arr.join("&");
if (url.charAt(url.length - 1) == "?")
url = url.split("?")[0];
return url;
}
/**
* 提取URL search参数对象
* @param url 原URL
* @returns search参数对象
*/
static urlObj(url = "") {
let arr = url.split('?')[1] ? url.split('?')[1].split('&') : [];
return arr.reduce((o, d) => {
if (d.includes("#"))
d = d.split("#")[0];
if (d)
o[d.split('=')[0]] = d.split('=')[1] || "";
return o;
}, {});
}
}
class Debug {
static log(...data) { console.log(`%c[${Format.timeFormat()}]`, "color: blue;", ...data); }
static info(...data) { console.info(`%c[${Format.timeFormat()}]`, "color: green;", ...data); }
static debug(...data) { console.debug(`[${Format.timeFormat()}]`, ...data); }
static warn(...data) { console.warn(`[${Format.timeFormat()}]`, ...data); }
static error(...data) { console.error(`[${Format.timeFormat()}]`, ...data); }
}
const debug = (...data) => Debug.log(...data);
debug.log = (...data) => Debug.log(...data);
debug.info = (...data) => Debug.info(...data);
debug.debug = (...data) => Debug.debug(...data);
debug.warn = (...data) => Debug.warn(...data);
debug.error = (...data) => Debug.error(...data);
class Toast {
static init() {
this.container = document.createElement("div");
this.style = document.createElement("link");
this.container.setAttribute("id", "toast-container");
this.container.setAttribute("class", "toast-top-right");
this.style.setAttribute("rel", "stylesheet");
this.style.setAttribute("id", "toastr-style");
this.style.setAttribute("href", "https://cdn.bootcdn.net/ajax/libs/toastr.js/latest/toastr.min.css");
}
static show(type, ...msg) {
if (!config.toastcheck)
return;
if (!document.body) {
if (this.check)
return;
return setTimeout(() => { this.check = true; this.show(type, ...msg); });
}
document.querySelector("#toastr-style") || document.head.appendChild(this.style);
document.querySelector("#toast-container") || document.body.appendChild(this.container);
this.box = document.querySelector("#toast-container") || this.container;
let item = document.createElement("div");
item.setAttribute("class", "toast toast-" + type);
item.setAttribute("aria-live", "assertive");
item.setAttribute("style", "visibility: hidden;position: absolute");
setTimeout(() => {
if (this.count > 0)
this.count--;
item = this.box.insertBefore(item, this.box.firstChild);
item.appendChild(this.msg(...msg));
this.come(item);
setTimeout(() => this.quit(item), (Number(config.toasttimeout) || 4) * 1000);
}, this.count * (Number(config.toaststep) || 250));
this.count++;
}
static come(item, i = 0) {
let height = item.clientHeight;
item.setAttribute("style", "display: none;");
let timer = setInterval(() => {
i++;
item.setAttribute("style", "padding-top: " + i / 4 + "px;padding-bottom: " + i / 4 + "px;height: " + i / 60 * height + "px;");
if (i === this.sence) {
clearInterval(timer);
item.removeAttribute("style");
}
});
}
static quit(item, i = this.sence) {
let height = item.clientHeight;
let timer = setInterval(() => {
i--;
item.setAttribute("style", "padding-top: " + i / 4 + "px;padding-bottom: " + i / 4 + "px;height: " + i / 60 * height + "px;");
if (i === 0) {
clearInterval(timer);
item.remove();
if (!this.box.firstChild)
this.box.remove();
}
});
}
static msg(...msg) {
let div = document.createElement("div");
div.setAttribute("class", "toast-message");
div.innerHTML = msg.reduce((s, d, i) => {
s = s + (i ? "
" : "") + String(d);
return s;
}, "");
return div;
}
}
/**
* 未呈现通知计数
*/
Toast.count = 0;
/**
* 动画呈现帧数
*/
Toast.sence = 60;
Toast.init();
const toast = (...msg) => { debug.debug(...msg); Toast.show("info", ...msg); };
toast.info = (...msg) => { debug.debug(...msg); Toast.show("info", ...msg); };
toast.success = (...msg) => { debug.log(...msg); Toast.show("success", ...msg); };
toast.warning = (...msg) => { debug.warn(...msg); Toast.show("warning", ...msg); };
toast.error = (...msg) => { debug.error(...msg); Toast.show("error", ...msg); };
registerSetting({
type: "sort",
key: "toast",
label: "浮动通知",
sub: 'toastr',
svg: '',
sort: "common",
list: [{
type: "switch",
key: "toastcheck",
label: "通知开关",
sort: "common",
value: true,
}, {
type: "input",
key: "toasttimeout",
label: "通知时长:/s",
sort: "common",
value: "4",
input: { type: "number", min: 1, max: 30 },
pattern: /^\d+$/
}, {
type: "input",
key: "toaststep",
label: "通知延时:/ms",
sort: "common",
value: "250",
input: { type: "number", min: 100, max: 1000 },
pattern: /^\d+$/
}]
});
// @ts-ignore 忽略unsafeWindow错误
const root = unsafeWindow;
class API {
constructor() {
this.GM = GM;
this.config = config;
this.Name = API.Name;
this.Virsion = API.Virsion;
this.Handler = [GM.info.scriptHandler, GM.info.version].join(" ");
this.registerSetting = registerSetting;
this.registerMenu = registerMenu;
this.changeSettingMode = changeSettingMode;
this.runWhile = API.runWhile;
this.importModule = API.importModule;
this.timeFormat = (time, type) => Format.timeFormat(time, type);
this.sizeFormat = (size) => Format.sizeFormat(size);
this.unitFormat = (num) => Format.unitFormat(num);
this.bubbleSort = (arr) => Format.bubbleSort(arr);
this.randomArray = (arr, num) => Format.randomArray(arr, num);
this.objUrl = (url, obj) => Format.objUrl(url, obj);
this.urlObj = (url) => Format.urlObj(url);
this.trace = (e, label = "", toastr = false) => { toastr ? toast.error(label, ...(Array.isArray(e) ? e : [e])) : Debug.error(label, ...(Array.isArray(e) ? e : [e])); };
API.API = new Proxy(this, {
get: (target, p) => {
return Reflect.get(this, p) || Reflect.get(root, p) || (Reflect.has(API.apply, p) ? (this.importModule(Reflect.get(API.apply, p), {}, true),
Reflect.get(this, p)) : undefined);
},
set: (_target, p, value) => {
Reflect.set(this, p, value);
return true;
}
});
Reflect.has(API.modules, "rewrite.js") ? API.init() : this.runWhile(() => document.body, () => this.alertMessage(`即将下载脚本运行所需基本数据,请允许脚本访问网络权限!推荐选择“总是允许全部域名”`).then(d => { d && API.firstInit(); }));
}
bofqiMessage(msg, time = 3, callback, replace = true) {
let node = document.querySelector(".bilibili-player-video-toast-bottom");
if (!node) {
if (msg) {
if (Array.isArray(msg))
return Debug.log(...msg);
return Debug.log(msg);
}
return;
}
if (!msg)
node.childNodes.forEach(d => d.remove());
const table = document.querySelector(".bilibili-player-video-toast-item.bilibili-player-video-toast-pay") || document.createElement("div");
table.setAttribute("class", "bilibili-player-video-toast-item bilibili-player-video-toast-pay");
const ele = document.createElement("div");
ele.setAttribute("class", "bilibili-player-video-toast-item-text");
table.appendChild(ele);
msg = Array.isArray(msg) ? msg : [msg];
if (!msg[0])
return;
replace && node.childNodes.forEach(d => d.remove());
ele.innerHTML = msg.reduce((s, d, i) => {
if (d) {
switch (i) {
case 0:
s += `${d}`;
break;
case 1:
s += `${d}`;
break;
case 2:
s += `${d}`;
break;
}
}
return s;
}, '');
node.appendChild(table);
callback && (ele.style.cursor = "pointer") && (ele.onclick = () => callback());
(time !== 0) && root.setTimeout(() => {
ele.remove();
!table.children[0] && table.remove();
}, time * 1000);
}
addElement(tag, attribute, parrent, innerHTML, top, replaced) {
let element = document.createElement(tag);
attribute && (Object.entries(attribute).forEach(d => element.setAttribute(d[0], d[1])));
parrent = parrent || document.body;
innerHTML && (element.innerHTML = innerHTML);
replaced ? replaced.replaceWith(element) : top ? parrent.insertBefore(element, parrent.firstChild) : parrent.appendChild(element);
return element;
}
async addCss(txt, id, parrent) {
if (!parrent && !document.head) {
await new Promise(r => this.runWhile(() => document.body, r));
}
parrent = parrent || document.head;
const style = document.createElement("style");
style.setAttribute("type", "text/css");
id && !parrent.querySelector(`#${id}`) && style.setAttribute("id", id);
style.appendChild(document.createTextNode(txt));
parrent.appendChild(style);
}
static runWhile(check, callback, delay = 100, stop = 180) {
let timer = root.setInterval(() => {
if (check()) {
root.clearInterval(timer);
callback();
}
}, delay);
stop && root.setTimeout(() => root.clearInterval(timer), stop * 1000);
}
async alertMessage(text, title = API.Name) {
return new Promise((r) => {
const root = this.addElement("div");
const div = root.attachShadow({ mode: "closed" });
const table = this.addElement("div", { class: "table" }, div, `