// ==UserScript==
// @name Bilibili 轴Man小助手
// @namespace http://tampermonkey.net/
// @version 1.2.0
// @description 将评论区的轴转换至Bilibili的笔记,实现手机可点的特性
// @author as042971
// @icon https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico
// @include *://www.bilibili.com/video/av*
// @include *://www.bilibili.com/video/BV*
// @license MIT
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// ==/UserScript==
(function() {
'use strict';
// 设置 useIndent = true 会在文本前增加缩进和引导线
const useIndent = true;
// 设置 useNewLine = true 会在文本后增加空行
const useNewLine = false;
const getTitle = function(url){
return new Promise(resolve => {
GM_xmlhttpRequest({
url: url,
method : "GET",
onload : function(xhr){
let myReg = new RegExp('<title.*title>');
let title = ''
try
{
let nt = xhr.responseText.match(myReg)[0];
title = nt.split("<")[1];
title = title.split(">")[1];
title = title.split("_哔哩哔哩_bilibili")[0]
title = '▶️'+title;
} catch( error){
title = "(打开二创)"
}
resolve(title);
},
onerror : function(err) {
resolve("(打开二创)");
}
});
});
};
const insertNewLine = function (quill) {
let currentPosition = quill.getSelection(true);
quill.insertText(currentPosition.index,'\n',
{'color': null, 'link': null,'bold': false ,'size': null, 'background': null},'silent');
};
const markTime = function (quill, cid, index, seconds, cidCount) {
let currentPosition = quill.getSelection(true);
quill.insertEmbed(currentPosition.index, 'tag', {
'cid': cid,
'oid_type': 1,
'status': 0,
'index': index,
'seconds': seconds,
'cidCount': cidCount,
'key': (new Date).getTime(),
'title': 'P'+index,
'epid': 0
}, 'silent');
currentPosition.index = currentPosition.index + 1;
quill.setSelection(currentPosition);
insertNewLine(quill);
};
const insertText = async function (quill, text, guide) {
let currentPosition = quill.getSelection(true);
if (useIndent) {
// 插入引导线
let guideStr = (guide)? " └─ " : '\n ';
quill.insertText(currentPosition.index, guideStr,
{'color': '#cccccc', 'link': null,'bold': false ,'size': null, 'background': null}, 'silent');
}
let mark = false;
if (text.charAt(text.length-1) == '*') {
text = text.substr(0, text.length - 1);
mark = true;
}
// 使用正则表达式分割链接
// 增加前后缀以避免BV在头尾出现
let exText = '*' + text + '*';
let textParts = exText.split(/BV[A-Za-z0-9]{10}/);
let bvParts = exText.match(/BV[A-Za-z0-9]{10}/g);
for (let i = 0; i < textParts.length; i++) {
// 增加文本部分
let textPart = textParts[i];
if (i == 0) {
// 删去先导符号
textPart = textPart.substr(1, textPart.length - 1);
}
if (i == textParts.length - 1) {
// 删去结尾符号
textPart = textPart.substr(0, textPart.length - 1);
}
if (textPart) {
currentPosition = quill.getSelection(true);
if (mark) {
quill.insertText(currentPosition.index, textPart,
{'color': '#ee230d', 'bold': true, 'link': null, 'size': null, 'background': null}, 'silent');
} else {
quill.insertText(currentPosition.index, textPart,
{'color': null, 'link': null, 'bold': false, 'size': null, 'background': null}, 'silent');
}
}
// 增加链接部分
if (i != textParts.length - 1) {
currentPosition = quill.getSelection(true);
let bvPart = bvParts[i];
let uri = 'https://www.bilibili.com/video/'+ bvPart;
let title = await getTitle(uri);
quill.insertText(currentPosition.index, title,
{'color': '#0b84ed', 'link': uri,'bold': false ,'size': null, 'background': null}, 'silent');
//currentPosition = quill.getSelection(true);
//quill.insertText(currentPosition, "(建议在评论区打开)", {'color': '#cccccc', 'link': null,'bold': false }, 'silent');
}
}
insertNewLine(quill);
};
const textWidth = function(text){
var span = document.createElement("span");
span.setAttribute('class', 'ql-size-18px');
var result = {};
result.width = span.offsetWidth;
span.style.visibility = "hidden";
span.style.display = "inline-block";
document.body.appendChild(span);
if(typeof span.textContent != "undefined"){
span.textContent = text;
}else{
span.innerText = text;
}
return parseFloat(window.getComputedStyle(span).width) - result.width;
}
const insertTitle = function(quill, title) {
insertNewLine(quill)
let currentPosition = quill.getSelection(true);
quill.formatLine(currentPosition.index, currentPosition.length , 'align', '');
// 总计240px
let hCnt = 0;
let wCnt = 0;
let titleWidth = textWidth(title);
let margin = (180 - titleWidth) / 2;
let hSpaceWidth = textWidth(' ');
let wSpaceWidth = textWidth(' ');
if (margin > 0) {
wCnt = parseInt(margin / wSpaceWidth);
margin -= wCnt * wSpaceWidth;
hCnt = parseInt(margin / hSpaceWidth);
}
for (let i = 0; i < wCnt; i++) {
title = ' ' + title + ' ';
}
for (let i = 0; i < wCnt; i++) {
title = ' ' + title + ' ';
}
quill.insertText(currentPosition.index, title,
{'color': null, 'link': null,'bold': true, 'size': '18px', 'background': '#fff359' }, 'silent');
insertNewLine(quill);
quill.formatLine(currentPosition.index, currentPosition.length , 'align', 'center');
}
const parseTime = function (timeStr) {
const timePart = timeStr.split(":");
if (timePart.length == 3) {
return parseInt(timePart[0]) * 3600 + parseInt(timePart[1]) * 60 + parseInt(timePart[2]);
} else {
return parseInt(timePart[0]) * 60 + parseInt(timePart[1]);
}
};
const handleTimeline = async function (inputStr, cid, index, cidCount, title) {
let quill = document.querySelector('.ql-container').__quill;
insertTitle(quill, title)
// h:mm:ss 型时间
const timeRegex = /^(\d{1})\:([0-5]{1}\d{1})\:([0-5]{1}\d{1})$/;
// mm:ss 型时间
const timeRegex2 = /^([0-5]{1}\d{1})\:([0-5]{1}\d{1})$/;
// 通过换行分隔
const inputStrList = inputStr.split(/[\r\n]+/);
for (let i = 0; i < inputStrList.length; i++) {
let inputStrItem = inputStrList[i];
let nonTimeStr = '';
let time = 0;
// 通过空格分隔
const inputPart = inputStrItem.split(' ');
for (let j = 0; j < inputPart.length; j++) {
let currentPosition = quill.getSelection(true);
let part = inputPart[j];
if (part) {
if (timeRegex.test(part) || timeRegex2.test(part)) {
// 这是一个时间戳
// 结束上一次的非时间戳内容
if (nonTimeStr != '') {
if (time != 0) {
markTime(quill, cid, index, time, cidCount);
await insertText(quill, nonTimeStr, true);
time = 0;
} else {
await insertText(quill, nonTimeStr, false);
}
if (useNewLine) {
insertNewLine(quill);
}
nonTimeStr = '';
}
// 标记这个时间戳
time = parseTime(part);
} else {
if (nonTimeStr != '') {
nonTimeStr += ' ';
}
nonTimeStr += part;
}
}
}
if (nonTimeStr != '') {
if (time != 0) {
markTime(quill, cid, index, time, cidCount);
await insertText(quill, nonTimeStr, true);
time = 0;
} else {
await insertText(quill, nonTimeStr, false);
}
if (useNewLine) {
insertNewLine(quill);
}
}
}
// 必须进行一次user插入,否则无法正常保存
insertNewLine(quill);
let currentPosition = quill.getSelection(true);
quill.insertText(currentPosition, '', 'user');
};
const inject = function(node) {
let pages = unsafeWindow.__INITIAL_STATE__.videoData.pages;
let cidCount = pages.length;
let container = document.createElement('div');
container.setAttribute('style', 'margin:0 10px 10px');
let pselect = document.createElement('select');
pselect.setAttribute('style', 'float:left');
let defaultPselectOption = document.createElement('option');
defaultPselectOption.innerHTML = '(当前分P)';
pselect.appendChild(defaultPselectOption);
for (let index=0; index < pages.length; index++) {
let pselectOption = document.createElement('option');
pselectOption.innerHTML = pages[index].part;
pselect.appendChild(pselectOption);
}
container.appendChild(pselect);
let customTitleInput = document.createElement('input');
customTitleInput.setAttribute('placeholder', '自定义标题');
customTitleInput.setAttribute('style', 'width: 100px;float:left');
container.appendChild(customTitleInput);
let timelineContainer = document.createElement('div');
timelineContainer.setAttribute('style', 'overflow:hidden');
let rawTimeline = document.createElement('input');
rawTimeline.setAttribute('style', 'width: 100%');
rawTimeline.setAttribute('placeholder', '将轴粘贴至这里...');
rawTimeline.oninput = async function () {
let data = rawTimeline.value;
rawTimeline.value = "";
rawTimeline.setAttribute('disabled', 'disabled');
rawTimeline.setAttribute('placeholder', '处理中,请稍后...');
let cid = undefined;
let index = undefined;
let title = '';
if (pselect.selectedIndex == 0) {
cid = unsafeWindow.cid;
for (index=0; index < pages.length; index++) {
if (pages[index].cid == cid) {
title = pages[index].part;
break;
}
}
index += 1;
} else {
index = pselect.selectedIndex;
let item = pages[index-1];
cid = item.cid;
title = item.part;
}
if (customTitleInput.value) {
title = customTitleInput.value;
}
await handleTimeline(data, cid, index, cidCount, title);
rawTimeline.removeAttribute('disabled');
rawTimeline.setAttribute('placeholder', '将轴粘贴至这里...');
};
timelineContainer.appendChild(rawTimeline);
container.appendChild(timelineContainer);
node.insertBefore(container, node.childNodes[3]);
};
let app = document.getElementById('app');
let observerOptions = {
childList: true,
attributes: false,
subtree: false
};
let observer = new MutationObserver((mutation_records) => {
let note = document.querySelector('.active-note');
if (note) {
inject(note);
observer.disconnect();
}
});
observer.observe(app, observerOptions);
})();