/*
* @title H::H lightni.ng filter feat. Proxy
* @description Hatena Haiku spam filtering w/ lightni.ng feat. Proxy
* @include *://h.hatena.ne.jp/*
* @contributor noromanba http://let.hatelabo.jp/noromanba/let/hJmc6brdnsgL
* @contributor austinburk http://let.hatelabo.jp/austinburk/let/hLHU6PPgg9Ya
* @license The MIT License https://opensource.org/licenses/MIT
* @javascript_url
*/
/*
ref.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
http://h.hatena.ne.jp/js/updu/ten-extras.js
*/
(() => {
const d_ = document;
const g = {
THRESHOLD: 5,
blacklist: {},
no_more_entry: false,
me: Hatena.Visitor.name,
};
function is_spam(entry) {
const poster = entry.querySelector(".info > .username > a");
const id = poster.title.slice("id:".length);
const is_spam = id != g.me && g.blacklist.has(id);
if (is_spam) {
const url = entry.querySelector(".info > .timestamp > a").href;
console.warn(`${url} is SPAM (id: ${id}, score: ${g.blacklist.get(id)})`);
}
return is_spam;
}
// see TL.DataSource.TimelinePage.prototype.loadData
TL.compat.getPage = new Proxy(TL.compat.getPage, {
apply: (target, thisArg, args) => {
const url = args[0];
// maybe pager-older's request
if (/reftime=-/.test(url)) {
let onload = args[1];
onload = new Proxy(onload, {
apply: (target, thisArg, args) => {
const xhr = args[0];
// no more entry ?
if (xhr.status < 400 && xhr.responseText.trim().length == 0) {
g.no_more_entry = true;
}
return Reflect.apply(target, thisArg, args);
},
});
args[1] = onload;
}
return Reflect.apply(target, thisArg, args);
},
});
// filtering EntryList
TL.EntryList.prototype.appendEntries = new Proxy(TL.EntryList.prototype.appendEntries, {
apply: (target, thisArg, args) => {
// filter SPAM
const list = args[0].filter(e => ! is_spam(e.element));
// if list is empty, Event "entriesappended" is not fired
if (! list.length) {
var ev = new TL.Event('entriesappended');
ev.newEntries = list;
thisArg.dispatchEvent(ev);
}
args[0] = list;
return Reflect.apply(target, thisArg, args);
},
});
function do_pagerize() {
if (g.no_more_entry) {
return;
}
const pager = d_.querySelector("div.pager a.pager-older");
const y1 = pager.getBoundingClientRect().top;
const y2 = window.innerHeight;
if (y1 < y2) {
pager.click();
}
}
// do pagerize if pager is in view after remove entries
TL.View.prototype.stopNextIndicator = new Proxy(TL.View.prototype.stopNextIndicator, {
apply: (target, thisArg, args) => {
do_pagerize();
return Reflect.apply(target, thisArg, args);
},
});
(async () => {
// load scores
const resp = await fetch("https://haikuantispam.lightni.ng/api/recent_scores.json", {
mode: "cors",
credentials: "same-origin",
});
const scores = await resp.json();
g.blacklist = new Map(Object.entries(scores)
.filter(([, score]) => score >= g.THRESHOLD)
);
// filter SPAM already displayed
d_.querySelectorAll(".tl-entry-list .tl-entry").forEach(entry => {
if (is_spam(entry)) {
entry.style.display = "none";
}
});
do_pagerize();
})();
})();