Media mode for Twitter home

Remove text-only tweets on the flow of Twitter home/list. It is currently Beta quality.

// vim: set ts=4 sw=4 expandtab:

// ==UserScript==
// @name               Media mode for Twitter home
// @name:zh-CN         Twitter 主页上的媒体模式
// @name:zh-TW         Twitter 主頁上的媒體模式
// @name:zh-HK         Twitter 主頁上的媒體模式
// @description        Remove text-only tweets on the flow of Twitter home/list. It is currently Beta quality.
// @description:zh-CN  在 Twitter 的主页和列表时间流上删除纯文本 Tweet。当前是 Beta 质量
// @description:zh-TW  在 Twitter 的主頁和列表時間流上刪除純文字 Tweet。當前是 Beta 質量
// @description:zh-HK  在 Twitter 的主頁和列表時間流上刪除純文本 Tweet。當前是 Beta 質量
// @icon               https://i.imgur.com/bUIPv1O.jpg
// @namespace          https://github.com/UtopicPanther/userscript-twitter-home-media
// @supportURL         https://github.com/UtopicPanther/userscript-twitter-home-media/issues
// @version            0.7.1
// @author             UtopicPanther
// @license            GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
// @match              https://twitter.com/*
// @match              https://mobile.twitter.com/*
// @grant              GM_registerMenuCommand
// @run-at             document-idle
// ==/UserScript==

/*
 *  Copyright (C) 2020 UtopicPanther
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 *  This script contains an additional exemption. When this script is
 *  injected into the site with the original setting of `@match`, 
 *  other user scripts running in the same space are not required to
 *  be compatible with GPL 3.
 *
 *  这个脚本包含一个附加豁免。当此脚本被注入到原有设置 `@match` 的
 *  站点时,不要求同一空间中运行的其他用户脚本与 GPL 3 兼容。
 */

(function() {
    'use strict';

    let hide = true;

    const removeTweet = article => {
        article.classList.add('mmfth_hide');
        const div = article.parentElement.parentElement;
        div.style.background = "red";
        if (hide)
            div.style.display = "none";
    }

    const showTweet = article => {
        console.log("showTweet: %O", article)
        article.classList.remove('mmfth_hide');
        const div = article.parentElement.parentElement;
        div.style.background = "";
        div.style.display = "";
    }

    const isTweetOnlyText = i => {
        if (Array.from(i.querySelectorAll('img')).some(img => {
            if (!img.src.match(/^[a-z]*:\/\/[^\/]*\/profile_images/) &&
                !img.src.match(/^[a-z]*:\/\/[^\/]*\/emoji/)) {
                return true;
            }
        })) return false;

        if (i.querySelector('div[data-testid=tweetPhoto]') != null) return false;

        if (Array.from(i.querySelectorAll('a')).some(a => {
            if (a.getAttribute('href').match(/^\/[^\/]*\/status\/[0-9]*\/photo\//)) {
                return true;
            }
        })) return false;

        if (i.querySelector('video') != null) return false;

        if (i.querySelector('div[role=progressbar]') != null) return false;

        let emptyMiddle = true;

        try {
            const tweet = i.querySelector('div[data-testid=tweet]');
            const tmp = tweet.children[1].children[1];
            const middle = tmp.children[tmp.length - 2];

            if (middle.children.length > 0)
                emptyMiddle = false;
        } catch (e) {}

        return emptyMiddle;
    }

    const findTweetsForRemove = () => {
        if (location.pathname.startsWith('/home') ||
            location.pathname.startsWith('/i/lists/')) {
            document.querySelectorAll('article:not(.mmfth_hide)').forEach(i => {
                if (isTweetOnlyText(i)) {
                    removeTweet(i);
                }
            });
            document.querySelectorAll('.mmfth_hide').forEach(i => {
                if (!isTweetOnlyText(i)) {
                    showTweet(i);
                }
            });
        }
    }

    const startObserver = () => {
        //const targetNode = document.querySelector('article').parentElement.parentElement.parentElement.parentElement;
        const targetNode = document.documentElement || document.body;

        findTweetsForRemove();

        const config = { childList: true, subtree: true };
        const observer = new MutationObserver((mutationsList, observer) => {
            findTweetsForRemove();
        });
        observer.observe(targetNode, config);
    }

    GM_registerMenuCommand("Show/Hide text-only tweets", () => {
        hide = !hide;
        alert("Text-only tweers will be " + (hide ? "hidden" : "shown (with red background)"));
        document.querySelectorAll('.mmfth_hide').forEach(i => {
            i.parentElement.parentElement.style.display = (hide ? "none" : "");
        });
    });

    setTimeout(() => {
        startObserver();
    }, 6000);
})();