Bilibili 啵啵表情包

在 Bilibili 表情包中增加啵啵系列

As of 2022-07-02. See the latest version.

// ==UserScript==
// @name         Bilibili 啵啵表情包
// @namespace    https://github.com/AS042971/bilibili-bobo
// @supportURL   https://github.com/AS042971/bilibili-bobo/issues
// @license      BSD-3
// @version      0.1.5
// @description  在 Bilibili 表情包中增加啵啵系列
// @author       as042971
// @match        https://*.bilibili.com/*
// @exclude      https://live.bilibili.com/*
// @icon         https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico
// @require      https://unpkg.com/[email protected]/dist/xhook.min.js
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    let emote_source = [
        // 来自 @风罗4个圈儿
        // https://t.bilibili.com/676767798070870018
        [3333111, "凑紫楠", "https://i0.hdslb.com/bfs/new_dyn/e7fb58dae8651b78666a97a964e394245858138.png"],
        [3333112, "叫我美女宝", "https://i0.hdslb.com/bfs/new_dyn/e25201330974e4227009a588b4861cdd5858138.png"],
        // https://t.bilibili.com/677505501543530521
        [3333113, "一正丨", "https://i0.hdslb.com/bfs/album/6c4402cd7de9737e666970f6299e61f5806bbf73.jpg"],
        [3333114, "大傻呗", "https://i0.hdslb.com/bfs/album/d60f6a9c5bc4a109a72aaa610525e3bae2e872bf.jpg"],
        [3333115, "你说谁", "https://i0.hdslb.com/bfs/album/58ac841450aa4da91f596f50cceb1fc893f5e16a.jpg"],
        [3333116, "你说谁唐", "https://i0.hdslb.com/bfs/album/51e6222e60a5c53fffa7259a6d1e26593791ee6f.jpg"],

        // 来自 @爱茉-Merry-
        // https://t.bilibili.com/677805689726304279
        [3333211, "吃瓜", "https://i0.hdslb.com/bfs/new_dyn/bdd4c8815ef2c491c935d06c5eb36cc1173530952.png"],
        [3333212, "别在这理发店", "https://i0.hdslb.com/bfs/new_dyn/5e06547caaf3d72d0d54ace1bb26776c173530952.png"],
        [3333213, "要牛奶钱", "https://i0.hdslb.com/bfs/new_dyn/8457f7cb02d9a7f57db13537be0140d5173530952.png"],
        [3333214, "大傻呗2", "https://i0.hdslb.com/bfs/new_dyn/622049f688365262102f6b7ec084fd4d173530952.png"],
        [3333215, "绿码", "https://i0.hdslb.com/bfs/new_dyn/a4ff8b3a33852683392a4896adf7e93a173530952.png"],
        [3333216, "熬夜啵比", "https://i0.hdslb.com/bfs/new_dyn/c363244428dcbe1f21b71489b01e3949173530952.png"],
        [3333217, "喝涮锅水", "https://i0.hdslb.com/bfs/new_dyn/32f960833820746725d72898a29efd3d173530952.png"],
        [3333218, "结婚", "https://i0.hdslb.com/bfs/new_dyn/86686f3cbfa089027a20ab3029803db0173530952.png"],
        [3333219, "结婚2", "https://i0.hdslb.com/bfs/new_dyn/2d3f4f0c3a1a2489e72257959ac34ae8173530952.png"],

        // 来自 @卡古拉的醋昆布e
        // https://t.bilibili.com/675526380607242246
        [3333311, "可爱滴捏", "https://i0.hdslb.com/bfs/album/98de968a6274f9cb14c9aaade94c1a0d07d415b4.jpg"],

        // 来自 @玉桂狗美图分享bot
        // https://t.bilibili.com/665015380203274241
        [3333411, "吐舌1", "https://i0.hdslb.com/bfs/album/e4abe1c249b67f33a0064075bd0ea00b589769ea.jpg"],
        [3333412, "吐舌2", "https://i0.hdslb.com/bfs/album/723e1aee265863b491cf30d77c9b3f6c5aef2c0b.jpg"],
        [3333413, "吐舌3", "https://i0.hdslb.com/bfs/album/c6cdd070d77d2eea270f62e0ffa1e02068171930.jpg"],
        [3333414, "笑死", "https://i0.hdslb.com/bfs/album/84b6b70cdcc103d678d1f31d436df26c834748f8.jpg"],
        [3333415, "哭哭", "https://i0.hdslb.com/bfs/album/954cbdeb0712fc119e1184242b5396dd9069dd22.jpg"],
        [3333416, "晕", "https://i0.hdslb.com/bfs/album/fa4b087cd7ddadcb6112a179391d9ef384522257.jpg"],
        [3333417, "大小眼", "https://i0.hdslb.com/bfs/album/6a8e897a75f6a908ee7af054a6eeedd9ce7071c0.jpg"],
        [3333418, "安逸", "https://i0.hdslb.com/bfs/album/9297ea482b101cb19e0c91cddfaa884618491276.jpg"],
        [3333419, "失去高光", "https://i0.hdslb.com/bfs/album/9f35464759d59ac26ee0d8410260ebe57b60d855.jpg"],
        [3333421, "哼", "https://i0.hdslb.com/bfs/album/a535ba2ee900ff47334a44cd243d32d3ee28a829.jpg"],
        [3333422, "失败了", "https://i0.hdslb.com/bfs/album/a00e1b34b0079887fd37eba520693f59ba08b116.jpg"],
        [3333423, "委屈", "https://i0.hdslb.com/bfs/album/6e34c617292499da20f82a8b09364124a03eaf57.jpg"],
        [3333424, "诶?", "https://i0.hdslb.com/bfs/album/6098e9a0b6acd435ec71da48a243dae94eda4c42.jpg"],
        [3333425, "还不错嘛", "https://i0.hdslb.com/bfs/album/ac8e96351d7bf58936d77266db38edf8d723b47b.jpg"],
        [3333426, "可爱", "https://i0.hdslb.com/bfs/album/ec19639cca79d73895da936850981769c2fa48f3.jpg"],
        [3333427, "饿饿", "https://i0.hdslb.com/bfs/album/94422b4d3ae81e84ddb82f8439ef773cbdd8e709.jpg"],
        [3333428, "星星眼", "https://i0.hdslb.com/bfs/album/4e522af3fe0682d23bcbad98a01234220585418b.jpg"],
        [3333429, "嘻嘻", "https://i0.hdslb.com/bfs/album/52797832ae62644abcb5a246d547c89e5ff6c2cf.jpg"],
        [3333431, "别急", "https://i0.hdslb.com/bfs/album/a543e79390f7ab501d1a8610fb9508e2045140c9.jpg"],
        [3333432, "呃呃", "https://i0.hdslb.com/bfs/album/800cbf45317f0b4466b674c1d98d8bc7fb92152f.jpg"],
        [3333433, "要哭了", "https://i0.hdslb.com/bfs/album/90b5b2d1c3c99056a1b12cc4f885b027ac405a40.jpg"],
        [3333434, "微笑", "https://i0.hdslb.com/bfs/album/5a9b5cde55216d1a2028b47c64f72403f8bf39c6.jpg"],

        // 来自 @原来是小瘪终极
        // https://t.bilibili.com/677009063564804096
        [3333511, "飞!", "https://i0.hdslb.com/bfs/album/232079a3a2135966a4182aacc6744dbee9a3454d.jpg"],

        // 来自 @馒头卡今天吃什么
        // https://t.bilibili.com/667973375719636996
        [3333611, "啵叽王子", "https://i0.hdslb.com/bfs/album/e02a273e3a8a9f3d3f38a3f0d52810dfecf701ce.png"],

        // 来自 @四等双足多用途北极熊
        // https://t.bilibili.com/677933357627080774
        [3333711, "玉米肠", "https://i0.hdslb.com/bfs/new_dyn/6cafd16007441caac580e763f9bec6625083548.png"],
        [3333712, "哇库哇库", "https://i0.hdslb.com/bfs/new_dyn/d0acdc3eb0744b6795c2f265eeae82c45083548.png"],
        [3333713, "可爱捏", "https://i0.hdslb.com/bfs/new_dyn/1ac7d26c47460d0dd4ec9ae1cb8f89725083548.png"],
        [3333714, "哭哭2", "https://i0.hdslb.com/bfs/new_dyn/36d7d7f3653b2e3a147d6c3942b154a15083548.png"],

        // 来自 @风罗4个圈儿
        // https://t.bilibili.com/668646710612852743
        [3333191, "桂物敬礼", "https://i0.hdslb.com/bfs/album/f8f8de3ef0b36b69fbff630a30ac33aee1fdfc96.png"],

        // 来自 @爱茉-Merry-
        // https://t.bilibili.com/676773935585427481
        [3333291, "桂物mua", "https://i0.hdslb.com/bfs/album/bb3613eabd73892f0a2e31f4596cb2a37980b9bb.png"],
        [3333292, "桂物啵啵", "https://i0.hdslb.com/bfs/album/29d7a0b905556fe5a365e4b62a15024ccf4654ee.png"],
        [3333293, "桂物抱抱", "https://i0.hdslb.com/bfs/album/187fbc36bbf5efb168932a5591a120cdb840dd1d.png"],
    ]

    let emote_dict = {}
    let chn_emote_dict = {}
    emote_source.forEach(function (arr){
        emote_dict["[啵啵_" + arr[1] + "]"] = arr[2];
        chn_emote_dict["【啵啵_" + arr[1] + "】"] = '<img src="' + arr[2] + '" alt="[啵啵_' + arr[1] + ']">';
    });

    let getEmote = function(arr) {
        return {
                "id": arr[0],
                "package_id": 3333,
                "text": "[啵啵_" + arr[1] + "]",
                "url": arr[2],
                "mtime": 1654321000,
                "type": 3,
                "attr": 0,
                "meta": {
                    "size": 2,
                    "suggest": [
                        ""
                    ],
                    "alias": arr[1]
                },
                "flags": {
                    "unlocked": false
                },
                "activity": null
            }
    }
    let bobo = {
        "id": 3333,
        "text": "啵啵 (来自 @风罗4个圈儿, @爱茉-Merry-, @卡古拉的醋昆布e, @玉桂狗美图分享bot, @原来是小瘪终极, @馒头卡今天吃什么, @四等双足多用途北极熊)",
        "url": "https://i0.hdslb.com/bfs/new_dyn/3e1656dd6dd1255f65fb91389dd73f775858138.png",
        "mtime": 1654321000,
        "type": 3,
        "attr": 28,
        "meta": {
            "size": 2,
            "item_id": 3333,
            "item_url": "https://www.bilibili.com",
            "asset_id": 333333333
        },
        "emote":  emote_source.map(getEmote),
        "flags": {
            "added": true
        }
    }

    let injectDynamicItem = function(item) {
        if (item && "modules" in item && "module_dynamic" in item.modules && "desc" in item.modules.module_dynamic && item.modules.module_dynamic.desc && "rich_text_nodes" in item.modules.module_dynamic.desc) {
            for(let node of item.modules.module_dynamic.desc.rich_text_nodes) {
                if (node.text in emote_dict) {
                    node.type = 'RICH_TEXT_NODE_TYPE_EMOJI'
                    node.emoji = {
                        "icon_url": emote_dict[node.text],
                        "size": 2,
                        "text": node.text,
                        "type": 3
                    }
                }
            }
        }
        if (item && "orig" in item && item.orig) {
            injectDynamicItem(item.orig);
        }
    }

    // 动态直接通过 Hook XHR 响应完成
    xhook.after(function(request, response) {
        if (request.url.includes('//api.bilibili.com/x/emote/user/panel/web?business=reply')) {
            // 表情包面板
            let response_json = JSON.parse(response.text);
            response_json.data.packages.push(bobo);
            response.text = JSON.stringify(response_json);
        } else if (request.url.includes('//api.bilibili.com/x/polymer/web-dynamic/v1/detail')){
            // 动态详情页
            let response_json = JSON.parse(response.text);
            injectDynamicItem(response_json.data.item);
            response.text = JSON.stringify(response_json);
        } else if (request.url.includes('//api.bilibili.com/x/polymer/web-dynamic/v1/feed/space') || request.url.includes('//api.bilibili.com/x/polymer/web-dynamic/v1/feed/all')) {
            // 主时间线和个人主页
            let response_json = JSON.parse(response.text);
            for (let i in response_json.data.items) {
                injectDynamicItem(response_json.data.items[i]);
            }
            response.text = JSON.stringify(response_json);
        } else if (request.url.includes('//app.bilibili.com/x/topic/web/details/cards')) {
            // 话题页
            let response_json = JSON.parse(response.text);
            for (let i in response_json.data.topic_card_list.items) {
                let item = response_json.data.topic_card_list.items[i]
                if (item.topic_type == 'DYNAMIC') {
                    injectDynamicItem(item.dynamic_card_item);
                }
            }
            response.text = JSON.stringify(response_json);
        }
    });

    // 评论区通过修改网页完成(感谢 @Sparanoid)
    window.addEventListener('load', () => {
        const DEBUG = false;
        const NAMESPACE = 'bilibili-bobo-emoji';

        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 (content.innerHTML.includes('【啵啵_')) {
                let innerHTML = content.innerHTML;
                for (let item in chn_emote_dict) {
                    innerHTML = innerHTML.replace(new RegExp(item,"gm"), chn_emote_dict[item]);
                }
                content.innerHTML = innerHTML;
            }
        }

        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);

 })();
长期地址
遇到问题?请前往 GitHub 提 Issues。