// ==UserScript==
// @name 虎牙重复弹幕过滤
// @namespace http://tampermonkey.net/
// @version 0.5
// @description 虎牙重复弹幕过滤, 过滤视频弹幕, 过滤聊天弹幕, 过滤表情弹幕, 放过自己弹幕
// @author Mindfulness
// @match https://www.huya.com/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const HuyaFilter = {
selfUserName: '',
filterCount: 0,
filterTopList: [],
recordDanmuList: [],
_options: {},
getOptions: function(optionKey, defaultValue)
{
if(this._options[optionKey] == undefined)
{
this._options[optionKey] = GM_getValue('_huya_filter_' + optionKey, defaultValue);
}
return this._options[optionKey];
},
setOptions: function(optionKey, optionValue)
{
GM_setValue('_huya_filter_' + optionKey, optionValue);
this._options[optionKey] = optionValue;
},
createUI: function()
{
let icon = document.createElement('i');
icon.id = 'J-room-chat-filter';
icon.className = 'room-chat-tool';
icon.style = 'display: inline-block;width: 35px;height: 22px;cursor:pointer;text-align:center;margin-top: 1px;border: 1px solid #ff8a00;border-radius: 5px;background-color: #ff8a00;color: #fff;font-weight: bold;user-select:none;';
icon.innerText = '过滤';
let toolsPannel = document.querySelector('#tipsOrchat .chat-room__ft .chat-room__ft__pannel .room-chat-tools');
toolsPannel.appendChild(icon);
let tipsOrchatRect = document.querySelector('#tipsOrchat').getBoundingClientRect();
let mainCol = document.querySelector('#main_col');
let mainColRect = mainCol.getBoundingClientRect();
let popup = document.createElement('div');
popup.id = 'J-room-chat-filter-pannel';
let popTop = tipsOrchatRect.top - mainColRect.top - 300 - 2;
let popLeft = tipsOrchatRect.left - mainColRect.left;
popup.style='border:1px solid #333;width:336px;height:300px;background-color:#eee;cursor:default;position:absolute;top:' + popTop + 'px;left:' + popLeft + 'px;z-index:100;display:none;';
let popupHeader = document.createElement('div');
popupHeader.style='padding:10px;border-bottom:1px solid #888;';
let filterCounter = document.createElement('span');
filterCounter.id = 'J-room-chat-filter-counter';
filterCounter.style = 'color:#008;';
filterCounter.innerText = '已过滤: 0';
popupHeader.appendChild(filterCounter);
let lastFilterDanmu = document.createElement('span');
lastFilterDanmu.id = 'J-room-chat-filter-last';
lastFilterDanmu.style = 'margin-left:10px;padding:0 2px;display:inline-flex;overflow:hidden;max-width:200px;height:18px;color:#aaa;background-color:#ff8;';
lastFilterDanmu.title = '最后过滤弹幕内容';
popupHeader.appendChild(lastFilterDanmu);
let popupClose = document.createElement('a');
popupClose.innerText = '关闭';
popupClose.style='cursor:pointer;color:#800;padding-right:10px;position:absolute;right:0;';
popupClose.addEventListener('click', () => {popup.style.display = 'none'});
popupHeader.appendChild(popupClose);
popup.appendChild(popupHeader);
let popupOptions = document.createElement('div');
popupOptions.style='padding:10px;border-bottom:1px solid #888;';
popup.appendChild(popupOptions);
let popupFilterTopList = document.createElement('div');
popupFilterTopList.style='padding:10px;';
let topListElement = document.createElement('ul');
topListElement.id = 'J-room-chat-filter-topList';
topListElement.style = 'padding:0;margin:0;';
popupFilterTopList.appendChild(topListElement);
let resetElement = document.createElement('a');
resetElement.innerText = '重置统计';
resetElement.style = 'cursor:pointer;color:#800;padding-right:10px;position:absolute;right:0;bottom:10px;';
resetElement.addEventListener('click', function()
{
this.filterCount = 0;
this.filterTopList = [];
this.recordDanmuList = [];
this.showFilterCountInfo();
}.bind(this));
popupFilterTopList.appendChild(resetElement);
popup.appendChild(popupFilterTopList);
mainCol.appendChild(popup);
icon.addEventListener('click', (e) => {popup.style.display = popup.style.display == 'block' ? 'none' : 'block'});
this.showCheckboxOptions(popupOptions, 'filter_video_danmu', '过滤视频弹幕');
this.showCheckboxOptions(popupOptions, 'filter_chat_danmu', '过滤聊天弹幕');
this.showCheckboxOptions(popupOptions, 'filter_icon_danmu', '过滤表情弹幕');
this.showCheckboxOptions(popupOptions, 'filter_skip_me', '放过自己发的弹幕');
},
showCheckboxOptions: function(contriner, optionKey, optionText)
{
let checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = this.getOptions(optionKey, 1) == 1;
checkbox.addEventListener('change', function(e)
{
this.setOptions(optionKey, checkbox.checked ? 1 : 0);
console.log(optionText + ': ' + (checkbox.checked ? '已开启' : '已关闭'));
}.bind(this));
let span = document.createElement('span');
span.innerText = optionText;
span.style='padding-left:10px;';
let label = document.createElement('label');
label.style = 'display: inline-block;width:45%;';
label.appendChild(checkbox);
label.appendChild(span);
contriner.appendChild(label);
},
showFilterCountInfo: function()
{
document.querySelector('#J-room-chat-filter-counter').innerHTML = '已过滤: ' + this.filterCount;
let topListElement = document.querySelector('#J-room-chat-filter-topList');
topListElement.innerHTML = '';
for(let i = 0; i < 10; i++)
{
let itemElement = document.createElement('li');
let showIndex = i + 1;
let itemHtml = '<span style="font-weight:bold;">' + showIndex + '.</span>';
if(i < this.filterTopList.length) itemHtml += ' (' + this.filterTopList[i].count + ') ' + this.filterTopList[i].danmu;
itemElement.innerHTML = itemHtml;
itemElement.style = 'padding:0;margin:0;list-style:none;display:block;width:316px;overflow:hidden;height:18px;line-height:18px;';
topListElement.appendChild(itemElement);
}
},
start: function()
{
let videoDanmuDiv = document.querySelector('#danmudiv');
let chatDanmuDiv = document.querySelector('#chat-room__list');
if(!videoDanmuDiv || !chatDanmuDiv) return setTimeout(this.start.bind(this), 1000);
this.selfUserName = document.querySelector('#login-username').innerText;
let videoDanmuFilterObserver = new MutationObserver((recordList, observer) => {
if(this.getOptions('filter_video_danmu', 1) != 1) return;
let hasNewfilterDanmu = false;
recordList.forEach((record) => {
if(record.type !== 'childList') return;
for(let i = 0; i < record.addedNodes.length; i++)
{
let danmuElement = record.addedNodes[i];
if(parseFloat(danmuElement.style.borderWidth) > 0 && this.getOptions('filter_skip_me', 1) == 1) continue;
let danmuText = danmuElement.innerText.trim();
if(danmuText.length == 0)
{
if(this.getOptions('filter_icon_danmu', 1) != 1) continue;
danmuText = '<表情>';
}
let filterDanmuText = danmuText;
let repeatMatches = danmuText.match(/^(.+?)\1+$/);
if(repeatMatches) filterDanmuText = repeatMatches[1] + ' ...';
if(!this.recordDanmuList[filterDanmuText]) this.recordDanmuList[filterDanmuText] = 0;
let repeatCount = ++this.recordDanmuList[filterDanmuText];
if(repeatCount == 1) continue;
danmuElement.remove();
// 统计新增
this.filterCount++;
hasNewfilterDanmu = true;
// 更新最后过滤弹幕文本
document.querySelector('#J-room-chat-filter-last').innerHTML = '(' + repeatCount + ') ' + danmuText;
// 追加
let alreadyExist = false;
for(let i = 0; i < this.filterTopList.length; i++)
{
if(filterDanmuText === this.filterTopList[i].danmu)
{
this.filterTopList[i].count = repeatCount;
alreadyExist = true;
break;
}
}
if(!alreadyExist) this.filterTopList.push({danmu: filterDanmuText, count: repeatCount});
}
});
if(hasNewfilterDanmu)
{
// 从多到少排序
this.filterTopList.sort((a, b) => b.count - a.count);
// 删除多余
while(this.filterTopList.length > 10) this.filterTopList.pop();
// 更新统计信息显示
this.showFilterCountInfo();
}
});
videoDanmuFilterObserver.observe(videoDanmuDiv, {childList: true});
let chatDanmuFilterObserver = new MutationObserver((recordList, observer) => {
if(this.getOptions('filter_chat_danmu', 1) != 1) return;
recordList.forEach((record) => {
if(record.type !== 'childList') return;
for(let i = 0; i < record.addedNodes.length; i++)
{
let danmuElement = record.addedNodes[i];
let danmuUserNameElement = danmuElement.querySelector('.name');
// 不是用户发言
if(!danmuUserNameElement) continue;
let danmuUserName = danmuUserNameElement.innerText;
if(danmuUserName == this.selfUserName && this.getOptions('filter_skip_me', 1) == 1) continue;
let danmuText = danmuElement.querySelector('.msg').innerText.trim();
if(danmuText.length == 0)
{
if(this.getOptions('filter_icon_danmu', 1) != 1) continue;
danmuText = '<表情>';
}
let filterDanmuText = danmuText;
let repeatMatches = danmuText.match(/^(.+?)\1+$/);
if(repeatMatches) filterDanmuText = repeatMatches[1] + ' ...';
if(!this.recordDanmuList[filterDanmuText]) continue;
let repeatCount = this.recordDanmuList[filterDanmuText];
if(repeatCount <= 1) continue;
danmuElement.remove();
}
});
});
chatDanmuFilterObserver.observe(chatDanmuDiv, {childList: true});
}
};
HuyaFilter.start();
HuyaFilter.createUI();
})();