// ==UserScript== // @name Bilibili 轴Man小助手 // @namespace http://tampermonkey.net/ // @version 1.0.2 // @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 // @downloadURL none // ==/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(''); 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,'\n','silent'); }; const markTime = function (quill, cid, index, seconds, cidCount) { let currentPosition = quill.getSelection(true); quill.insertEmbed(currentPosition, '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 + 2; quill.setSelection(currentPosition); }; const insertText = async function (quill, text, guide) { let currentPosition = quill.getSelection(true); if (useIndent) { // 插入引导线 let guideStr = (guide)? " └─ " : '\n    '; quill.insertText(currentPosition, 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, textPart, {'color': '#ee230d', 'bold': true, 'link': null, 'size': null, 'background': null}, 'silent'); } else { quill.insertText(currentPosition, 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, 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'); } } }; const insertTitle = function(quill, title) { let currentPosition = quill.getSelection(true); let margin = 17 - title.length; for (let i = 0; i < margin; i+= 2) { title = ' ' + title + ' '; } quill.insertText(currentPosition, title, {'color': null, 'link': null,'bold': true, 'size': '18px', 'background': '#fff359' }, 'silent'); 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插入,否则无法正常保存 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 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; } 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); })();