Skip to content

Instantly share code, notes, and snippets.

@noromanba
Last active September 21, 2018 17:25
Show Gist options
  • Save noromanba/e485c35ffba606ae8ecacac2c9a8da3c to your computer and use it in GitHub Desktop.
Save noromanba/e485c35ffba606ae8ecacac2c9a8da3c to your computer and use it in GitHub Desktop.
Hatena Haiku spam filter for UserScript
// ==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