Last active
September 21, 2018 17:25
-
-
Save noromanba/e485c35ffba606ae8ecacac2c9a8da3c to your computer and use it in GitHub Desktop.
Hatena Haiku spam filter for UserScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Hatena Haiku spam filter | |
// @namespace https://noromanba.github.com | |
// @description Hatena Haiku spam filter for UserScript | |
// @include *://h.hatena.ne.jp/* | |
// @exclude *://h.hatena.ne.jp/setting/* | |
// @exclude *://h.hatena.ne.jp/guide | |
// @exclude *://h.hatena.ne.jp/help/* | |
// @grant none | |
// @noframes | |
// @run-at document-end | |
// @version 2018.7.24.1 | |
// @homepage https://gist.github.com/noromanba/e485c35ffba606ae8ecacac2c9a8da3c | |
// @intallURL https://gist.github.com/noromanba/e485c35ffba606ae8ecacac2c9a8da3c/raw/hatenahaiku-spam-filter.user.js | |
// @downloadURL https://gist.github.com/noromanba/e485c35ffba606ae8ecacac2c9a8da3c/raw/hatenahaiku-spam-filter.user.js | |
// @contributor noromanba http://let.hatelabo.jp/noromanba/let/hJmc6brdnsgL | |
// @license MIT License https://nrm.mit-license.org/2018 | |
// @author noromanba https://noromanba.github.com | |
// @icon https://2.bp.blogspot.com/-U5JvHoBChxI/UbVvVisD1iI/AAAAAAAAUvU/2RDCXj5ZOpg/s0/supamusubi.png | |
// ==/UserScript== | |
// Icon (original license by irasutoya.com) | |
// https://www.irasutoya.com/2013/09/blog-post_2296.html | |
// https://www.irasutoya.com/p/terms.html | |
// https://www.irasutoya.com/p/faq.html | |
// Devel | |
// https://gist.github.com/noromanba/e485c35ffba606ae8ecacac2c9a8da3c | |
// Bookmarklet | |
// http://let.hatelabo.jp/noromanba/let/hJmc6brdnsgL | |
// nitpicking via | |
// http://let.hatelabo.jp/austinburk/let/hLHU6PPgg9Ya | |
// Proxy/Reflect ver | |
// http://let.hatelabo.jp/a-kuma3/let/hJmc7ZTR7vBO | |
// TODO | |
// - handle "Related/Hot Keywords" | |
// - http://let.hatelabo.jp/noromanba/let/hLHVltCFk5dw | |
// non i18n similar services/software by id:tukihatu c.f. | |
// Hatena Haiku a la mode (3rd party web services) | |
// http://hh-alamode.fanweb.jp | |
// http://h.hatena.ne.jp/target?word=%E3%81%AF%E3%81%A6%E3%81%AA%E3%83%8F%E3%82%A4%E3%82%AF%E3%82%A2%E3%83%A9%E3%83%A2%E3%83%BC%E3%83%89 | |
// Hatena Haiku Soldier (Chrome/ium Extensions) | |
// https://chrome.google.com/webstore/detail/hhgejafgjjjfaocaopajkhgmdgaeimin | |
// http://h.hatena.ne.jp/target?word=%E3%81%AF%E3%81%A6%E3%81%AA%E3%83%8F%E3%82%A4%E3%82%AF%E3%82%BD%E3%83%AB%E3%82%B8%E3%83%A3%E3%83%BC | |
// e.g. | |
// http://h.hatena.ne.jp | |
(async () => { | |
'use strict'; | |
if (window.self !== window.top) return; // @noframes in Vanilla | |
// timeout | |
// https://gist.github.com/noromanba/7e76cd75d15e27b102007298a8156d8f | |
// TBD Promise based functionize | |
const controller = new AbortController(); | |
const signal = controller.signal; | |
const TIMEOUT = 1000 * 10; | |
signal.onabort = () => { | |
console.error(`timeout: ${ TIMEOUT / 1000 } sec elapsed`); | |
}; | |
const timer = setTimeout(() => controller.abort(), TIMEOUT); | |
// Access-Control-Allow-Origin: http://h.hatena.ne.jp | |
const USER_FILTER = 'https://haikuantispam.lightni.ng/api/recent_scores.json'; | |
// TBD CORS proxy c.f. | |
// http://let.hatelabo.jp/noromanba/let/hJmc7rWIvsJj | |
// for avoid web-beacon/bug and fingerprinting like risks | |
// https://hacks.mozilla.org/2016/03/referrer-and-cache-control-apis-for-fetch/ | |
// https://html.spec.whatwg.org/#cors-settings-attributes | |
const request = new Request(USER_FILTER, { | |
// https://fetch.spec.whatwg.org/#request-headers | |
// https://html.spec.whatwg.org/#initialise-the-document-object | |
cache: 'no-cache', | |
credentials: 'omit', | |
mode: 'cors', | |
// Referrer-Policy fallback | |
referrer: '', | |
// [^1] Referrer-Policy | |
// https://developer.mozilla.org/en-US/docs/Web/API/Request/referrerPolicy | |
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy | |
referrerPolicy: 'no-referrer', | |
signal, | |
}); | |
class ConnectionError extends Error { | |
constructor(msg) { | |
super(msg); | |
this.name = this.constructor.name; | |
} | |
} | |
class TimeoutError extends ConnectionError { } | |
// TBD | |
// - refresh filter when infinite scrolling | |
// - small IIFE async scope | |
// - try-catch-finally w/ await | |
const blacklist = await fetch(request).then(res => { | |
if (!res.ok || res.status !== 200) { | |
// TBD Custom Error | |
return Promise.reject(new Error([ | |
res.status, res.statusText, res | |
])); | |
} | |
return res.json(); | |
}).catch(err => { | |
if (signal.aborted) { | |
return Promise.reject(new TimeoutError(err)); | |
} | |
// http://isup.me | |
const isup = 'https://downforeveryoneorjustme.com/'; | |
return Promise.reject(new ConnectionError([ | |
'check a network, location and server', | |
isup + new URL(USER_FILTER).hostname, | |
].join('\n'))); | |
}).then(json => { | |
const SPAM_THRESHOLD = 5; | |
return new Map( | |
Object.entries(json).filter(([, score]) => score >= SPAM_THRESHOLD) | |
); | |
}).catch(err => { | |
console.error([ | |
'Hatena::Haiku spam filter', | |
err, | |
//err.stack, | |
].join('\n')); | |
}).finally(() => { | |
clearTimeout(timer); | |
}); | |
console.debug(blacklist); | |
if (!blacklist || blacklist.size === 0) return; | |
// TBD show indicator/throbber when wiping | |
const wipeout = (ctx) => { | |
if (!ctx.querySelectorAll) return; | |
ctx.querySelectorAll([ | |
'.entry.tl-entry', | |
]).forEach(entry => { | |
// permalink syntax; | |
// <a href="http://h.hatena.ne.jp/{HATENA_ID}/" title="id:{HATENA_ID}">{SCREEN_NAME}</a> | |
const poster = entry.querySelector('.username a[href][title^="id:"]'); | |
if (!poster) return; | |
const id = poster.title.slice('id:'.length); | |
if (blacklist.has(id)) { | |
const USER_RECENT_STATS = 'https://haikuantispam.lightni.ng/id/'; | |
/*/ | |
entry.style.backgroundColor = 'red'; | |
entry.querySelectorAll([ | |
'.entry-body-content a[href]', | |
]).forEach(link => { | |
link.style.pointerEvents = 'none'; | |
}); | |
/*/ | |
// TBD suppress Reflow/Layout | |
entry.style.display = 'none'; | |
//entry.remove(); | |
// TODO reduce same users or other methods c.f. | |
// https://developer.mozilla.org/en-US/docs/Web/API/Console | |
//console.table({ | |
// id, | |
// score: blacklist.get(id), | |
// detail: USER_RECENT_STATS + id, | |
//}); | |
console.warn([ | |
'id:' + id, | |
'score: ' + blacklist.get(id), | |
'detail: ' + USER_RECENT_STATS + id, | |
].join('\t')); | |
//*/ | |
} | |
}); | |
}; | |
const timeline = document.body.querySelector('.entries'); | |
if (!timeline) return; | |
wipeout(timeline); | |
new MutationObserver(records => { | |
records.forEach(record => { | |
wipeout(record.target); | |
}); | |
}).observe(timeline, { childList: true, subtree: true, }); | |
})(); | |
// DEV | |
// | |
// [1]: Referrer-Policy | |
// specs | |
// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies | |
// https://fetch.spec.whatwg.org/#concept-request-referrer-policy | |
// | |
// test servers | |
// https://api.github.com | |
// https://httpstat.us | |
// https://httpstat.us/404 | |
// https://httpstat.us/200?sleep=60000 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment