Impression Zombie Buster(mod)

Auto fire to Impression Zombies with Japanese Twitter!

// ==UserScript==
// @name         Impression Zombie Buster(mod)
// @namespace    http://tampermonkey.net/
// @version      2025-09-18
// @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)/.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);
})();