bilibili 评论折跃小助手

复制评论区的折跃链接

// ==UserScript==
// @name         bilibili 评论折跃小助手
// @version      0.1.1
// @description  复制评论区的折跃链接
// @author       as042971
// @author       Sparanoid
// @license      AGPL
// @include      *://www.bilibili.com/video/av*
// @include      *://www.bilibili.com/video/BV*
// @icon         https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico
// @grant        none
// @run-at       document-start
// @namespace https://greasyfork.org/users/865651
// ==/UserScript==

window.addEventListener('load', () => {
    const DEBUG = false;
    const NAMESPACE = 'bilibili-comment-wrap';

    console.log(`${NAMESPACE} loaded`);
    function debug(description = '', msg = '', force = false) {
        if (DEBUG || force) {
            console.log(`${NAMESPACE}: ${description}`, msg)
        }
    }

    function attachEl(item) {
        let injectWrap = item.querySelector('.con .info');

        // .text - comment content
        // .text-con - reply content
        let content = item.querySelector('.con .text') || item.querySelector('.reply-con .text-con');
        let id = item.dataset.id;
        let avID = window.aid;

        // Simple way to attach element on replies initially loaded with comment
        // which wouldn't trigger mutation inside observeComments
        let replies = item.querySelectorAll('.con .reply-box .reply-item');
        if (replies.length > 0) {
            [...replies].map(reply => {
                attachEl(reply);
            });
        }

        if (injectWrap.querySelector('.comment-wrap')) {
            debug('already loaded for this comment');
        } else {
            // Insert wrap check button
            let wrapEl = document.createElement('span');

            wrapEl.classList.add('comment-wrap', 'btn-hover', 'btn-highlight');
            wrapEl.innerHTML = '复制折跃地址';
            wrapEl.addEventListener('click', e => {
                let link = 'https://www.bilibili.com/video/av'+avID+'/#reply'+id;
                let aux = document.createElement("input");
                aux.setAttribute("value", link);
                document.body.appendChild(aux);
                aux.select();
                document.execCommand("copy");
                document.body.removeChild(aux);
            }, false);

            injectWrap.append(wrapEl);
        }
    }

    function observeComments(wrapper) {
        // .comment-list - general list for video, zhuanlan, and dongtai
        // .reply-box - replies attached to specific comment
        let commentLists = wrapper ? wrapper.querySelectorAll('.comment-list, .reply-box') : document.querySelectorAll('.comment-list, .reply-box');

        if (commentLists) {

            [...commentLists].map(commentList => {

                // Directly attach elements for pure static server side rendered comments
                // and replies list. Used by zhuanlan posts with reply hash in URL.
                // TODO: need a better solution
                [...commentList.querySelectorAll('.list-item, .reply-item')].map(item => {
                    attachEl(item);
                });

                const observer = new MutationObserver((mutationsList, observer) => {

                    for (const mutation of mutationsList) {

                        if (mutation.type === 'childList') {

                            debug('observed mutations', [...mutation.addedNodes].length);

                            [...mutation.addedNodes].map(item => {
                                attachEl(item);

                                // Check if the comment has replies
                                // I check replies here to make sure I can disable subtree option for
                                // MutationObserver to get better performance.
                                let replies = item.querySelectorAll('.con .reply-box .reply-item');

                                if (replies.length > 0) {
                                    observeComments(item)
                                    debug(item.dataset.id + ' has rendered reply(ies)', replies.length);
                                }
                            })
                        }
                    }
                });
                observer.observe(commentList, { attributes: false, childList: true, subtree: false });
            });
        }
    }

    // .bb-comment loads directly for zhuanlan post. So load it directly
    observeComments();

    // .bb-comment loads dynamcially for dontai and videos. So observe it first
    const wrapperObserver = new MutationObserver((mutationsList, observer) => {

        for (const mutation of mutationsList) {

            if (mutation.type === 'childList') {

                [...mutation.addedNodes].map(item => {
                    debug('mutation wrapper added', item);

                    if (item.classList?.contains('bb-comment')) {
                        debug('mutation wrapper added (found target)', item);

                        observeComments(item);

                        // Stop observing
                        // TODO: when observer stops it won't work for dynamic homepage ie. https://space.bilibili.com/703007996/dynamic
                        // so disable it here. This may have some performance impact on low-end machines.
                        // wrapperObserver.disconnect();
                    }
                })
            }
        }
    });
    wrapperObserver.observe(document.body, { attributes: false, childList: true, subtree: true });

}, false);