Greasy Fork

Greasy Fork is available in English.

哔哩哔哩直播 被吞弹幕标记

在B站直播发送弹幕时,被吞的弹幕会被划线,蜀黍吞的会变红,主播吞的会变黄。

当前为 2025-08-10 提交的版本,查看 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name                Bilibili Live Banned Danmaku Marker
// @name:zh-CN          哔哩哔哩直播 被吞弹幕标记
// @name:zh-TW          嗶哩嗶哩直播 烙賽彈幕標記
// @description         You sent banned danmakus will be add strikethrough line.
// @description:zh-CN   在B站直播发送弹幕时,被吞的弹幕会被划线,蜀黍吞的会变红,主播吞的会变黄。
// @description:zh-TW   在B站直播發送彈幕時,被封鎖的彈幕會被劃線。
// @version             0.5
// @author              Yulon
// @namespace           https://github.com/yulon
// @icon                https://www.bilibili.com/favicon.ico
// @license             MIT
// @match               *://live.bilibili.com/*
// @grant               unsafeWindow
// @run-at              document-start
// ==/UserScript==

(function() {
	'use strict';

	function parseJson(t) {
		if (!t || t.length === 0) {
			return null
		}
		try {
			return JSON.parse(t)
		} catch (e) {
			return null
		}
	}

	function FetchHook(onResp, urlFilter, gThis) {
        if (!gThis) {
            gThis = window
        }

        const th1s = this

        this.fetch = gThis.fetch

        gThis.fetch = function (url) {
            const prm = th1s.fetch.apply(this, arguments)
            if (urlFilter && (typeof url !== 'string' || !urlFilter(url))) {
                return prm
            }
            return prm.then((resp) => resp.text().then((t) => {
                t = onResp(t, url, th1s.fetch)
                resp.json = function () {
                    try {
                        return Promise.resolve(JSON.parse(t))
                    } catch (e) {
                        return Promise.reject(e)
                    }
                }
                resp.text = function () {
                    return Promise.resolve(t)
                }
                return resp
            }))
        }

        const xhrPt = gThis.XMLHttpRequest.prototype
        const xhrOpen = xhrPt.open
        const xhrSend = xhrPt.send

        this.newXMLHttpRequest = function () {
            let xhr = new XMLHttpRequest()
            xhr.open = xhrOpen
            xhr.send = xhrSend
            return xhr
        }

        xhrPt.open = function (method, url, async, user, password) {
            if ((typeof url === 'string' && urlFilter(url)) || !urlFilter) {
                this._fetchHookUrl = url
                this._fetchHookAsync = async
            }
            return xhrOpen.apply(this, arguments)
        }

        const onreadystatechangeHook = function () {
            if (this.readyState === 4 && this.status === 200) {
                let newVal = onResp(this.responseText, this._fetchHookUrl, this.fetch)
                if (newVal !== this.responseText) {
                    Object.defineProperty(this, 'responseText', {
                        value: newVal
                    })
                }
            }
            if (!this._fetchHookOnreadystatechange) {
                return
            }
            return this._fetchHookOnreadystatechange.apply(this, arguments)
        }

        xhrPt.send = function () {
            if (!('_fetchHookUrl' in this)) {
                return xhrSend.apply(this, arguments)
            }
            if (this._fetchHookAsync) {
                this._fetchHookOnreadystatechange = this.onreadystatechange
                this.onreadystatechange = onreadystatechangeHook
                return xhrSend.apply(this, arguments)
            }
            const r = xhrSend.apply(this, arguments)
            if (this.status === 200) {
                let newVal = onResp(this.responseText, this._fetchHookUrl, this.fetch)
                if (newVal !== this.responseText) {
                    Object.defineProperty(this, 'responseText', {
                        value: newVal
                    })
                }
            }
            return r
        }
    }

	function getContent(r) {
		if (
			r &&
			(('msg' in r && (r.msg === 'f' || r.msg === 'k')) || ('message' in r && (r.message === 'f' || r.message === 'k'))) &&
			'data' in r && 'mode_info' in r.data && 'extra' in r.data.mode_info
		) {
			const extra = parseJson(r.data.mode_info.extra)
			if (extra && 'content' in extra) {
				return extra.content
			}
		}
		return null
	}

	function checkContent(r) {
		const cont = getContent(r)
		if (!cont) {
			return
		}
		;(function lineThrough() {
			const danmakus = document.querySelectorAll('#chat-items .danmaku-item-right')
			for (let danmaku of danmakus) {
				if (danmaku.innerText === cont && !('_isMarked' in danmaku)) {
					let danmakuItem = danmaku.parentElement
					danmakuItem.style.textDecoration = 'line-through'
					let clr = ((('msg' in r && r.msg === 'f') || ('message' in r && r.message === 'f'))) ? 'red' : '#f09300'
					danmakuItem.style.color = clr
					let userName = danmakuItem.querySelector('.user-name')
					if (userName) {
						userName.style.color = clr
					}
                    danmaku._isMarked = true
					return
				}
			}
			setTimeout(lineThrough, 500)
		})()
	}

	new FetchHook(
		(t) => {
            checkContent(parseJson(t))
            return t
        },
		(url) => url.indexOf('api.live.bilibili.com/msg/send') >= 0,
		unsafeWindow
	)
})();