您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
B站直播弹幕清理,旨在提高弹幕的信息密度
// ==UserScript== // @name 收收味 // @namespace http://tampermonkey.net/ // @version 0.3 // @description B站直播弹幕清理,旨在提高弹幕的信息密度 // @author xfgryujk // @include /https?:\/\/live\.bilibili\.com\/?\??.*/ // @include /https?:\/\/live\.bilibili\.com\/\d+\??.*/ // @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/ // @run-at document-start // @require https://cdn.jsdelivr.net/gh/google/brotli@5692e422da6af1e991f9182345d58df87866bc5e/js/decode.js // @require https://greasyfork.org/scripts/417560-bliveproxy/code/bliveproxy.js?version=984333 // @grant none // ==/UserScript== (function() { let filterers = [] function main() { filterers = [ trim, shink1CharLeft, shink1CharRight, shink2CharLeft, shink3CharLeft, hideRecentRepeat, hideRepeater, ] bliveproxy.addCommandHandler('DANMU_MSG', danmakuHandler) } function danmakuHandler(command) { let ctx = new DanmakuFilterContext(command) for (let filterer of filterers) { filterer(ctx) if (!ctx.showDanmaku && !ctx.showComment) { break } } let info = command.info if (info[1] != ctx.msg) { // console.log(`${info[1]} -> ${ctx.msg}`) info[1] = ctx.msg } let hideFlag = 0 if (!ctx.showComment) { hideFlag |= 1 } if (!ctx.showDanmaku) { hideFlag |= 2 } if (hideFlag != 0) { // console.log(`hide: ${ctx.msg}`) let params = command.cmd.split(':') while (params.length < 5) { params.push('0') } params[4] = hideFlag.toString() command.cmd = params.join(':') } } class DanmakuFilterContext { constructor(command) { let info = command.info this.msg = info[1] this.uid = info[2][0] this.username = info[2][1] this._showDanmaku = true this._showComment = true } get showDanmaku() { return this._showDanmaku } hideDanmaku() { this._showDanmaku = false } get showComment() { return this._showComment } hideComment() { this._showComment = false } } function trim(ctx) { ctx.msg = ctx.msg.trim() } function shink1CharLeft(ctx) { // 以至少4个相同字符开头,尾部可跟最多4个任意字符,例:888888888 const REG = /^(.)\1{3,}.{0,4}?$/ let match = REG.exec(ctx.msg) if (match !== null) { // 保留3个字符 ctx.msg = [match[1], match[1], match[1]].join('') } } function shink1CharRight(ctx) { // 以最多4个任意字符开头,尾部至少4个相同字符,例:ohhhhhhhh const REG = /^(.{0,4}?)(.)\2{3,}$/ let match = REG.exec(ctx.msg) if (match !== null) { // 保留头部+尾部3个字符 ctx.msg = [match[1], match[2], match[2], match[2]].join('') } } function shink2CharLeft(ctx) { // 以2个字符至少重复3次开头,例:欧啦欧啦欧啦欧啦 const REG = /^(..)\1\1/ let match = REG.exec(ctx.msg) if (match !== null) { // 保留2次重复 ctx.msg = match[1] + match[1] } } function shink3CharLeft(ctx) { // 以至少3个字符至少重复2次开头,例:awslawslawslawsl const REG = /^(.{3,}?)\1/ let match = REG.exec(ctx.msg) if (match !== null) { // 保留1次重复 ctx.msg = match[1] } } // msg -> {time: 上次显示弹幕时间} let recentRepeatInfoMap = new Map() const MIN_REPEAT_INTERVAL = 10 * 1000 // 隐藏最近重复的弹幕 function hideRecentRepeat(ctx) { let curTime = new Date() let key = ctx.msg.toLowerCase() let repeatInfo = recentRepeatInfoMap.get(key) if (repeatInfo === undefined) { repeatInfo = {time: curTime} recentRepeatInfoMap.set(key, repeatInfo) // 清理 if (recentRepeatInfoMap.size >= 1024) { let keysToDel = [] for (let [key, repeatInfo] of recentRepeatInfoMap.entries()) { if (curTime - repeatInfo >= MIN_REPEAT_INTERVAL) { keysToDel.push(key) } } for (let key of keysToDel) { recentRepeatInfoMap.delete(key) } } return } if (curTime - repeatInfo.time >= MIN_REPEAT_INTERVAL) { repeatInfo.time = curTime return } ctx.hideDanmaku() } // uid -> {msg: 重复弹幕内容, startTime: 重复开始时间, repeatNum: 重复次数} let uidRepeatInfoMap = new Map() let uidBlacklistForRepeat = new Set() const REPEAT_PERIOD_PER_USER = 30 * 1000 const REPEAT_NUM_FOR_ADD_BLACK = 3 // 屏蔽复读机用户 function hideRepeater(ctx) { if (uidBlacklistForRepeat.has(ctx.uid)) { ctx.hideDanmaku() ctx.hideComment() return } let curTime = new Date() let repeatInfo = uidRepeatInfoMap.get(ctx.uid) if (repeatInfo !== undefined) { // 清理 if (curTime - repeatInfo.startTime > REPEAT_PERIOD_PER_USER || repeatInfo.msg != ctx.msg) { repeatInfo = undefined } } if (repeatInfo === undefined) { // 第一次重复计数 repeatInfo = { msg: ctx.msg, startTime: curTime, repeatNum: 1 } uidRepeatInfoMap.set(ctx.uid, repeatInfo) return } if (++repeatInfo.repeatNum >= REPEAT_NUM_FOR_ADD_BLACK) { console.log(`拉黑【${ctx.username}】,uid=${ctx.uid},复读内容:${repeatInfo.msg}`) ctx.msg = '【此用户已屏蔽】' + ctx.msg uidBlacklistForRepeat.add(ctx.uid) ctx.hideDanmaku() } } main() })();