B 站大表情

放大 B 站表情,并在光标指向表情时显示其名称

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         B 站大表情
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  放大 B 站表情,并在光标指向表情时显示其名称
// @author       5ec1cff
// @license      AGPL
// @match        *://*.bilibili.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        unsafeWindow
// @run-at       document-start
// @grant        GM_addStyle
// ==/UserScript==

(function(window) {
    'use strict';
    const DEBUG_LOGGING = false;

    const selector = 'p.text>img[alt],span.text-con>img[alt],img.bili-rich-text-emoji,img.emoji-large,img.opus-text-rich-emoji,p#contents>img[alt]';
    const console = unsafeWindow.console;

    function logd(...args) {
      if (DEBUG_LOGGING) console.log(...args)
    }

    function loge(...args) {
      console.error(...args)
    }

    GM_addStyle(`${selector} { width: 64px !important; height: 64px !important;  }`);
    GM_addStyle(`.bili-rich-text__content.folded { max-height: unset !important; }`)
    const emoMap = {};
    window.emoMap = emoMap
    let observer = new MutationObserver(function(mutations, observe) {
        let s = new Set(mutations.map(x=>x.target))
        for (let root of s) {
            root.querySelectorAll(selector).forEach(e => {
                e.src = e.src.replace(/@\d+w_\d+h/, '@200w_200h');//'@100w_100h.webp');
                let id = emoMap[e.alt];
                e.style="width:100px !important;height:100px !important";
                if (id)
                    e.title = e.alt + ' ' + id;
                else
                    e.title = e.alt;
            })
        }
    })

    let oldAttachShadow = Element.prototype.attachShadow;
    window.__bilibigemo_oldAttachShadow = oldAttachShadow;
    Element.prototype.attachShadow = function(...args) {
        let ret = oldAttachShadow.apply(this, args);
        try {
            if (this.tagName == 'BILI-RICH-TEXT')
                observer.observe(ret, { 'childList': true, 'subtree': true });
        } catch (e) {
            console.trace(e);
        }
        return ret;
    }


    document.addEventListener('DOMContentLoaded', () => {
      observer.observe(document.body, { 'childList': true, 'subtree': true });
    })

    function scanreplies(replies) {
      let i = 0
      for (let reply of replies) {
        let emote = reply?.content?.emote;
        if (emote) {
          for (let emo in emote) {
            if (!(emo in emoMap)) i++;
            emoMap[emo] = emote[emo].package_id
          }
        }
        if (reply.replies) i+=scanreplies(reply.replies)
      }
      return i
    }

    let xhrp = unsafeWindow.XMLHttpRequest.prototype;
    let oldopen = xhrp.open;
    xhrp.open = function(method, url, ...args) {
      oldopen.call(this, method, url, ...args);
      logd(url)
      if (!this.__mark && url.startsWith('https://api.bilibili.com/x/v2/reply/')) {
        logd(url, 'matched')
        this.__mark = true;
        const xhr = this;
        this.addEventListener('readystatechange', (e) => {
          if (xhr.readyState === XMLHttpRequest.DONE && xhr.status == 200) {
            try {
              const r = JSON.parse(xhr.responseText);
              let replies = r?.data?.replies;
              if (replies) {
                let i = scanreplies(replies);
                logd(url, 'scan added', i);
              }
            } catch (e) {
              loge(e);
            }
          }
        });
      }
    }

    let headp = unsafeWindow.HTMLHeadElement.prototype;
    let oldappendc = headp.appendChild;
    headp.appendChild = function (x, ...args) {
      if (x instanceof HTMLScriptElement) {
        if (x.src?.startsWith?.('https://api.bilibili.com/x/v2/reply')) {
          logd('script', x.src)
          let u = new URL(x.src)
          let cb = u.searchParams.get('callback')
          logd(cb, window[cb])
          let ocb = window[cb]
          window[cb] = (r) => {
            try {
              let replies = r?.data?.replies;
              if (replies) scanreplies(replies)
            } catch (e) {
              loge(e);
            }
            ocb(r)
          }
        }
      }
      oldappendc.call(this, x, ...args)
    }

    let origfetch = window.fetch;
    window.fetch = async function(url, ...args) {
        let r = await origfetch.apply(this, [url, ...args]);
        try {
            if (url?.indexOf?.('//api.bilibili.com/x/v2/reply/wbi/main') === 0) {
                let r2 = r.clone()
                let data = await r2.json();
                let replies = data?.data?.replies;
                if (replies) scanreplies(replies)
            }
        } catch (e) {
            console.trace('parse fetch', url, e);
        }
        return r;
    }
})(unsafeWindow);