Bilibili 轴Man小助手

将评论区的轴转换至Bilibili的笔记,实现手机可点的特性

目前为 2022-03-14 提交的版本。查看 最新版本

// ==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);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址