您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
将评论区的轴转换至Bilibili的笔记,实现手机可点的特性
当前为
// ==UserScript== // @name Bilibili 轴Man小助手 // @namespace http://tampermonkey.net/ // @version 1.4.0 // @description 将评论区的轴转换至Bilibili的笔记,实现手机可点的特性 // @author as042971 // @icon https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico // @match *://www.bilibili.com/video/av* // @match *://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, labelTitle) { 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': labelTitle, '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, mode, labelTitle) { let quill = document.querySelector('.ql-container').__quill; if (mode != 1 && title != '') { 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 = -1; // 通过空格分隔 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 (mode == 1 && nonTimeStr.startsWith('##')) { insertTitle(quill, nonTimeStr.substr(2, nonTimeStr.length - 2)); } else if (time != -1) { markTime(quill, cid, index, time, cidCount, labelTitle); await insertText(quill, nonTimeStr, true); time = -1; } else { await insertText(quill, nonTimeStr, false); } if (useNewLine) { insertNewLine(quill); } nonTimeStr = ''; } // 标记这个时间戳 time = parseTime(part); } else { if (nonTimeStr != '') { nonTimeStr += ' '; } nonTimeStr += part; } } } if (nonTimeStr != '') { if (mode == 1 && nonTimeStr.startsWith('##')) { insertTitle(quill, nonTimeStr.substr(2, nonTimeStr.length - 2)); } else if (time != -1) { markTime(quill, cid, index, time, cidCount, labelTitle); await insertText(quill, nonTimeStr, true); time = -1; } 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 subContainer2 = document.createElement('div'); subContainer2.setAttribute('style', 'width: 100%; display:flex; flex-flow:row;'); let customTitleInputContainer = document.createElement('div'); customTitleInputContainer.setAttribute('style', 'flex:1; display:flex; flex-flow:row;'); let customTitleInput = document.createElement('input'); customTitleInput.setAttribute('disabled', 'disabled'); customTitleInput.setAttribute('placeholder', '自定义分p标题'); customTitleInput.setAttribute('style', 'flex:1; display:flex; flex-flow:row;'); customTitleInputContainer.appendChild(customTitleInput); let customTimeLabelInput = document.createElement('input'); customTimeLabelInput.setAttribute('placeholder', '自定义标签'); customTimeLabelInput.setAttribute('style', 'width:75px; display:flex; flex-flow:row'); customTitleInputContainer.appendChild(customTimeLabelInput); let modeSelect = document.createElement('select'); modeSelect.setAttribute('style', 'width:180px; display:flex; flex-flow:row'); let modeSelectOption1 = document.createElement('option'); modeSelectOption1.innerHTML = '使用默认的分P标题'; modeSelect.appendChild(modeSelectOption1); let modeSelectOption2 = document.createElement('option'); modeSelectOption2.innerHTML = '使用轴内标记的章节标题'; modeSelect.appendChild(modeSelectOption2); let modeSelectOption3 = document.createElement('option'); modeSelectOption3.innerHTML = '使用自定义标题'; modeSelect.appendChild(modeSelectOption3); modeSelect.onchange = function() { if (modeSelect.selectedIndex == 2) { customTitleInput.removeAttribute('disabled'); } else { customTitleInput.setAttribute('disabled', 'disabled'); } } subContainer2.appendChild(modeSelect); subContainer2.appendChild(customTitleInputContainer); let subContainer1x = document.createElement('div'); subContainer1x.setAttribute('style', 'width: 100%; display:none; flex-flow:row;'); let subContainer1xLeft =document.createElement('div'); subContainer1xLeft.setAttribute('style', 'width:180px; display:flex; flex-flow:row;'); let cidIdxInput = document.createElement('input'); cidIdxInput.setAttribute('style', 'width:50%; display:flex; flex-flow:row;'); cidIdxInput.setAttribute('placeholder', '分P序号'); subContainer1xLeft.appendChild(cidIdxInput); let cidCntInput = document.createElement('input'); cidCntInput.setAttribute('style', 'width:50%; display:flex; flex-flow:row;'); cidCntInput.setAttribute('placeholder', '分P数量'); subContainer1xLeft.appendChild(cidCntInput); subContainer1x.appendChild(subContainer1xLeft); let cidInput = document.createElement('input'); cidInput.setAttribute('style', 'flex:1; display:flex; flex-flow:row;'); cidInput.setAttribute('placeholder', '分P CID'); subContainer1x.appendChild(cidInput); let subContainer1 = document.createElement('div'); subContainer1.setAttribute('style', 'width: 100%; display:flex; flex-flow:row;'); let pselect = document.createElement('select'); pselect.setAttribute('style', 'width:180px; display:flex; flex-flow:row;'); 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); } let advancedPselectOption = document.createElement('option'); advancedPselectOption.innerHTML = '(高级)'; pselect.appendChild(advancedPselectOption); pselect.onchange = function() { if (pselect.selectedIndex == pages.length + 1) { subContainer1x.style.display = 'flex'; } else { subContainer1x.style.display = 'none'; } } subContainer1.appendChild(pselect); let rawTimeline = document.createElement('input'); rawTimeline.setAttribute('style', 'flex:1; display:flex; flex-flow:row;'); 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 if (pselect.selectedIndex == pages.length + 1) { cid = cidInput.value; index = parseInt(cidIdxInput.value); cidCount = parseInt(cidCntInput.value); } else { index = pselect.selectedIndex; let item = pages[index-1]; cid = item.cid; title = item.part; } if (customTitleInput.value && modeSelect.selectedIndex == 2) { title = customTitleInput.value; } let labelTitle = ''; if (customTimeLabelInput.value) { labelTitle = customTimeLabelInput.value; } else { labelTitle = 'P'+index; } await handleTimeline(data, cid, index, cidCount, title, modeSelect.selectedIndex, labelTitle); rawTimeline.removeAttribute('disabled'); rawTimeline.setAttribute('placeholder', '将轴粘贴至这里...'); }; subContainer1.appendChild(rawTimeline); container.appendChild(subContainer1); container.appendChild(subContainer1x); container.appendChild(subContainer2); 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); })();