非公開 TxRxs

    @@ -12,7 +12,8 @@ // http://let.hatelabo.jp/a-kuma3/let/hJmc7ZTR7vBO // TODO -// user.js on Gist +// - user.js on Gist +// - handle "Related/Hot Keywords" // non i18n similar services/software by id:tukihatu c.f. // Hatena Haiku a la mode (3rd party web services) @@ -41,40 +42,73 @@ 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'; + 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 - const request = new Request(FILTER_URL, { + // 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: 'no-referrer', + // Referrer-Policy fallback + referrer: '', + // https://w3c.github.io/webappsec-referrer-policy/#referrer-policies + // https://fetch.spec.whatwg.org/#concept-request-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(message) { + super(message); + this.name = this.constructor.name; + } + } + class TimeoutError extends ConnectionError { } + // TBD // - refresh filter when infinite scrolling // - small IIFE async scope - // - standard try-catch-finally model + // - try-catch-finally w/ await const blacklist = await fetch(request).then(res => { if (!res.ok || res.status !== 200) { - return Promise.reject(new Error(res.status, res.statusText, res)); + return Promise.reject(new Error([ + res.status, res.statusText, res + ])); } return res.json(); + }).catch(err => { + if (signal.aborted) { + return Promise.reject(new TimeoutError(err)); + } + return Promise.reject(new ConnectionError([ + 'check a network, location and server', + // https://downforeveryoneorjustme.com + 'http://isup.me/' + 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 => { - // TBD re-throw - console.error(err.message, err); + console.error([ + 'Hatena::Haiku spam filter', + err, + //err.stack, + ].join('\n')); }).finally(() => { clearTimeout(timer); }); console.debug(blacklist); - // TBD throw - if (blacklist.size === 0) return; + + if (!blacklist || blacklist.size === 0) return; // TBD show indicator/throbber when wiping const wipeout = (ctx) => { @@ -90,6 +124,7 @@ const id = poster.title.slice('id:'.length); if (blacklist.has(id)) { + const USER_DETAIL = 'https://haikuantispam.lightni.ng/id/'; /*/ entry.style.backgroundColor = 'red'; entry.querySelectorAll([ @@ -98,19 +133,19 @@ link.style.pointerEvents = 'none'; }); /*/ - // TBD suppress Reflow + // TBD suppress Reflow/Layout entry.style.display = 'none'; //entry.remove(); // TODO reduce same users //console.table({ // id, // score: blacklist.get(id), - // detail: 'https://haikuantispam.lightni.ng/id/' + id, + // detail: USER_DETAIL + id, //}); console.warn([ 'id:' + id, 'score: ' + blacklist.get(id), - 'detail: ' + 'https://haikuantispam.lightni.ng/id/' + id, + 'detail: ' + USER_DETAIL + id, ].join('\t')); //*/ } @@ -128,3 +163,8 @@ }).observe(timeline, { childList: true, subtree: true, }); })(); +// DEV +// https://httpstat.us +// https://httpstat.us/404 +// https://httpstat.us/200?sleep=60000 +
  • /*
     * @title H::H -spam
     * @description Hatena Haiku spam filter
     * @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
    // - handle "Related/Hot Keywords"
    
    // 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: '',
            // https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
            // https://fetch.spec.whatwg.org/#concept-request-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(message) {
                super(message);
                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) {
                return Promise.reject(new Error([
                    res.status, res.statusText, res
                ]));
            }
            return res.json();
        }).catch(err => {
            if (signal.aborted) {
                return Promise.reject(new TimeoutError(err));
            }
            return Promise.reject(new ConnectionError([
                'check a network, location and server',
                // https://downforeveryoneorjustme.com
                'http://isup.me/' + 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_DETAIL = '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
                    //console.table({
                    //    id,
                    //    score: blacklist.get(id),
                    //    detail: USER_DETAIL + id,
                    //});
                    console.warn([
                        'id:' + id,
                        'score: ' + blacklist.get(id),
                        'detail: ' + USER_DETAIL + 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
    // https://httpstat.us
    // https://httpstat.us/404
    // https://httpstat.us/200?sleep=60000
    
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2022/01/03 03:19:45 - 2022-01-03
  2. 2018/07/25 07:58:22 - 2018-07-25
  3. 2018/07/24 09:11:46 - 2018-07-24
  4. 2018/07/24 09:10:04 - 2018-07-24
  5. 2018/07/23 06:29:05 - 2018-07-23
  6. 2018/07/22 08:20:30 - 2018-07-22
  7. 2018/07/22 08:16:36 - 2018-07-22
  8. 2018/05/31 00:33:21 - 2018-05-31
  9. 2018/05/31 00:32:16 - 2018-05-31
  10. 2018/05/30 07:50:21 - 2018-05-30
  11. 2018/05/19 17:41:09 - 2018-05-19
  12. 2018/05/19 17:35:13 - 2018-05-19
  13. 2018/05/19 17:22:58 - 2018-05-19
  14. 2018/05/11 05:48:13 - 2018-05-11
  15. 2018/05/11 05:16:41 - 2018-05-11
  16. 2018/05/11 05:08:50 - 2018-05-11