BG6UFA Regarding blocking users, I am currently using this Tampermonkey script to filter them out; it's a simple way to keep things clean.

These locations can all be operated.



// ==UserScript==
// @name HamCQ User Blocking
// @namespace https://flarum.blocker.HamCQ
// @version 1.0
// @description HamCQ Blocking + Keyword Filtering
// @match https://forum.hamcq.cn/*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.registerMenuCommand
// @run-at document-end
// ==/UserScript==
(() => {> {
'use strict';
const KEY_U = 'fb_users_v4';
const KEY_W = 'fb_words_v4';
let users = new Set();
let words = new Set();
async function load() {
users = new Set(JSON.parse(await GM.getValue(KEY_U, '[]')).map(x => x));
words = new Set(JSON.parse(await GM.getValue(KEY_W, '[]')).map(x => x));
}
async function save() {
await GM.setValue(KEY_U, Array.from(users).sort());
await GM.setValue(KEY_W, Array.from(words).sort());
}
// Example usage:
// load();
// users.add('user1');
// users.add('user2');
// words.add('word1');
// words.add('word2');
// save();
}> x.toLowerCase()));
words = new Set(JSON.parse(await GM.getValue(KEY_W, '[]')).map(x =>> x.toLowerCase()));
}
async function save() {
await GM.setValue(KEY_U, JSON.stringify([...users]));
await GM.setValue(KEY_W, JSON.stringify([...words]));
}
function getUser(el) {
if (!el) return '';
return (el.getAttribute?.('data-username') || el.textContent || '').trim().toLowerCase();
}
function hasWord(text) {
if (!text) return false;
text = text.toLowerCase();
return [...words].some(w =>> w && if (text.includes(w)) {
}
function filterAll() {
const nodes = document.querySelectorAll(`
.DiscussionListItem,
.Post,
.PostStream-item,
.UserCard,
.PostUser,
article
`);
nodes.forEach(el => {
let userEl =
el.querySelector('[data-username]') ||
el.querySelector('.username a') ||
el.querySelector('.username') ||
el.querySelector('a[href*="/u/"], a[href*="/user/"]');
const name = getUser(userEl);
const text = (el.innerText || '').toLowerCase();
if (users.has(name) || hasWord(text)) {
el.style.display = 'none';
el.dataset.fbHidden = '1';
}
});
}
function injectDropdown() {
document.querySelectorAll('.Dropdown-menu').forEach(menu =>> {
if (menu.dataset.fbInjected) return;
const card =
menu.closest('.UserCard') ||
menu.closest('.PostUser') ||
menu.closest('.Post');
if (!card) return;
const userEl =
card.querySelector('[data-username]') ||
card.querySelector('.username a') ||
card.querySelector('.username');
const name = getUser(userEl);
if (!name) return;
const li = document.createElement('li');
li.className = 'Dropdown-menuItem item-block-user';
li.innerHTML = `
<button type="button" class="Dropdown-item">
<I class="icon fas fa-ban" style="margin-right:8px;"></i>
Block a user
</button>
`;
const btn = li.querySelector('button');
btn.onclick = async (e) => {> ```javascript
{
e.preventDefault();
e.stopPropagation();
users.add(name);
await save();
filterAll();
toast('Blacklisted ' + name);
};
menu.appendChild(li);
menu.dataset.fbInjected = '1';
});
}
function toast(msg) {
const el = document.createElement('div');
el.textContent = msg;
el.style.cssText = `
position:fixed;
top:20px;
right:20px;
background:rgba(0,0,0,.85);
color:#fff;
padding:8px 12px;
border-radius:6px;
z-index:999999;
font-size:12px;
`;
document.body.appendChild(el);
setTimeout(() => {
el.remove();
}, 3000);
}
```> el.remove(), 1500);
}
function openPanel() {
const old = document.getElementById('fb-panel');
if (old) old.remove();
const overlay = document.createElement('div');
overlay.id = 'fb-panel';
overlay.style.cssText = `
position:fixed;
inset:0;
background:rgba(0,0,0,.4);
z-index:999999;
`;
const box = document.createElement('div');
box.style.cssText = `
position:absolute;
top:60px;
right:12px;
width:320px;
background:var(--body-bg);
color:var(--text-color);
border-radius:10px;
padding:12px;
box-shadow:0 10px 30px rgba(0,0,0,.2);
`;
box.innerHTML = `
<div style="font-weight:600;margin-bottom:10px;">>Filtering management<</div>>
<div style="font-size:12px;opacity:.7;">Users (${users.size})<</div>>
<input id="u" style="width:100%; padding: 6px;">
<button id="ua" style="width:100%; margin-top: 6px;">Add user</button>
<div id="ulist" style="margin-top:8px;"><</div>>
<hr>
<div style="font-size:12px;opacity:.7;">Keywords (${words.size})<</div>>
<input id="w" style="width:100%; padding: 6px;">
<button id="wa" style="width:100%; margin-top: 6px;">Add keywords</button>
<div id="wlist" style="margin-top:8px;">><</div>>
<button id="close" style="width:100%; margin-top: 10px;">Close</button>
`;
overlay.appendChild(box);
document.body.appendChild(overlay);
overlay.onclick = (e) => {> {
if (e.target === overlay) overlay.remove();
};
box.onclick = (e) => {> ```javascript
this.stopPropagation();
const ulist = box.querySelector('#ulist');
const wlist = box.querySelector('#wlist');
function render() {
ulist.innerHTML = [...users].map(u => /* ... */);
}
```>
`<`<div>스타일="디스플레이:플렉스; 정렬-콘텐츠:스페이스-비tween; 마진:4px 0;"`>
<span>{u}</span>
<button data-u="`${u}`">Delete</button>
<</div>>```javascript
).join('');
wlist.innerHTML = [...words].map(w => w);
```>
`<`<div>스타일="디스플레이:플렉스;정렬-콘텐츠:스페이스-비트윈;마진:4px 0;"`>
<span>{w}</span>
<button data-w="${w}">Delete</button>
<</div>>```javascript
).join('');
ulist.querySelectorAll('button').forEach(b => {
```> {
b.onclick = async () => {> {
users.delete(b.dataset.u);
await save();
render();
filterAll();
};
});
wlist.querySelectorAll('button').forEach(b => {> {
b.onclick = async () => {> {
words.delete(b.dataset.w);
await save();
render();
filterAll();
};
});
}
box.querySelector('#ua').onclick = async () => {> {
const v = box.querySelector('#u').value.trim().toLowerCase();
if (!v) return;
users.add(v);
await save();
render();
filterAll();
};
box.querySelector('#wa').onclick = async () => {> {
const v = box.querySelector('#w').value.trim().toLowerCase();
if (!v) return;
words.add(v);
await save();
render();
filterAll();
};
box.querySelector('#close').onclick = () => {> overlay.remove();
render();
}
function createFloat() {
if (document.getElementById('fb-float')) return;
const btn = document.createElement('div');
btn.id = 'fb-float';
btn.innerHTML = `<I class="icon fas fa-filter"></i>`;
btn.style.cssText = `
position:fixed;
top:12px;
right:12px;
width:36px;
height:36px;
background:var(--control-bg,#f5f5f5);
border:1px solid rgba(0,0,0,.1);
border-radius:6px;
display:flex;
align-items:center;
justify-content:center;
cursor:pointer;
z-index:999999;
`;
btn.onclick = openPanel;
document.body.appendChild(btn);
}
async function init() {
await load();
createFloat();
filterAll();
injectDropdown();
const obs = new MutationObserver(() => {> {
requestAnimationFrame(() => {> {
filterAll();
injectDropdown();
});
});
obs.observe(document.body, { childList: true, subtree: true });
GM.registerMenuCommand('Filter Panel', openPanel);
}
init();
})();