Impression Zombie Buster(mod)

Auto fire to Impression Zombies with Japanese Twitter!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Impression Zombie Buster(mod)
// @namespace    http://tampermonkey.net/
// @version      2025-10-08
// @description  Auto fire to Impression Zombies with Japanese Twitter!
// @author       Ganohr, @rmc_km
// @match        https://x.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant        none
// @license      CC BY-SA 4.0
// @license url  https://creativecommons.org/licenses/by-sa/4.0/deed.ja
// @source page  https://ganohr.net/blog/a-monkey-soldier-who-automatically-exterminates-the-zombies-infesting-x/
// ==/UserScript==

(function () {
    "use strict";
    setInterval(() => {
        const menuGet = (e) =>
            e.parentElement.parentElement.querySelector(
                "button[aria-haspopup='menu']"
            );
        document
            .querySelectorAll(
                "div[data-testid='Tweet-User-Avatar']:not(:has(button))"
            )
            .forEach((e) => {
                if (!menuGet(e)) {
                    return;
                }
                const button = document.createElement("button");
                button.style = "font-size:7pt;height:30pt;";
                button.textContent = "block";
                button.onclick = () => {
                    const menuButton = menuGet(e);
                    menuButton.click();
                    const i1 = setInterval(() => {
                        const blockButton = document.querySelector(
                            "div[role='menuitem'][data-testid='block']"
                        );
                        if (!blockButton) {
                            return;
                        }
                        clearInterval(i1);
                        blockButton.click();
                        const i2 = setInterval(() => {
                            const confirmButton = document.querySelector(
                                "button[data-testid='confirmationSheetConfirm']"
                            );
                            if (!confirmButton) {
                                return;
                            }
                            clearInterval(i2);
                            confirmButton.click();
                        }, 100);
                    }, 100);
                };
                e.append(button);
            });
    }, 1000);

    const urlReg =
        /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
    const emojiReg =
        /[\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF]/g;
    const punctuationReg =
        /[ \$\uFFE5\^\+=`~<>{}\[\]|\u3000-\u303F!-#%-\x2A,-/:;\x3F@\x5B-\x5D_\x7B}\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E3B\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]+/g;
    const getPostedDate = (e) => {
        if (!e) {
            return new Date();
        }
        const time = e.querySelector("time[datetime]");
        return time ? new Date(time.attributes.datetime.value) : null;
    };
    const getPostedText = (e, plain = false) => {
        if (!e) {
            return null;
        }
        const tweet = e.querySelector('div[data-testid="tweetText"]');
        const plainer = (text) =>
            plain ? text : text.replace(punctuationReg, "");
        return tweet ? plainer(tweet.innerText.trim()) : null;
    };
    const getPostedAccount = (e) => {
        if (!e) {
            return null;
        }
        const account = e.querySelector(
            "div[data-testid='User-Name']>div:last-child"
        );
        return account ? account.innerText.split("\n")[0].trim() : null;
    };
    const getPostedAccountName = (e) => {
        if (!e) {
            return null;
        }
        const accountName = e.querySelector(
            "div[data-testid='User-Name'] span"
        );
        return accountName ? accountName.innerText.split("\n")[0].trim() : null;
    };
    const checkZombieCarrier = (e) => {
        const firstFoundCarrier = e.querySelector(
            "svg[aria-label='認証済みアカウント']"
        );
        const rePostingCarrier = e.querySelector(
            "div[role='link']  svg[aria-label='認証済みアカウント']"
        );
        if (
            firstFoundCarrier &&
            rePostingCarrier &&
            firstFoundCarrier !== rePostingCarrier
        ) {
            return true;
        }
        if (firstFoundCarrier && !rePostingCarrier) {
            return true;
        }
        return false;
    };
    const hasEmoji = (e) => {
        let hasEmoji = false;
        e.querySelectorAll("img[alt]").forEach((img) => {
            if (hasEmoji) {
                return;
            }
            const html = img.outerHTML;
            if (emojiReg.test(html) || /emoji/.test(html)) {
                hasEmoji = true;
            }
        });
        return hasEmoji;
    };
    const checkEmojiOnlyPost = (e) => {
        if (!e) {
            return false;
        }
        const text = getPostedText(e);
        if (text) {
            return text.replace(emojiReg, "").trim().length === 0;
        }
        return hasEmoji(e);
    };
    const checkRePostOnlyPost = (e) => {
        if (!e) {
            return false;
        }
        const text = e.innerText;
        if (/ブロックしているアカウントによるポストです。/g.test(text)) {
            return true;
        }
        if (!/引用/g.test(text)) {
            return false;
        }
        const textArray = e.innerText.split("\n");
        const NOT_FOUND = -1;
        const START_INDEX = 4;
        let searchIndex = NOT_FOUND;
        let startOffset = 0;
        textArray.forEach((line, index) => {
            if (index === 0 && line === "Block") {
                startOffset = 1;
            }
            if (searchIndex !== NOT_FOUND) {
                return;
            }
            if (line === "引用") {
                searchIndex = index;
            }
        });
        if (searchIndex === START_INDEX + startOffset) {
            return true;
        }
        let post = "";
        for (
            let index = START_INDEX + startOffset;
            index < searchIndex;
            index++
        ) {
            post += textArray[index];
        }
        return (
            post
                .replace(urlReg, "")
                .replace(emojiReg, "")
                .replace(punctuationReg, "")
                .replace(/[\s\r\n ]/g, "").length === 0
        );
    };
    const checkSpamTweet = (e) => {
        const innerText = e.innerText;
        if (/(dmm\.co\.jp|app\.link|a\.r10\.to|lin\.ee)/.test(innerText)) {
            return true;
        }

        // ドメイン検出を最初に行う(innerTextとtextの両方をチェック)
        const suspiciousDomainReg =
            /\.(?:xyz|tk|ml|ga|cf|live|site|click|top|info|biz|pw|win)(?:$|\/|[:?#])|[a-z0-9]{8,}\..*kabu.*\.xyz|news[a-z0-9]{6,}\.[a-z0-9]+\.xyz|[a-z0-9]{10,}\.[a-z0-9]+\.(?:xyz|tk|live)|[a-z0-9]{6,}\.(?:tosayo|nayoto|newekuk|yilife|liveright)\.xyz|[a-z0-9]{6,}\.fangp\.top|[a-z0-9]{6,}\.yuizoo[a-z]{2,}\.xyz/gi;

        if (suspiciousDomainReg.test(innerText)) {
            return true;
        }

        const text = getPostedText(e, true);

        // textでもドメインチェック
        if (suspiciousDomainReg.test(text)) {
            return true;
        }

        if (
            /(^| |\n|\r)(\$BEYOND|\$PARAM|\$BUBBLE|\@ricyofficial|\$RICY|\$XTER|\$COOKIE|\$BUBBLE|\$LOL|@Cookie3_com)($| |\n|\r)/gi.test(
                text
            )
        ) {
            return true;
        }
        if (
            /打扰楼主帖子|发个推广|借楼主宝|价不亏|的点击主页联系|此推特不|作任何回|([良よ](かったら|ければ))?(プロフ(ィール)?|ぷろふ)[のを]?(URL|リンク)?(から)?([き来]て|[見み]て|確認|かくにん|チェック|ちぇっく|check)|[気き]になったから(リプ|りぷ)(ライ|らい)?し|今フォローした.+?資産.+倍|NUDES\s*IN\s*PROFILE|NUDES\s*IN\s*PROFILE|月超絶材料[↓⬇︎▼▽⤋⤓⇩⇊⤸]{0,3}|株主優待制度.+?(拡充|の|を).+?(発表|拡充)|来週は.?S高.?(行く|いく)|S高.?(行く|いく).?の.?かい.???|明日は.?(ストップ高|S高)|ストップ高.?(期待|狙い|確実|決まり|買い気配)|爆益|爆上げ|テンバガー|業績修正|決算発表.+?(ストップ高|S高)|株式分割.+?(好感|材料)|自己株式.+?取得/gi.test(
                text
            )
        ) {
            return true;
        }
        if (
            /\s*[→⇒]\s*@/g.test(text) &&
            hasEmoji(e) &&
            /(プロフ(ィール)?(URL)?から|ぷろふ(ぃーる)?(URL)から|こんにち[はわ]|こんばん[はわ]|連絡(してね?)?|絡みましょう?|絡もう?|からもう?|お?話しし?ま(しょ|せんか|よう|する)|こっち|ここ)/gi.test(
                text
            ) &&
            /よろしくね?|よろしくおねがいします|お?返事(待ってるね?|待ってます|してね?|ちょうだい|楽しみ|たのしみ|ください|下さい)/gi.test(
                text
            )
        ) {
            return true;
        }
        if (
            (/(りぷ|リプ)頂戴|お?(話|はな)しし?ましょう?|(ぷろふ(ぃー?る)?|プロフ(ィー?ル))?から([来き]て|よろしく|宜しく)|profみて|qr(コード)?を(スマホで?|すまほで)?([読よ]んでね?|読み?[こ込]んで)|♡と(ふぉろー?|フォロー)|(ふぉろー?|フォロー?)(らぶ|ラブ)?(りつ|リツ)\s*(して|で)/i.test(
                text
            ) ||
                /(おな|オナ|オナ|すか|スカ|スカ|マン|マン|まん|パイ|ぱい|パイ|チン|チン|ちん)凸|無修正|(スカトロ|すかとろ)|レズ(ビアン)?|([おぉオォ][なナナ][二に][いぃーイィ]?)|ハメ撮り|のくぱぁ欲しい人|初めての人優先でDM送ります|発情期|おな凸|R18|18以上だけだよ|依存相手募集中|[MMSS][女男]|裏(アカ|あか)[男女]|写メ|おじさんすき/i.test(
                    text
                )) &&
            hasEmoji(e) &&
            text.length > "おはなししましょう🙌🙌🙌".length
        ) {
            return true;
        }
        if (
            /#pr|即現?金|即.+?円|総額.+?円|本日限定|稼[が-ご]|登録|報酬|#ad/i.test(
                text
            ) &&
            /ポイ活|tiktok\s*lite|ポイント|マイル|クーポン|GET|プレゼント|ゲット[!!しすせだ]|paypay|追加報酬|過去最高|登録/i.test(
                text
            ) &&
            text.length > 64
        ) {
            return true;
        }
        const accountName = getPostedAccountName(e);
        const spamSiteReg =
            /bokuao-antena\.antenam\.jp|kinmirainews\.com|bnc\.lt/g;
        const sensitiveNGWordReg = /オフパコ|セフレ|おかず|オカズ/g;
        const followMeReg =
            /(follow|フォロー?|ふぉろー?)しても(OK|いい|良い|大丈夫)(かな|ですか?)?/g;
        if (
            false ||
            spamSiteReg.test(text) ||
            (true &&
                (false ||
                    sensitiveNGWordReg.test(text) ||
                    sensitiveNGWordReg.test(accountName)) &&
                followMeReg.test(text)) ||
            (true &&
                followMeReg.test(text) &&
                (false || hasEmoji(e) || emojiReg.test(text)))
        ) {
            return true;
        }
        return false;
    };
    const zombieQueue = {};
    const targettingZombie = (e) => {
        const zombie = getPostedAccount(e);
        if (zombieQueue.hasOwnProperty(zombie)) {
            return;
        }
        zombieQueue[zombie] = e;
    };
    const postQueue = [];
    const NEED_POST_LENGTH = 8;
    const checkNearString = (a, b) => {
        // https://qiita.com/gomaoaji/items/603904e31f965d759293
        // https://www.k-intl.co.jp/blog/B_200729A
        // thanx KIマーケティングチーム-川村インターナショナル, @gomaoaji-Qiita
        const getToNgram = (text, n = 3) => {
            let ret = {};
            for (var m = 0; m < n; m++) {
                for (var i = 0; i < text.length - m; i++) {
                    const c = text.substring(i, i + m + 1);
                    ret[c] = ret[c] ? ret[c] + 1 : 1;
                }
            }
            return ret;
        };
        const getValuesSum = (obj) => {
            return Object.values(obj).reduce(
                (prev, current) => prev + current,
                0
            );
        };
        const calculate = (a, b) => {
            const aGram = getToNgram(a);
            const bGram = getToNgram(b);
            const keyOfAGram = Object.keys(aGram);
            const keyOfBGram = Object.keys(bGram);
            // aGramとbGramに共通するN-gramのkeyの配列
            const abKey = keyOfAGram.filter((n) => keyOfBGram.includes(n));

            // aGramとbGramの内積(0と1の掛け算のため、小さいほうの値を足し算すれば終わる。)
            let dot = abKey.reduce(
                (prev, key) => prev + Math.min(aGram[key], bGram[key]),
                0
            );

            // 長さの積(平方根の積は積の平方根)
            const abLengthMul = Math.sqrt(
                getValuesSum(aGram) * getValuesSum(bGram)
            );
            return dot / abLengthMul;
        };
        return calculate(a, b) * 100;
    };
    const checkSamePost = (text, date, e, isZombieCarrier) => {
        const postText = text.replace(emojiReg, "").toLowerCase().trim();
        if (postText.length < NEED_POST_LENGTH) {
            return false;
        }
        let isSamePost = false;
        let targetElement = null;
        const removeIndex = [];
        postQueue.forEach((post, i) => {
            const postedText = post.text;
            const postedDate = post.date;
            const postedIsCarrier = post.isCarrier;
            targetElement = post.element;
            if (isSamePost) {
                return;
            }
            if (postText != postedText) {
                if (checkNearString(postText, postedText) <= 90) {
                    return;
                }
            }
            if (date < postedDate) {
                if (
                    (postedIsCarrier && targetElement) ||
                    (!postedIsCarrier &&
                        targetElement &&
                        postedText.length >= 15)
                ) {
                    targettingZombie(targetElement);
                }
                removeIndex.push(i);
                return;
            }
            isSamePost = true;
        });
        removeIndex
            .sort()
            .reverse()
            .forEach((i) => {
                postQueue.splice(i, 1);
            });
        if (!isSamePost) {
            postQueue.push({
                text: postText,
                date: date,
                element: e,
                isCarrier: isZombieCarrier,
            });
            return false;
        }
        return targetElement;
    };
    const checkedPostQueue = {};
    const checkAlreadyCheckedPost = (post, date, account) => {
        const key = account + date;
        if (!checkedPostQueue.hasOwnProperty(key)) {
            checkedPostQueue[key] = post;
            return false;
        }
        if (checkedPostQueue[key] !== post) {
            return false;
        }
        return true;
    };
    const checkMoreReplyLoadded = () => {
        let hasMoreReplyLoadded = false;
        document
            .querySelectorAll('div[data-testid="cellInnerDiv"] h2 span')
            .forEach((e) => {
                if (hasMoreReplyLoadded) {
                    return;
                }
                if (/返信をさらに表示/.test(e.innerText)) {
                    hasMoreReplyLoadded = true;
                }
            });
        return hasMoreReplyLoadded;
    };
    const checkAndClickMoreReply = () => {
        if (checkMoreReplyLoadded()) {
            return false;
        }
        let hasMoreReply = false;
        document
            .querySelectorAll('div[data-testid="cellInnerDiv"] span')
            .forEach((e) => {
                if (hasMoreReply) {
                    return;
                }
                if (/返信をさらに表示/.test(e.innerText)) {
                    e.click();
                    hasMoreReply = true;
                } else if (/さらに返信を表示する/.test(e.innerText)) {
                    const button =
                        e.parentElement.parentElement.parentElement.querySelector(
                            "button[role='button']:has(span span)"
                        );
                    if (button) {
                        button.click();
                        hasMoreReply = true;
                    }
                }
            });
        return hasMoreReply;
    };
    const checkZombie = (e) => {
        const post = getPostedText(e);
        const date = getPostedDate(e);
        const account = getPostedAccount(e);
        if (post === null || !date) {
            return false;
        }
        if (checkAlreadyCheckedPost(post, date, account)) {
            return false;
        }
        if (postAuthor && account && postAuthor === account) {
            return false;
        }
        if (checkSpamTweet(e)) {
            return true;
        }
        if (!checkZombieCarrier(e)) {
            checkSamePost(post, date, e, false);
            return false;
        }
        return (
            false ||
            checkEmojiOnlyPost(e) ||
            checkSamePost(post, date, e, true) ||
            checkRePostOnlyPost(e)
        );
    };
    const loggingZombie = (message, e) =>
        console.log("Can't bust Zombie! " + message, e);
    const SHOOT_MODE_MENU = 1;
    const SHOOT_MODE_BLOCK = 2;
    const SHOOT_MODE_CONFIRM = 3;
    const SHOOT_WAIT = 20;
    let shootMode = SHOOT_MODE_MENU;
    let shootWait = SHOOT_WAIT;
    const initShoot = () => {
        shootMode = SHOOT_MODE_MENU;
        shootWait = SHOOT_WAIT;
    };
    const removeZombieFromQueue = (zombie) => {
        delete zombieQueue[zombie];
        initShoot();
    };
    const LS_KEY_KILLED_ZOMBIE = "ganohrs_izb_killed";
    const getKilledZombieCount = () => {
        const num = Number(localStorage.getItem(LS_KEY_KILLED_ZOMBIE));
        if (Number.isNaN(num)) {
            return 0;
        }
        return num;
    };
    const LS_KEY_KILLED_LIST = "ganohrs_izb_list";
    const getKilledZombieList = () => {
        const csv = localStorage.getItem(LS_KEY_KILLED_LIST);
        if (!csv) {
            return [];
        }
        return csv.split(",");
    };
    const killedZombie = (zombie) => {
        const count = getKilledZombieCount() + 1;
        localStorage.setItem(LS_KEY_KILLED_ZOMBIE, count);
        const list = getKilledZombieList();
        list.push(zombie);
        localStorage.setItem(LS_KEY_KILLED_LIST, list);
        return count;
    };
    const shootZombie = () => {
        const entries = Object.entries(zombieQueue);
        if (!entries || entries.length === 0) {
            return;
        }
        const zombie = entries[0][0];
        const e = entries[0][1];
        const post = getPostedText(e, true);
        const date = getPostedDate(e);

        shootWait--;
        if (shootWait <= 0) {
            loggingZombie(
                "Can't bust Zombie! zombie named: [" +
                    zombie +
                    "], mode = " +
                    shootMode,
                e
            );
            removeZombieFromQueue(zombie);
            return;
        }
        const fireZombie = (nextMode, needRemove, e) => {
            if (e) {
                e.click();
                shootWait = SHOOT_WAIT;
                shootMode = nextMode;
                if (needRemove) {
                    removeZombieFromQueue(zombie);
                }
            }
        };

        switch (shootMode) {
            case SHOOT_MODE_MENU: {
                fireZombie(
                    SHOOT_MODE_BLOCK,
                    false,
                    e.querySelector(
                        'button[aria-expanded="false"][aria-haspopup="menu"][aria-label="もっと見る"]'
                    )
                );
                return;
            }
            case SHOOT_MODE_BLOCK: {
                const topMenuItem = document.querySelector(
                    "div[data-testid='Dropdown'] div[role='menuitem']"
                );
                if (!topMenuItem) {
                    return;
                }
                if (/さんのフォローを解除$/.test(topMenuItem.innerText)) {
                    debugger;
                    e.querySelector(
                        'div[aria-expanded="true"][aria-haspopup="menu"][aria-label="もっと見る"]'
                    ).click();
                    removeZombieFromQueue(zombie);
                    shootMode = SHOOT_MODE_MENU;
                    return;
                }
                fireZombie(
                    SHOOT_MODE_CONFIRM,
                    false,
                    document.querySelector(
                        'div[role="menuitem"][data-testid="block"]'
                    )
                );
                return;
            }
            case SHOOT_MODE_CONFIRM: {
                fireZombie(
                    SHOOT_MODE_MENU,
                    true,
                    document.querySelector(
                        'button[role="button"][data-testid="confirmationSheetConfirm"]'
                    )
                );
                const killedZombieCount = killedZombie(zombie);
                console.log(
                    "killed zombie named: [" +
                        zombie +
                        "], total: " +
                        killedZombieCount
                );
                console.log(
                    "\tpost is : [" +
                        post +
                        "], datetime : [" +
                        date.toLocaleString() +
                        "]"
                );
                return;
            }
            default: {
                removeZombieFromQueue(zombie);
                return;
            }
        }
    };
    let postAuthor = "";
    let nowLocation = location.href;
    const clearProperties = (o) => {
        Object.keys(o).forEach((k) => {
            delete o[k];
        });
    };
    const needSkip = () =>
        /\/(home|notifications|explore|messages|lists)/.test(nowLocation);
    const isEndOfTimeLine = () => {
        let foundMore = false;
        document
            .querySelectorAll("div[data-testid='cellInnerDiv']")
            .forEach((e) => {
                if (foundMore && e.clientHeight > 0) {
                    return;
                }
                if (
                    /もっと見つける/.test(e.innerText) &&
                    /Xから/.test(e.innerText)
                ) {
                    foundMore = true;
                }
            });
        return foundMore;
    };
    let loadingMoreReply = false;
    const cooldown = () => {
        nowLocation = location.href;
        clearProperties(zombieQueue);
        clearProperties(postQueue);
        postAuthor = "";
        loadingMoreReply = false;
        initShoot();
    };
    const searchZombie = () => {
        if (nowLocation !== location.href) {
            cooldown();
        }
        if (needSkip()) {
            return;
        }
        if (loadingMoreReply) {
            if (checkMoreReplyLoadded()) {
                loadingMoreReply = false;
            }
        } else if (checkAndClickMoreReply()) {
            loadingMoreReply = true;
            return;
        }
        document
            .querySelectorAll(
                "article[data-testid='tweet'] div[aria-labelledby][id^='id'] button.redblock-btn" +
                    ", article[data-testid='tweet']:has(button.redblock-btn):has(div[data-testid='tweetPhoto']) button.redblock-btn" +
                    ", article[data-testid='tweet']:has(button.redblock-btn):has(div[data-testid^='card']) button.redblock-btn" +
                    ", article[data-testid='tweet']:has(article .r-x572qd) button.redblock-btn"
                // + ", article[data-testid='tweet']:has(button.redblock-btn):has(img[src^='https://abs-0.twimg.com/emoji/v2/svg/']) button.redblock-btn"
                // + ", article[data-testid='tweet']:has(button.redblock-btn):has(svg[aria-label='認証済みアカウント']) button.redblock-btn"
            )
            .forEach((e) => {
                e.style.display = "block";
            });
        document.querySelectorAll("time[datetime]").forEach((t) => {
            t.innerText = new Date(new Date(t.dateTime) * 1 + 9 * 3600 * 1000)
                .toISOString()
                .substring(2, 16)
                .replace(/T/, " ");
        });

        if (isEndOfTimeLine()) {
            return;
        }
        document
            .querySelectorAll("article[data-testid='tweet']")
            .forEach((e) => {
                postAuthor = getPostedAccount(document);
                if (checkZombie(e)) {
                    targettingZombie(e);
                }
            });
    };
    cooldown();
    setInterval(shootZombie, 50);
    setInterval(searchZombie, 1000);
})();