TxRxs
by
noromanba
2022-01-03 [2022/01/03 03:19:45]
replace http to https
@@ -8,13 +8,15 @@
// nitpicking via
// http://let.hatelabo.jp/austinburk/let/hLHU6PPgg9Ya
+// Proxy/Reflect ver
+// http://let.hatelabo.jp/a-kuma3/let/hJmc7ZTR7vBO
// 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://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
@@ -22,57 +24,56 @@
// e.g.
// http://h.hatena.ne.jp
-(() => {
+(async () => {
'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);
- }
+ // 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} msec elapsed`);
+ };
+ const timer = setTimeout(() => controller.abort(), TIMEOUT);
- 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';
+ // TBD no-referrer and/or use CORS proxy c.f.
+ // http://let.hatelabo.jp/noromanba/let/hJmc7rWIvsJj
+ // for avoid web-beacon/bug and fingerprinting like risks
+ const headers = new Headers();
+ //headers.set('', '');
- // 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();
+ // TBD
+ // - refresh filter when infinite scrolling
+ // - IIFE async and try-catch-finally model
+ const blacklist = await fetch(FILTER_URL, {
+ headers,
+ //mode: 'cors',
+ signal,
+ })
+ .then(res => {
+ if (!res.ok) {
+ return Promise.reject(new Error(res.status, res.statusText));
+ }
+ return res.json();
+ })
+ .then(json => {
+ const SPAM_THRESHOLD = 5;
+ return new Map(Object.entries(json)
+ .filter(([, score]) => score >= SPAM_THRESHOLD));
+ })
+ .catch(err => {
+ console.error(err.message, err);
+ })
+ .finally(() => {
+ clearTimeout(timer);
+ });
+ console.debug(blacklist);
const wipeout = (ctx) => {
if (!ctx.querySelectorAll) return;
@@ -80,21 +81,22 @@
ctx.querySelectorAll([
'.entry.tl-entry'
]).forEach(entry => {
- const poster = entry.querySelector('.username a[href][title]');
+ // 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)) {
/*/
- //entry.style.backgroundColor = 'red';
- entry.innerHTML = `
- <p><a href="https://haikuantispam.lightni.ng/id/${id}"
- title="spam-detail"
- target="_blank">
- <censored>
- </a></p>`;
+ entry.style.backgroundColor = 'red';
/*/
+ // TBD suppress reflow
+ console.debug(`id:${id} score: ${blacklist.get(id)}
+ https://haikuantispam.lightni.ng/id/${id}
+ `);
entry.style.display = 'none';
+ entry.innerHTML = '';
//*/
}
});
/*
* @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
// Proxy/Reflect ver
// http://let.hatelabo.jp/a-kuma3/let/hJmc7ZTR7vBO
// 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
(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} msec elapsed`);
};
const timer = setTimeout(() => controller.abort(), TIMEOUT);
// Access-Control-Allow-Origin: http://h.hatena.ne.jp
const FILTER_URL = 'https://haikuantispam.lightni.ng/api/recent_scores.json';
// TBD no-referrer and/or use CORS proxy c.f.
// http://let.hatelabo.jp/noromanba/let/hJmc7rWIvsJj
// for avoid web-beacon/bug and fingerprinting like risks
const headers = new Headers();
//headers.set('', '');
// TBD
// - refresh filter when infinite scrolling
// - IIFE async and try-catch-finally model
const blacklist = await fetch(FILTER_URL, {
headers,
//mode: 'cors',
signal,
})
.then(res => {
if (!res.ok) {
return Promise.reject(new Error(res.status, res.statusText));
}
return res.json();
})
.then(json => {
const SPAM_THRESHOLD = 5;
return new Map(Object.entries(json)
.filter(([, score]) => score >= SPAM_THRESHOLD));
})
.catch(err => {
console.error(err.message, err);
})
.finally(() => {
clearTimeout(timer);
});
console.debug(blacklist);
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)) {
/*/
entry.style.backgroundColor = 'red';
/*/
// TBD suppress reflow
console.debug(`id:${id} score: ${blacklist.get(id)}
https://haikuantispam.lightni.ng/id/${id}
`);
entry.style.display = 'none';
entry.innerHTML = '';
//*/
}
});
};
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, });
})();
- Permalink
- このページへの個別リンクです。
- RAW
- 書かれたコードへの直接のリンクです。
- Packed
- 文字列が圧縮された書かれたコードへのリンクです。
- Userscript
- Greasemonkey 等で利用する場合の .user.js へのリンクです。
- Loader
- @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
- Metadata
- コード中にコメントで @xxx と書かれたメタデータの JSON です。