Greasy Fork

ニコニコ静画、クッキー☆連投荒らしNG

申し訳ないが連投荒らしはNG

当前为 2020-10-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         ニコニコ静画、クッキー☆連投荒らしNG
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  申し訳ないが連投荒らしはNG
// @author       cbxm
// @match        https://seiga.nicovideo.jp/tag/%E3%82%AF%E3%83%83%E3%82%AD%E3%83%BC%E2%98%86*
// @grant        GM.xmlHttpRequest
// @grant        GM.getValue
// @grant        GM.setValue
// @run-at document-start
// ==/UserScript==
(async () => {
    "use strict";
    ;
    ;
    class Util {
        //xmlString=未探査部分
        static XmlToObj(xmlString) {
            const F = (xmlString, obj) => {
                //タグを抜き出す
                let tagMatchs = null;
                while (true) {
                    tagMatchs = xmlString.match(/<([^>]+)>/);
                    //タグがないということはそれが値になる
                    if (tagMatchs == null) {
                        return xmlString;
                    }
                    if (tagMatchs[1][tagMatchs[1].length - 1] == "/") {
                        xmlString = xmlString.replace(/<[^>]+>([^]*)/, "$1");
                    }
                    else {
                        break;
                    }
                }
                const tag = tagMatchs[1];
                //タグの内側とその先を抜き出す
                const matchChildlen = [];
                while (true) {
                    const matchs = xmlString.match(new RegExp(`^[^<]*<${tag}>([^]+?)<\/${tag}>([^]*)`));
                    if (matchs == null) {
                        break;
                    }
                    matchChildlen.push(matchs[1]);
                    xmlString = matchs[2];
                }
                //タグあったのにマッチしなかったおかしい
                if (matchChildlen.length == 0) {
                    return obj;
                }
                //そのタグが一つしかないとき、オブジェクトになる
                if (matchChildlen.length == 1) {
                    //子を探す
                    obj[tag] = F(matchChildlen[0], {});
                }
                //そのタグが複数あるとき、配列になる
                if (matchChildlen.length > 1) {
                    obj = [];
                    for (let i = 0; i < matchChildlen.length; i++) {
                        //子を探す
                        obj[i] = F(matchChildlen[i], {});
                    }
                }
                //兄弟を探す
                F(xmlString, obj);
                return obj;
            };
            //初期化で<xml>を取り除く
            xmlString = xmlString.replace(/\s*<[^>]+>([^]+)/, "$1");
            return F(xmlString, {});
        }
        static HtmlToDocument(str) {
            const parser = new DOMParser();
            return parser.parseFromString(str, "text/html");
        }
        static HtmlToChildNodes(str) {
            return this.HtmlToDocument(str).body.childNodes;
        }
        static Wait(ms) {
            return new Promise(r => setTimeout(r, ms));
        }
    }
    ;
    class Fetcher {
        static GMFetchText(url) {
            return new Promise(r => {
                GM.xmlHttpRequest({
                    url: url,
                    method: "GET",
                    onload: (response) => {
                        r(response.responseText);
                    }
                });
            });
        }
        static async FetchIllustDatas(ids) {
            if (ids.length == 0) {
                return { illusts: [], userIds: [] };
            }
            const url = `http:\/\/seiga.nicovideo.jp/api/illust/info?id_list=${ids.join()}`;
            const res = await this.GMFetchText(url);
            const obj = Util.XmlToObj(res);
            const list = Array.isArray(obj.response.image_list) ? obj.response.image_list : [obj.response.image_list.image];
            const illusts = [];
            for (let i = 0; i < list.length; i++) {
                illusts[i] = {
                    id: list[i].id,
                    created: new Date(list[i].created)
                };
            }
            return {
                illusts: illusts,
                userIds: list.map(l => l.user_id)
            };
        }
        static async FetchUserName(userId) {
            const url = "http://seiga.nicovideo.jp/api/user/info?id=" + userId;
            const json = Util.XmlToObj(await this.GMFetchText(url));
            return json.response.user.nickname;
        }
        static async FetchUserId(illustId) {
            const url = "https://seiga.nicovideo.jp/api/illust/info?id=im" + illustId;
            const resultText = await this.GMFetchText(url);
            const json = Util.XmlToObj(resultText);
            return json.response.image.user_id;
        }
    }
    ;
    class Storage {
        constructor(storageName) {
            this.storageName = "";
            this.storageName = storageName;
        }
        async GetStorageData(defaultValue = null) {
            const text = await GM.getValue(this.storageName, null);
            return text != null ? JSON.parse(decodeURIComponent(text)) : defaultValue;
        }
        async SetStorageData(data) {
            await GM.setValue(this.storageName, encodeURIComponent(JSON.stringify(data)));
        }
    }
    ;
    class Observer {
        static Wait(predicate, parent = document, option = null) {
            return new Promise(r => {
                if (option == null) {
                    option = {
                        childList: true,
                        subtree: true
                    };
                }
                const mutationObserver = new MutationObserver((mrs) => {
                    if (predicate(mrs)) {
                        mutationObserver.disconnect();
                        r(mrs);
                        return;
                    }
                });
                mutationObserver.observe(parent, option);
            });
        }
        ;
        static WaitAddedNode(predicate, parent, option = null) {
            return new Promise(r => {
                if (option == null) {
                    option = {
                        childList: true,
                        subtree: true
                    };
                }
                const mutationObserver = new MutationObserver((mrs) => {
                    //console.log(document.head.innerHTML);
                    //console.log(document.body.innerHTML);
                    for (let node of mrs) {
                        node.addedNodes.forEach(added => {
                            //console.log(added);
                            if (predicate(added)) {
                                mutationObserver.disconnect();
                                r(added);
                                return;
                            }
                        });
                    }
                });
                mutationObserver.observe(parent, option);
            });
        }
        ;
        static async DefinitelyGetElementById(id, parent = document, option = null) {
            const e = document.getElementById(id);
            if (e != null) {
                return e;
            }
            return this.WaitAddedNode(e => e.id != null && e.id == id, parent, option);
        }
        //getElementsByClassNameをつかうけど単体なので注意
        static async DefinitelyGetElementByClassName(className, parent = document, option = null) {
            const e = document.getElementsByClassName(className)[0];
            if (e != null) {
                return e;
            }
            return this.WaitAddedNode(e => e.className != null && e.className == className, parent, option);
        }
        //getElementsByTagNameをつかうけど単体なので注意
        static async DefinitelyGetElementByTagName(tagName, parent = document, option = null) {
            tagName = tagName.toUpperCase();
            const e = document.getElementsByTagName(tagName)[0];
            if (e != null) {
                return e;
            }
            return this.WaitAddedNode(e => e.tagName != null && e.tagName == tagName, parent, option);
        }
    }
    ;
    class Main {
        constructor() {
            this.cache = [];
            this.storage = new Storage("NICONICO_RENTO_ARASI_NG_DATA_CACHE");
        }
        async GetCache() {
            this.cache = await this.storage.GetStorageData([]);
        }
        async ResetCache() {
            this.cache = [];
            await this.SetCache();
        }
        ResetStatus() {
            for (let c of this.cache) {
                if (c.status != "LNG" && c.status != "LOK") {
                    c.status = "OK";
                    this.ChackArasi(c);
                }
            }
        }
        async SetCache() {
            await this.storage.SetStorageData(this.cache);
        }
        GetUser(id) {
            for (let c of this.cache) {
                for (let illust of c.illusts) {
                    if (illust.id == id) {
                        return c;
                    }
                }
            }
            return undefined;
        }
        ChackArasi(user) {
            if (user.illusts.length == 0 || user.status == "LNG" || user.status == "LOK" || user.status == "NG") {
                return;
            }
            for (let illust of user.illusts) {
                if (typeof illust.created == "string") {
                    illust.created = new Date(illust.created);
                }
            }
            //新しい順
            const sorted = user.illusts.sort((a, b) => b.created.getTime() - a.created.getTime());
            for (let i = 0; i < sorted.length; i++) {
                const currentDate = sorted[i].created;
                let j = i + 1;
                let count = 0;
                while (true) {
                    if (j >= sorted.length || currentDate.getTime() - sorted[j].created.getTime() > 7200000) {
                        break;
                    }
                    j++;
                    count++;
                }
                if (count >= 3) {
                    user.status = "NG";
                    return;
                }
            }
        }
        ;
        GetIllustIds() {
            const illustIdElements = document.getElementsByClassName("item_list")[0].getElementsByTagName("a");
            const illustIds = [];
            for (let i = 0; i < illustIdElements.length; i++) {
                const idMatchs = illustIdElements[i].href.match(/im(\d+)/);
                if (idMatchs == null) {
                    continue;
                }
                const id = idMatchs[1];
                illustIds.push(id);
            }
            return illustIds;
        }
        async Main() {
            const pageCountText = (await Observer.DefinitelyGetElementByClassName("page_count")).textContent;
            if (pageCountText == null) {
                return;
            }
            const pageCountMatchs = pageCountText.match(/(\d+)〜(\d+)/);
            if (pageCountMatchs == null) {
                return;
            }
            const count = parseInt(pageCountMatchs[2]) - parseInt(pageCountMatchs[1]) + 1;
            await Observer.Wait(() => document.getElementsByClassName("list_item").length >= count);
            const removedNGs = [];
            const illustElements = Array.from(document.getElementsByClassName("list_item"));
            const illustIds = this.GetIllustIds();
            const unknownIllustIds = [];
            const unknownElements = [];
            for (let i = 0; i < illustIds.length; i++) {
                const user = this.GetUser(illustIds[i]);
                if (user != undefined) {
                    //既存のものはもう判定
                    if (user.status == "NG" || user.status == "LNG") {
                        if (user.status == "NG") {
                            removedNGs.push(illustElements[i]);
                        }
                        illustElements[i].remove();
                    }
                    continue;
                }
                unknownIllustIds.push(illustIds[i]);
                unknownElements.push(illustElements[i]);
            }
            console.log("Fetch", unknownIllustIds);
            const r = await Fetcher.FetchIllustDatas(unknownIllustIds);
            const newIllusts = r.illusts;
            const newUserIds = r.userIds;
            const updateUsers = new Set();
            //キャッシュに追加
            for (let i = 0; i < newIllusts.length; i++) {
                const user = this.cache.find((c) => c.userId == newUserIds[i]);
                if (user != null) {
                    user.illusts.push(newIllusts[i]);
                    updateUsers.add(user);
                    continue;
                }
                const newUser = {
                    illusts: [newIllusts[i]],
                    userId: newUserIds[i],
                    status: "OK"
                };
                this.cache.push(newUser);
                updateUsers.add(newUser);
            }
            //更新
            for (let c of updateUsers.values()) {
                this.ChackArasi(c);
            }
            //消す
            for (let i = 0; i < newIllusts.length; i++) {
                const user = this.cache.find((c) => c.userId == newUserIds[i]);
                if (user && (user.status == "NG" || user.status == "LNG")) {
                    if (user.status == "NG") {
                        removedNGs.push(unknownElements[i]);
                    }
                    unknownElements[i].remove();
                }
            }
            const illustListElements = document.getElementsByClassName("item_list")[0];
            for (let removedNG of removedNGs) {
                removedNG.getElementsByTagName("img")[0].style.filter = "brightness(0.3)";
                illustListElements.appendChild(removedNG);
            }
        }
    }
    ;
    const main = new Main();
    await main.GetCache();
    //main.ResetStatus();
    // main.ResetCache();
    await main.Main();
    await main.SetCache();
})();
//キャッシュは最小限に(ユーザーID、isArasi、イラスト「ID、投稿時間」)                                     ok
//投稿時間は必要になったときにパース                                                                   ok
//多分荒らしじゃない="OK"、多分荒らし="NG"、絶対荒らしじゃない=”LOK”、絶対荒らし="LNG"        ok
//イラスト右下にホワイトリスト、ブラックリストボタンをつける
//設定項目:「何時間」以内に「何回」投稿が「何回」あった
//        ホワイトリスト、ブラックリスト
//       キャッシュリセット
//       NGリセット
//# sourceMappingURL=script.js.map