function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, ("value" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
// ==UserScript==
// @name Internet Content Filterer
// @name:zh 网络内容过滤器
// @namespace Violentmonkey Scripts
// @match *://*.weibo.com/*
// @match *://*.weibo.cn/*
// @match *://weibo.com/*
// @match *://m.hupu.com/*
// @match *://tieba.baidu.com/*
// @match *://www.zhihu.com/search*
// @exclude *://weibo.com/tv*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @version 3.8
// @author fbz
// @description block users' posts and comments whom you blocked
// @description:zh 屏蔽特定用户的帖子和评论
// @run-at document-start
// @license MIT
// @require https://unpkg.com/[email protected]/dist/ajaxhook.js
// ==/UserScript==
/* eslint-disable max-classes-per-file,no-param-reassign,no-console */
/* jshint esversion: 6 */
// eslint-disable-next-line func-names
(async function (_dec, _dec2, _dec3, _class, _WeiboFilter, _apiBlackList, _dec4, _class2, _HupuFilter, _dec5, _dec6, _dec7, _class3, _TiebaFilter, _DomainStore) {
/* 添加样式 */
const BlockButtonClass = 'block-button';
const cssByCls = cls => `.${cls}`;
const css = `
#add_ngList_btn {
position: fixed;
bottom: 2rem;
left: 1rem;
width: 2rem;
height: 2rem;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.5);
background: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer !important;
z-index: 100;
}
#add_ngList_btn::before {
content: '';
position: absolute;
width: 16px;
height: 2px;
background: rgba(0, 0, 0, 0.5);
top: calc(50% - 1px);
left: calc(50% - 8px);
}
#add_ngList_btn::after {
content: '';
position: absolute;
height: 16px;
width: 2px;
background: rgba(0, 0, 0, 0.5);
top: calc(50% - 8px);
left: calc(50% - 1px);
}
.my-dialog__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 10000;
background: rgba(0, 0, 0, 0.3);
display: none;
}
.my-dialog {
position: relative;
background: #FFFFFF;
border-radius: 2px;
box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
box-sizing: border-box;
width: 50%;
transform: none;
left: 0;
margin: 0 auto;
}
.my-dialog .my-dialog__header {
border-bottom: 1px solid #e4e4e4;
padding: 14px 16px 10px 16px;
}
.my-dialog__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.my-dialog__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: transparent;
border: none;
outline: none;
cursor: pointer;
font-size: 16px;
width: 12px;
height: 12px;
transform: rotateZ(45deg);
}
.my-dialog .my-dialog__header .my-dialog__headerbtn {
right: 16px;
top: 16px;
}
.my-dialog__headerbtn .my-dialog__close::before {
content: '';
position: absolute;
width: 12px;
height: 1.5px;
background: #909399;
top: calc(50% - 0.75px);
left: calc(50% - 6px);
border-radius: 2px;
}
.my-dialog__headerbtn:hover .my-dialog__close::before {
background: #1890ff;
}
.my-dialog__headerbtn .my-dialog__close::after {
content: '';
position: absolute;
height: 12px;
width: 1.5px;
background: #909399;
top: calc(50% - 6px);
left: calc(50% - 0.75px);
border-radius: 2px;
}
.my-dialog__headerbtn:hover .my-dialog__close::after {
background: #1890ff;
}
.my-dialog__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
word-break: break-all;
}
.my-dialog__footer {
padding: 20px;
padding-top: 10px;
text-align: right;
box-sizing: border-box;
}
.my-dialog .my-dialog__footer {
padding: 0px 16px 24px 16px;
margin-top: 40px;
}
#ngList {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
max-height: 480px;
overflow-y: scroll;
}
${cssByCls(BlockButtonClass)} {
cursor: pointer;
height: 12px;
width: 12px;
margin-left: 1px;
float: inherit;
background: white;
border-width: 0;
padding: 0;
line-height:0px;
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
transformOrigin: '50% bottom;
}
${cssByCls(BlockButtonClass)}:hover {
transform: translateY(0) scale(1.5);
}
.close-icon {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
position: relative;
transform: rotateZ(45deg);
margin-left: 8px;
cursor: pointer;
}
.close-icon:hover {
background: #409eff;
}
.close-icon::before {
content: '';
position: absolute;
width: 8px;
height: 2px;
background: #409eff;
top: calc(50% - 1px);
left: calc(50% - 4px);
border-radius: 2px;
}
.close-icon:hover::before {
background: #fff;
}
.close-icon::after {
content: '';
position: absolute;
height: 8px;
width: 2px;
background: #409eff;
top: calc(50% - 4px);
left: calc(50% - 1px);
border-radius: 2px;
}
.close-icon:hover::after {
background: #fff;
}
.ng_item {
background-color: #ecf5ff;
display: inline-flex;
align-items: center;
padding: 0 10px;
font-size: 12px;
color: #409eff;
border: 1px solid #d9ecff;
border-radius: 4px;
box-sizing: border-box;
white-space: nowrap;
height: 28px;
line-height: 26px;
margin-left: 12px;
margin-top: 8px;
}
.input_container {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.el-input {
position: relative;
font-size: 14px;
display: inline-block;
width: 100%;
}
.el-input__inner {
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: inherit;
height: 40px;
line-height: 40px;
outline: none;
padding: 0 15px;
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
width: 100%;
cursor: pointer;
font-family: inherit;
}
.el-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
transition: .1s;
font-weight: 500;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.el-button:focus,
.el-button:hover {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
.el-button:active {
color: #3a8ee6;
border-color: #3a8ee6;
outline: none;
}
.input_container .el-input {
margin-right: 12px;
}
.tips {
margin-top: 24px;
font-size: 12px;
color: #F56C6C;
}
`;
/* 按钮模板 */
const btnTemp = `
<span class="Configs_alink_2Yg6L" yawf-component-tag="woo-box">
<div class="woo-box-flex woo-box-alignCenter woo-pop-item-main" role="button" tabindex="0" data-focus-visible="true" yawf-component-tag="woo-pop-item woo-box">
屏蔽词设置
</div>
</span>
`;
const svgIcon = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<line x1="4" y1="4" x2="20" y2="20" />
</svg>
`;
/* 添加样式 */
function addStyle(cssStyle) {
if (!cssStyle) return;
const head = document.querySelector('head');
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = cssStyle;
head.appendChild(style);
}
const defaultNameFn = elem => elem.innerText || elem.getAttribute('aria-label');
function createButtonElement(name) {
// eslint-disable-next-line
name = name.trim().replace(/^@/, '').replace(':', '');
const wrapper = document.createElement('div');
wrapper.innerHTML = svgIcon;
wrapper.className = BlockButtonClass;
wrapper.setAttribute('user-name', name);
return wrapper;
}
function newListAndUser(list, user) {
return {
listSelector: list,
userSelector: user
};
}
function injectButton({
listSelector,
userSelector,
elementButtonFunc,
doc = document,
subListSelector,
subUserSelector,
nameFn = defaultNameFn
}) {
const list = doc.querySelectorAll(listSelector);
list.forEach(element => {
if (subListSelector && subUserSelector) {
injectButton({
listSelector: subListSelector,
userSelector: subUserSelector,
doc: element
});
}
if (elementButtonFunc) elementButtonFunc(element);
let user = element.querySelector(userSelector);
if (!user) {
return;
}
if (element.querySelector(`${cssByCls(BlockButtonClass)}`)) {
return;
}
const btn = createButtonElement(nameFn(user));
while (user.parentNode) {
if (user.parentNode.childElementCount > 1) {
user.parentNode.appendChild(btn);
break;
}
user = user.parentNode;
}
});
}
var _filters = /*#__PURE__*/new WeakMap();
var _proxyOpt = /*#__PURE__*/new WeakMap();
var _registered = /*#__PURE__*/new WeakMap();
class WebsiteFilter {
/**
* @param {Array.<{listSelector: String, userSelector:String}>} selectors
* @param {Document | Element} root
* @param nameFn
*/
removeElements(selectors, root = document, nameFn = defaultNameFn) {
if (selectors.length < 1) {
return;
}
const selector = selectors[0];
const list = root.querySelectorAll(selector.listSelector);
list.forEach(element => {
const user = element.querySelector(selector.userSelector);
if (!user) {
return;
}
const poster = nameFn(user);
if (this.inBlackList(poster)) {
element.parentNode.removeChild(element);
} else {
this.removeElements(selectors.slice(1), element);
}
});
}
inBlackList(name) {
return this.store.hasUser(name);
}
/**
*
* @param {DomainStore} store
*/
constructor(store) {
_classPrivateFieldInitSpec(this, _filters, []);
_classPrivateFieldInitSpec(this, _proxyOpt, {});
_classPrivateFieldInitSpec(this, _registered, false);
/**
* @type DomainStore
*/
_defineProperty(this, "store", void 0);
this.store = store;
this.addHook(...Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(name => Object.getPrototypeOf(this)[name].hookMeta).reduce((hooks, name) => {
const fn = this[name].bind(this);
fn.hookMeta = this[name].hookMeta;
hooks.push(fn);
return hooks;
}, []));
_classPrivateFieldSet(_filters, this, Object.getOwnPropertyNames(Object.getPrototypeOf(this)).reduce((fns, name) => {
const method = this[name];
if (method.filterMeta) {
const fn = method.bind(this);
fn.hookMeta = this[name].hookMeta;
fns.push(fn);
}
return fns;
}, []));
}
addHook(...hooks) {
_classPrivateFieldSet(_proxyOpt, this, {
..._classPrivateFieldGet(_proxyOpt, this),
onError: (err, handler) => {
console.log(err);
handler.next(err);
},
onResponse: this.handleResponseFunc(...hooks)
});
}
handleResponseFunc(...hooks) {
return (response, handler) => {
const {
url
} = response.config;
if (typeof url !== 'string') {
handler.next(response);
return;
}
for (const hooker of hooks) {
const pattern = hooker.hookMeta;
if (url.includes(pattern)) {
let res = response.response;
if (res) {
try {
res = JSON.parse(res);
const ret = hooker(url, res);
res = ret || res;
response.response = JSON.stringify(res);
} catch (err) {
/* empty */
}
}
break;
}
}
handler.next(response);
};
}
withProxyOpt(o) {
_classPrivateFieldSet(_proxyOpt, this, {
..._classPrivateFieldGet(_proxyOpt, this),
...o
});
}
render() {
if (!_classPrivateFieldGet(_registered, this)) {
const opt = _classPrivateFieldGet(_proxyOpt, this);
ah.proxy(opt, unsafeWindow);
_classPrivateFieldSet(_registered, this, true);
}
_classPrivateFieldGet(_filters, this).forEach(f => {
f();
});
}
}
function filterFunc(target, name, descriptor) {
const fn = descriptor.value;
fn.filterMeta = true;
return descriptor;
}
function hookFunc(pattern) {
return (target, name, descriptor) => {
descriptor.value.hookMeta = pattern;
return descriptor;
};
}
let WeiboFilter = (_dec = hookFunc('/friendstimeline'), _dec2 = hookFunc('/searchBand'), _dec3 = hookFunc('/buildComments'), (_class = (_apiBlackList = /*#__PURE__*/new WeakMap(), (_WeiboFilter = class WeiboFilter extends WebsiteFilter {
// 接口黑名单
constructor(store) {
super(store);
_classPrivateFieldInitSpec(this, _apiBlackList, ['/female_version.mp3', '/intake/v2/rum/events']);
this.withProxyOpt({
onRequest: (config, handler) => {
if (!_classPrivateFieldGet(_apiBlackList, this).some(item => config.url.toString().includes(item))) {
// 不在接口黑名单里的请求才放行
handler.next(config);
}
}
});
this.hideTrends();
}
async filterSearchResults() {
const selector = {
cards: 'div.card-wrap',
cardUser: 'div.info a.name',
retweetedCards: 'div.card-wrap div.card-comment',
retweetedCardUser: 'div.con a.name',
comments: 'div.card-review',
commentUser: 'div.content a.name'
};
injectButton({
listSelector: selector.cards,
userSelector: selector.cardUser,
elementButtonFunc: WeiboFilter.createCommentButton
});
injectButton({
listSelector: selector.retweetedCards,
userSelector: selector.retweetedCardUser
});
this.removeElements([{
listSelector: selector.cards,
userSelector: selector.cardUser
}, {
listSelector: selector.comments,
userSelector: selector.commentUser
}]);
this.removeElements([{
listSelector: selector.cards,
userSelector: selector.retweetedCardUser
}]);
}
async filterFeeds() {
const selector = {
cardListSelector: 'div.vue-recycle-scroller__item-view',
cardUserSelector: 'div.Feed_body_3R0rO a.ALink_default_2ibt1',
commentListSelector: 'div.wbpro-list',
commentUserSelector: 'div.con1.woo-box-item-flex div.text a.ALink_default_2ibt1',
replyListSelector: 'div.item2',
replyUserSelector: 'div.con2 a.ALink_default_2ibt1'
};
this.removeElements([newListAndUser(selector.cardListSelector, selector.cardUserSelector), newListAndUser(selector.commentListSelector, selector.commentUserSelector), newListAndUser(selector.replyListSelector, selector.replyUserSelector)]);
injectButton({
listSelector: selector.cardListSelector,
userSelector: selector.cardUserSelector
});
const feedWrappers = document.querySelectorAll(selector.cardListSelector);
feedWrappers.forEach(feed => {
injectButton({
listSelector: selector.commentListSelector,
userSelector: selector.commentUserSelector,
doc: feed,
subListSelector: selector.replyListSelector,
subUserSelector: selector.replyUserSelector
});
});
}
async filterReplies() {
const reply = document.querySelector('div.ReplyModal_scroll3_2kADQ');
if (!reply) {
return;
}
const selector = {
commentListSelector: 'div.wbpro-list',
commentUserSelector: 'div.con1.woo-box-item-flex a.ALink_default_2ibt1',
root: reply,
replyListSelector: 'div.vue-recycle-scroller__item-view',
replyUserSelector: 'div.con2 a.ALink_default_2ibt1'
};
this.removeElements([{
listSelector: selector.commentListSelector,
userSelector: selector.commentUserSelector
}, {
listSelector: selector.replyListSelector,
userSelector: selector.replyUserSelector
}], reply);
injectButton({
listSelector: selector.commentListSelector,
userSelector: selector.commentUserSelector,
doc: reply,
subListSelector: selector.replyListSelector,
subUserSelector: selector.replyUserSelector
});
}
static async createCommentButton(card) {
injectButton({
listSelector: 'div.card-together div.list div.card-review',
userSelector: 'a.name',
doc: card
});
}
async filterDetailComments() {
const selector = {
commentListSelector: 'div.vue-recycle-scroller__item-view',
commentUserSelector: 'div.con1.woo-box-item-flex div.text a.ALink_default_2ibt1',
replyListSelector: 'div.item2',
replyUserSelector: 'div.con2 a.ALink_default_2ibt1'
};
this.removeElements([{
listSelector: selector.commentListSelector,
userSelector: selector.commentUserSelector
}, {
listSelector: selector.replyListSelector,
userSelector: selector.replyUserSelector
}]);
injectButton({
listSelector: selector.commentListSelector,
userSelector: selector.commentUserSelector,
subListSelector: selector.replyListSelector,
subUserSelector: selector.replyUserSelector
});
}
async hideTrends() {
let trend = document.querySelector('div.main-side');
if (trend) trend.style.display = 'none';
if (trend = document.querySelector('div.Main_side_i7Vti')) trend.style.display = 'none';
}
async createRetweetButton() {
this.removeElements([newListAndUser('div.vue-recycle-scroller__item-view', 'div.retweet.Feed_retweet_JqZJb a.ALink_default_2ibt1')]);
injectButton({
listSelector: 'div.retweet.Feed_retweet_JqZJb',
userSelector: 'a.ALink_default_2ibt1'
});
}
/**
*@param {Array.<{user: {screen_name: string}, text: string}>} comments
* */
filterComments(comments) {
return comments.reduce((filtered, comment) => {
var _comment$user;
const myText = comment.text || '';
const ngWordInMyText = this.inBlackList(myText) || ((_comment$user = comment.user) === null || _comment$user === void 0 ? void 0 : _comment$user.screen_name) && this.inBlackList(comment.user.screen_name);
if (!ngWordInMyText) {
filtered.push(comment);
}
return filtered;
}, []);
}
/**
* @typedef Status
* @type {object}
* @property {?User} user
* @property {string} text
* @property {?Status} retweeted_status
*
* @typedef User
* @type {object}
* @property {boolean} following
* @property {string} screen_name
*
*
* @param {Status[]} statuses
* */
filterStatuses(statuses) {
return statuses.reduce((acc, cur) => {
if (cur.user.following) {
var _cur$user;
const myText = cur.text || '';
const ngWordInMyText = this.inBlackList(myText) || ((_cur$user = cur.user) === null || _cur$user === void 0 ? void 0 : _cur$user.screen_name) && this.inBlackList(cur.user.screen_name);
if (!ngWordInMyText) {
if (cur.retweeted_status) {
var _cur$retweeted_status;
const oriText = cur.retweeted_status.text || '';
const ngWordInOriText = this.inBlackList(oriText) || ((_cur$retweeted_status = cur.retweeted_status) === null || _cur$retweeted_status === void 0 || (_cur$retweeted_status = _cur$retweeted_status.user) === null || _cur$retweeted_status === void 0 ? void 0 : _cur$retweeted_status.screen_name) && this.inBlackList(cur.retweeted_status.user.screen_name);
if (ngWordInOriText) return acc;
}
acc.push(cur);
}
}
return acc;
}, []);
}
filterSearchBand(searchBands) {
return searchBands.reduce((acc, cur) => {
if (!this.inBlackList(cur.word)) {
acc.push(cur);
}
return acc;
}, []);
}
onFriendTimeline(url, res) {
if (url.includes('m.weibo.cn')) {
res.data.statuses = this.filterStatuses(res.data.statuses);
} else {
res.statuses = this.filterStatuses(res.statuses);
console.log(`filtered url: ${url}, users: ${res.statuses.reduce((pre, cur) => {
pre.push(cur.user.screen_name);
return pre;
}, [])}`);
}
}
onSearchBand(url, res) {
res.data.realtime = this.filterSearchBand(res.data.realtime);
}
hook_buildComments(url, res) {
res.data = this.filterComments(res.data);
}
}, _defineProperty(_WeiboFilter, "host", 'weibo.com'), _WeiboFilter)), (_applyDecoratedDescriptor(_class.prototype, "filterSearchResults", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterSearchResults"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterFeeds", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterFeeds"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterReplies", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterReplies"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterDetailComments", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterDetailComments"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hideTrends", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "hideTrends"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "createRetweetButton", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "createRetweetButton"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onFriendTimeline", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "onFriendTimeline"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onSearchBand", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "onSearchBand"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hook_buildComments", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "hook_buildComments"), _class.prototype)), _class));
let HupuFilter = (_dec4 = hookFunc('/bbs-reply-detail'), (_class2 = (_HupuFilter = class HupuFilter extends WebsiteFilter {
async filterHupuComments() {
const selector = {
commentsSelector: 'div.index_discuss-card__Nd4MK.hp-m-discuss-card.post-card',
userSelector: 'p.discuss-card__user span.discuss-card__username',
repliesSelector: 'div.index_discuss-card__Nd4MK.hp-m-discuss-card.discuss-card',
quotesSelector: 'div.discuss-card__quote-container-quote',
quoteUserSelector: 'div.discuss-card__quote-container-quote span.discuss-card__quote-container-discusser'
};
this.removeElements([{
listSelector: selector.commentsSelector,
userSelector: selector.userSelector
}]);
injectButton({
listSelector: selector.commentsSelector,
userSelector: selector.userSelector
});
this.removeElements([newListAndUser(selector.commentsSelector, selector.quoteUserSelector)]);
injectButton({
listSelector: selector.quotesSelector,
userSelector: selector.quoteUserSelector
});
this.removeElements([newListAndUser(selector.repliesSelector, selector.userSelector)]);
injectButton({
listSelector: selector.repliesSelector,
userSelector: selector.userSelector
});
}
hupuRouter(url, res) {
function filterHupuReplies(replies) {
return replies.reduce((filtered, reply) => {
const user = reply.user.username;
if (!this.inBlackList(user)) {
filtered.push(reply);
}
return filtered;
}, []);
}
res.data.replies = filterHupuReplies(res.data.replies);
}
}, _defineProperty(_HupuFilter, "host", 'hupu.com'), _HupuFilter), (_applyDecoratedDescriptor(_class2.prototype, "filterHupuComments", [filterFunc], Object.getOwnPropertyDescriptor(_class2.prototype, "filterHupuComments"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "hupuRouter", [_dec4], Object.getOwnPropertyDescriptor(_class2.prototype, "hupuRouter"), _class2.prototype)), _class2));
let TiebaFilter = (_dec5 = hookFunc('/p/totalComment'), _dec6 = hookFunc('/topicList'), _dec7 = hookFunc('/suggestion'), (_class3 = (_TiebaFilter = class TiebaFilter extends WebsiteFilter {
constructor(store) {
super(store);
this.hideLoginPopup();
}
async hideLoginPopup() {
let login = null;
while (!login) {
login = document.querySelector('div.tieba-custom-pass-login');
if (login) {
const ob = new MutationObserver(mutations => {
const record = mutations[0];
if (record.target.style.display === 'block') {
login.style.display = 'none';
}
});
ob.observe(login, {
attributeFilter: ['style']
});
break;
}
await sleep(10);
}
}
async filterTiebaThreadListComments() {
const selector = {
threadsSelector: 'li.j_thread_list.clearfix.thread_item_box',
threadUserSelector: 'span.tb_icon_author'
};
const fn = user => {
var _user$getAttribute;
return (_user$getAttribute = user.getAttribute('title')) === null || _user$getAttribute === void 0 ? void 0 : _user$getAttribute.replace('主题作者: ', '');
};
this.removeElements([newListAndUser(selector.threadsSelector, selector.threadUserSelector)], document, fn);
injectButton({
listSelector: selector.threadsSelector,
userSelector: selector.threadUserSelector,
nameFn: fn
});
}
async filterTiebaThreadComments() {
const selector = {
commentsSelector: 'div.l_post.l_post_bright.j_l_post.clearfix',
commentUserSelector: 'div.d_author li.d_name a.p_author_name.j_user_card',
repliesSelector: 'div.j_lzl_c_b_a.core_reply_content li.lzl_single_post.j_lzl_s_p',
replyUserSelector: 'div.lzl_cnt a.at.j_user_card'
};
this.removeElements([newListAndUser(selector.commentsSelector, selector.commentUserSelector), newListAndUser(selector.repliesSelector, selector.replyUserSelector)]);
injectButton({
listSelector: selector.commentsSelector,
userSelector: selector.commentUserSelector,
subListSelector: selector.repliesSelector,
subUserSelector: selector.replyUserSelector
});
}
async hideRightBar() {
const rightBar = document.querySelector('div.right_section.right_bright');
if (!rightBar) {
return;
}
rightBar.parentNode.removeChild(rightBar);
}
hookReplies(url, res) {
res.data = filterTiebaReplies(res.data);
}
/**
* @typedef Topic
* @type {object}
* @property {string[]} topic_list
*
* @param {string} url
* @param {object} res
* @param {{user_his_topic: Topic, sug_topic: Topic, bang_topic: Topic, manual_topic: Topic}} res.data
*
* */
hookTopicList(url, res) {
res.data.user_his_topic.topic_list = [];
res.data.sug_topic.topic_list = [];
res.data.bang_topic.topic_list = [];
res.data.manual_topic.topic_list = [];
}
/**
* @param {string} url
* @param {{hottopic_list: object}} res
* */
hookSuggestion(url, res) {
res.hottopic_list.search_data = [];
}
}, _defineProperty(_TiebaFilter, "host", 'tieba.baidu.com'), _TiebaFilter), (_applyDecoratedDescriptor(_class3.prototype, "hideLoginPopup", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "hideLoginPopup"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "filterTiebaThreadListComments", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "filterTiebaThreadListComments"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "filterTiebaThreadComments", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "filterTiebaThreadComments"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hideRightBar", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "hideRightBar"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookReplies", [_dec5], Object.getOwnPropertyDescriptor(_class3.prototype, "hookReplies"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookTopicList", [_dec6], Object.getOwnPropertyDescriptor(_class3.prototype, "hookTopicList"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookSuggestion", [_dec7], Object.getOwnPropertyDescriptor(_class3.prototype, "hookSuggestion"), _class3.prototype)), _class3));
/**
* @param {Object} data
* @property {{user_nickname_v2: string}[]} user_list
* @property {{comment_info: Object[]}[]} comment_list
* @property {{show_nickname: string}} comment_info
* */
function filterTiebaReplies(data) {
const userMap = new Map();
for (const user of Object.values(data.user_list)) {
userMap.set(user.user_nickname_v2, true);
}
const comments = data.comment_list;
data.comment_list = Object.entries(comments).reduce((filtered, [postId, comment]) => {
let replies = comment.comment_info;
replies = replies.filter(reply => !this.inBlackList(reply.show_nickname));
comment.comment_list_num = replies.length;
comment.comment_info = replies;
if (comment.comment_list_num > 0) {
filtered = {
...filtered,
[postId]: comment
};
}
return filtered;
}, {});
return data;
}
var _blackUsers = /*#__PURE__*/new WeakMap();
var _DomainStore_brand = /*#__PURE__*/new WeakSet();
class DomainStore {
static async init() {
let list = await this.loadBlackList();
return new DomainStore(list);
}
/**
* @type {Map.<string, boolean>}
* */
get userList() {
return _classPrivateFieldGet(_blackUsers, this).keys();
}
hasUser(name) {
return _classPrivateFieldGet(_blackUsers, this).has(name);
}
addUser(name) {
_classPrivateFieldGet(_blackUsers, this).set(name, true);
_assertClassBrand(_DomainStore_brand, this, _flush).call(this);
}
removeUser(name) {
_classPrivateFieldGet(_blackUsers, this).delete(name);
_assertClassBrand(_DomainStore_brand, this, _flush).call(this);
}
/**
* @param {string[]} list
* */
constructor(list) {
_classPrivateMethodInitSpec(this, _DomainStore_brand);
_classPrivateFieldInitSpec(this, _blackUsers, void 0);
_classPrivateFieldSet(_blackUsers, this, list.reduce((map, user) => {
return map.set(user, true);
}, new Map()));
}
// 获取屏蔽词列表
static async loadBlackList() {
let value = await GM.getValue(DomainStore.domainKeyPrefix);
if (!value) return [];
const ret = JSON.parse(String(value));
console.log(`gm value: ${ret}`);
return ret;
}
}
_DomainStore = DomainStore;
async function _flush() {
await GM.setValue(_DomainStore.domainKeyPrefix, JSON.stringify(_classPrivateFieldGet(_blackUsers, this).keys()));
}
_defineProperty(DomainStore, "NgListKey", 'NgList');
(() => {
const domain = document.location.host;
let segs = domain.split('.');
if (segs.length > 2) segs = segs.slice(1);
_DomainStore.domainKeyPrefix = `${segs.join('.')}:${_DomainStore.NgListKey}`;
})();
var _store = /*#__PURE__*/new WeakMap();
var _filter = /*#__PURE__*/new WeakMap();
class MainView {
/**
*
* @param {DomainStore} store
* @param {WebsiteFilter} filter
*/
constructor({
store,
filter
}) {
/**
* @member {DomainStore} #store
* */
_classPrivateFieldInitSpec(this, _store, void 0);
/**
* @member {WebsiteFilter} #filter
*/
_classPrivateFieldInitSpec(this, _filter, void 0);
_classPrivateFieldSet(_store, this, store);
_classPrivateFieldSet(_filter, this, filter);
addStyle(css); // 添加样式
window.addEventListener('load', () => {
// 屏蔽视频播放后的弱智三连语音
appObserverInit();
});
}
dialogElement() {
return document.querySelector(MainView.DialogSelector);
}
render() {
setInterval(() => {
this.renderSettingButton();
this.renderSettingPanel();
_classPrivateFieldGet(_filter, this).render();
this.renderBlockButtons();
}, 1000);
}
async renderBlockButtons() {
document.querySelectorAll(`${cssByCls(BlockButtonClass)}`).forEach(button => {
// 点击按钮展示弹窗
button.addEventListener('click', ev => {
ev.stopPropagation();
let name = button.getAttribute('user-name');
_classPrivateFieldGet(_store, this).addUser(name);
});
});
}
/* 生成添加屏蔽关键词的按钮 */
async renderSettingButton() {
if (!document.body) {
return;
}
if (document.body.querySelector('#add_ngList_btn')) {
return;
}
const btn = document.createElement('div');
btn.title = '添加屏蔽关键词';
const span = document.createElement('span');
span.innerText = '';
btn.appendChild(span);
btn.id = 'add_ngList_btn';
document.body.appendChild(btn);
// 点击按钮展示弹窗
btn.addEventListener('click', () => {
this.renderBlockedUsers();
this.showDialog();
});
}
async renderSettingPanel() {
/* dialog模板 */
const dialogTemplate = `
<div class="my-dialog" style="margin-top: 15vh; width: 40%;">
<div class="my-dialog__header">
<span class="my-dialog__title">屏蔽词列表</span>
<button type="button" aria-label="Close" class="my-dialog__headerbtn">
<i class="my-dialog__close"></i>
</button>
</div>
<div class="my-dialog__body">
<div class="input_container">
<div class="el-input">
<input id="ngWord_input" class="el-input__inner" type="text" />
</div>
<button type="button" class="el-button" id="add_btn">
<span>添加</span>
</button>
</div>
<div id="ngList"></div>
<p class="tips">注:1. 可过滤包含屏蔽词的用户、微博、评论、热搜。 2. 关键词保存在本地的local storage中。 3. 更改关键词后刷新页面生效(不刷新页面的情况下,只有之后加载的微博才会生效)。</p>
</div>
<div class="my-dialog__footer"></div>
</div>
`;
if (!document.body) {
return;
}
if (document.body.querySelector('.my-dialog__wrapper')) {
return;
}
const wrapper = document.createElement('div');
wrapper.classList.add('my-dialog__wrapper');
wrapper.innerHTML = dialogTemplate;
document.body.appendChild(wrapper);
/* 初始化事件 */
document.querySelector('.my-dialog__headerbtn').addEventListener('click', () => {
// 关闭按钮点击事件
this.hideDialog();
});
document.querySelector('#add_btn').addEventListener('click', () => {
// 添加关键词按钮点击事件
const ngWord_input = document.querySelector('#ngWord_input');
if (ngWord_input && ngWord_input.value) {
_classPrivateFieldGet(_store, this).addUser(ngWord_input.value);
ngWord_input.value = '';
this.renderBlockedUsers();
}
});
}
showDialog() {
this.dialogElement().style.display = 'initial';
}
hideDialog() {
this.dialogElement().style.display = 'none';
}
renderBlockedUsers() {
let blockedUsersHTML = '';
const users = [..._classPrivateFieldGet(_store, this).userList];
for (const [i, item] of users.entries()) {
blockedUsersHTML += `<span class="ng_item">${item}<i class="close-icon" data-index=${i}></i></span>`;
}
const ngListNode = document.querySelector('#ngList');
if (ngListNode) {
ngListNode.innerHTML = blockedUsersHTML;
const buttons = ngListNode.querySelectorAll('.close-icon');
for (const button of buttons) {
button.addEventListener('click', () => {
const name = button.textContent;
_classPrivateFieldGet(_store, this).removeUser(name);
this.renderBlockedUsers();
});
}
}
}
}
// 创建观察器
_defineProperty(MainView, "DialogSelector", '.my-dialog__wrapper');
function appObserverInit() {
const targetNode = document.getElementById('app');
if (!targetNode) {
return;
}
// 观察器的配置(需要观察什么变动)
const config = {
childList: true,
subtree: true
};
// 当观察到变动时执行的回调函数
const callback = function () {
const audioList = document.querySelectorAll('.AfterPatch_bg_34rqc');
for (const audio of audioList) {
audio.remove();
console.log('移除了弱智三连');
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const store = await DomainStore.init();
let filter = null;
const {
host
} = document.location;
filter = host.includes(WeiboFilter.host) ? new WeiboFilter(store) : host.includes(HupuFilter.host) ? new HupuFilter(store) : host.includes(TiebaFilter.host) ? new TiebaFilter(store) : null;
const controller = new MainView({
store,
filter
});
controller.render();
})();