/* * @title H::H lightni.ng filter * @description Hatena Haiku spam filtering w/ lightni.ng * @include *://h.hatena.ne.jp/* * @license The MIT License https://opensource.org/licenses/MIT * @javascript_url */ // nitpicking via // http://let.hatelabo.jp/austinburk/let/hLHU6PPgg9Ya // TODO // user.js on Gist // 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 (() => { 'use strict'; if (window.self !== window.top) return; // @noframes in Vanilla // TODO // - async use w/ await // - Promise.all/.race for timeout // TBD // - refresh filter when infinite scrolling let blacklist; const syncBlockingFetch = () => { const xhr = new XMLHttpRequest(); /* XXX asynchronous only xhr.timeout = 3000; xhr.responseType = 'json'; */ // TODO unreachable? const cancel = (evt) => { throw Error(evt.type, xhr.status, xhr.readyState, evt); }; xhr.addEventListener('load', evt => { if (xhr.status !== 200) { cancel(evt); } const THRESHOLD = 5; // TBD omit JSON.parse blacklist = new Map(Object.entries(JSON.parse(xhr.response)) .filter(([, score]) => score >= THRESHOLD) ); }); [ 'abort', 'timeout', 'error', ].forEach(type => { xhr.addEventListener(type, evt => { cancel(evt); }); }); // Access-Control-Allow-Origin: http://h.hatena.ne.jp const FILTER_URL = 'https://haikuantispam.lightni.ng/api/recent_scores.json'; // XXX synchronize xhr.open('GET', FILTER_URL, false); xhr.send(); }; syncBlockingFetch(); const wipeout = (ctx) => { if (!ctx.querySelectorAll) return; ctx.querySelectorAll([ '.entry.tl-entry' ]).forEach(entry => { const poster = entry.querySelector('.username a[href][title]'); if (!poster) return; const id = poster.title.slice('id:'.length); if (blacklist.has(id)) { /*/ //entry.style.backgroundColor = 'red'; entry.innerHTML = `
`; /*/ entry.style.display = 'none'; //*/ } }); }; 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, }); })();