H::H lightni.ng filter feat. Proxy

    
      
  • /*
     * @title H::H lightni.ng filter feat. Proxy
     * @description Hatena Haiku spam filtering w/ lightni.ng feat. Proxy
     * @include *://h.hatena.ne.jp/*
     * @contributor noromanba   http://let.hatelabo.jp/noromanba/let/hJmc6brdnsgL
     * @contributor austinburk  http://let.hatelabo.jp/austinburk/let/hLHU6PPgg9Ya
     * @license  The MIT License https://opensource.org/licenses/MIT
     * @javascript_url
     */
    /*
        ref.
            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
            http://h.hatena.ne.jp/js/updu/ten-extras.js
    */
    (() => {
        const d_ = document;
    
        const g = {
            THRESHOLD: 5,
            blacklist: {},
            no_more_entry: false,
            me: Hatena.Visitor.name,
        };
    
    
        function is_spam(entry) {
            const poster = entry.querySelector(".info > .username > a");
            const id = poster.title.slice("id:".length);
            const is_spam = id != g.me && g.blacklist.has(id);
            if (is_spam) {
                const url = entry.querySelector(".info > .timestamp > a").href;
                console.warn(`${url} is SPAM (id: ${id}, score: ${g.blacklist.get(id)})`);
            }
            return is_spam;
        }
    
        // see TL.DataSource.TimelinePage.prototype.loadData
        TL.compat.getPage = new Proxy(TL.compat.getPage, {
            apply: (target, thisArg, args) => {
                const url = args[0];
    
                // maybe pager-older's request
                if (/reftime=-/.test(url)) {
                    let onload = args[1];
    
                    onload = new Proxy(onload, {
                        apply: (target, thisArg, args) => {
                            const xhr = args[0];
    
                            // no more entry ?
                            if (xhr.status < 400 && xhr.responseText.trim().length == 0) {
                                g.no_more_entry = true;
                            }
    
                            return Reflect.apply(target, thisArg, args);
                        },
                    });
    
                    args[1] = onload;
                }
    
                return Reflect.apply(target, thisArg, args);
            },
        });
    
        // filtering EntryList
        TL.EntryList.prototype.appendEntries = new Proxy(TL.EntryList.prototype.appendEntries, {
            apply: (target, thisArg, args) => {
                // filter SPAM
                const list = args[0].filter(e => ! is_spam(e.element));
    
                // if list is empty, Event "entriesappended" is not fired
                if (! list.length) {
                    var ev = new TL.Event('entriesappended');
                    ev.newEntries = list;
                    thisArg.dispatchEvent(ev);
                }
    
                args[0] = list;
                return Reflect.apply(target, thisArg, args);
            },
        });
    
        function do_pagerize() {
            if (g.no_more_entry) {
                return;
            }
            const pager = d_.querySelector("div.pager a.pager-older");
            const y1 = pager.getBoundingClientRect().top;
            const y2 = window.innerHeight;
            if (y1 < y2) {
                pager.click();
            }
        }
    
        // do pagerize if pager is in view after remove entries
        TL.View.prototype.stopNextIndicator = new Proxy(TL.View.prototype.stopNextIndicator, {
            apply: (target, thisArg, args) => {
                do_pagerize();
                return Reflect.apply(target, thisArg, args);
            },
        });
    
    
        (async () => {
    
            // load scores
            const resp = await fetch("https://haikuantispam.lightni.ng/api/recent_scores.json", {
                mode: "cors",
                credentials: "same-origin",
            });
            const scores = await resp.json();
            g.blacklist = new Map(Object.entries(scores)
                .filter(([, score]) => score >= g.THRESHOLD)
            );
    
            // filter SPAM already displayed
            d_.querySelectorAll(".tl-entry-list .tl-entry").forEach(entry => {
                if (is_spam(entry)) {
                    entry.style.display = "none";
                }
            });
    
            do_pagerize();
    
        })();
    
    })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2018/05/16 23:11:02 - 2018-05-16