☰

chats.gr toolbar

Filter chats.gr users by gender and username.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         chats.gr toolbar
// @namespace    tampermonkey
// @version      2.3
// @description  Filter chats.gr users by gender and username.
// @author       unknownposter025
// @license      MIT
// @match        *://chats.gr/app/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chats.gr
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    /* =========================================================
       CONFIG
       ========================================================= */

    const HEADER_SELECTOR = '#chat_head';
    const USER_ITEM_SELECTOR = '.user_item';
    const USERNAME_SELECTOR = 'p.username.user';

    const GENDER_CONTAINER_ID = 'tm-gender-filter-container';
    const SEARCH_CONTAINER_ID = 'tm-word-search-container';
    const SEARCH_INPUT_ID = 'tm-word-search-input';

    const STYLE_ID = 'tm-user-filter-styles';

    const GENDER_HIDDEN_CLASS = 'tm-gender-filter-hidden';
    const SEARCH_HIDDEN_CLASS = 'tm-word-search-hidden';

    const CONTROLS_CLASS = 'tm-user-filter-control';

    const CONTROL_BACKGROUND = '#1077cf';

    const SEARCH_WIDTH = '150px';

    const categories = {
        male: {
            emoji: '♂️',
            className: 'malebg',
            count: 0
        },

        female: {
            emoji: '♀️',
            className: 'femalebg',
            count: 0
        },

        other: {
            emoji: '⚧️',
            className: 'otherbg',
            count: 0
        }
    };


    /* =========================================================
       STATE
       ========================================================= */

    let activeGender = null;

    let observer = null;
    let filterScheduled = false;
    let totalsCounted = false;


    /* =========================================================
       STYLES
       ========================================================= */

    function injectStyles() {
        if (document.getElementById(STYLE_ID)) {
            return;
        }

        const style = document.createElement('style');

        style.id = STYLE_ID;

        style.textContent = `

            /* -------------------------------------------------
               FILTER CLASSES
               ------------------------------------------------- */

            .${GENDER_HIDDEN_CLASS},
            .${SEARCH_HIDDEN_CLASS} {
                display: none !important;
            }


            /* -------------------------------------------------
               SHARED CONTROL PARENTS
               ------------------------------------------------- */

            #${GENDER_CONTAINER_ID},
            #${SEARCH_CONTAINER_ID} {
                float: left !important;

                height: 43px !important;

                margin: 0 !important;
                padding: 0 5px !important;

                box-sizing: border-box !important;

                background: ${CONTROL_BACKGROUND} !important;
                border-radius: 0 !important;

                overflow: hidden !important;
            }


            /* -------------------------------------------------
               GENDER FILTER
               ------------------------------------------------- */

            #${GENDER_CONTAINER_ID} {
                display: none !important;

                width: 230px !important;
            }

            #${GENDER_CONTAINER_ID}.tm-visible {
                display: block !important;
            }

            #${GENDER_CONTAINER_ID} > .btable {
                display: table !important;
                table-layout: fixed !important;

                width: 100% !important;
                height: 43px !important;

                margin: 0 !important;
                padding: 0 !important;
            }

            #${GENDER_CONTAINER_ID} .tm-filter-cell {
                display: table-cell !important;

                width: 100% !important;
                height: 43px !important;

                margin: 0 !important;
                padding: 0 !important;

                vertical-align: middle !important;
            }

            #${GENDER_CONTAINER_ID} .tm-filter-inner {
                display: flex !important;
                align-items: center !important;

                width: 100% !important;
                height: 31px !important;

                gap: 4px !important;

                margin: 0 !important;
                padding: 0 !important;

                box-sizing: border-box !important;

                white-space: nowrap !important;
            }


            /* -------------------------------------------------
               ALL FOUR BUTTONS
               ------------------------------------------------- */

            #${GENDER_CONTAINER_ID} .tm-gender-button {
                appearance: none !important;

                flex: 1 1 0 !important;
                min-width: 0 !important;

                height: 27px !important;

                margin: 0 !important;
                padding: 1px 4px !important;

                border: 1px solid rgba(255, 255, 255, 0.65) !important;
                border-radius: 5px !important;

                background: rgba(255, 255, 255, 0.95) !important;
                color: #111111 !important;

                font-family: Arial, sans-serif !important;
                font-size: 11px !important;
                font-weight: 700 !important;
                line-height: 23px !important;

                text-align: center !important;
                white-space: nowrap !important;

                overflow: hidden !important;

                cursor: pointer !important;
                touch-action: manipulation !important;

                outline: none !important;
                box-shadow: none !important;
            }


            /*
             * Buttons only look pressed while physically
             * being pressed. No persistent active state.
             */
            #${GENDER_CONTAINER_ID}
            .tm-gender-button:active {
                background: rgba(225, 225, 225, 1) !important;
            }


            #${GENDER_CONTAINER_ID} .tm-gender-emoji {
                font-family:
                    "Noto Color Emoji",
                    "Segoe UI Emoji",
                    "Apple Color Emoji",
                    sans-serif !important;

                font-size: 13px !important;
                font-weight: normal !important;
                line-height: 1 !important;

                vertical-align: -1px !important;
            }

            #${GENDER_CONTAINER_ID} .tm-gender-count {
                margin-left: 1px !important;

                font-family: Arial, sans-serif !important;
            }


            /* -------------------------------------------------
               WORD SEARCH
               ------------------------------------------------- */

            #${SEARCH_CONTAINER_ID} {
                display: none !important;

                align-items: center !important;
                justify-content: center !important;

                width: ${SEARCH_WIDTH} !important;
            }

            #${SEARCH_CONTAINER_ID}.tm-visible {
                display: flex !important;
            }

            #${SEARCH_CONTAINER_ID} .tm-word-search-inner {
                display: flex !important;
                align-items: center !important;

                width: 100% !important;
                height: 31px !important;

                margin: 0 !important;
                padding: 0 !important;

                box-sizing: border-box !important;
            }

            #${SEARCH_INPUT_ID} {
                display: block !important;

                width: 100% !important;
                height: 27px !important;

                min-width: 0 !important;

                margin: 0 !important;
                padding: 1px 8px !important;

                box-sizing: border-box !important;

                border: 1px solid rgba(255, 255, 255, 0.65) !important;
                border-radius: 5px !important;

                background: rgba(255, 255, 255, 0.95) !important;
                color: #111111 !important;

                font-family: Arial, sans-serif !important;
                font-size: 11px !important;
                font-weight: 700 !important;
                line-height: 23px !important;

                outline: none !important;
                box-shadow: none !important;
            }

            #${SEARCH_INPUT_ID}::placeholder {
                color: #666666 !important;
                opacity: 1 !important;
            }

            #${SEARCH_INPUT_ID}:focus {
                border-color: rgba(255, 255, 255, 0.95) !important;
            }


            /* -------------------------------------------------
               MOBILE
               ------------------------------------------------- */

            @media (max-width: 480px) {

                #${GENDER_CONTAINER_ID} {
                    width: 220px !important;

                    padding-left: 3px !important;
                    padding-right: 3px !important;
                }

                #${SEARCH_CONTAINER_ID} {
                    width: 170px !important;

                    padding-left: 3px !important;
                    padding-right: 3px !important;
                }

                #${GENDER_CONTAINER_ID} .tm-filter-inner {
                    gap: 3px !important;
                }

                #${GENDER_CONTAINER_ID} .tm-gender-button {
                    flex: 1 1 0 !important;
                    min-width: 0 !important;

                    height: 27px !important;

                    padding-left: 2px !important;
                    padding-right: 2px !important;

                    font-size: 10px !important;
                }

                #${GENDER_CONTAINER_ID} .tm-gender-emoji {
                    font-size: 12px !important;
                }

                #${SEARCH_INPUT_ID} {
                    padding-left: 6px !important;
                    padding-right: 6px !important;

                    font-size: 10px !important;
                }
            }
        `;

        document.documentElement.appendChild(style);
    }


    /* =========================================================
       USER / GENDER HELPERS
       ========================================================= */

    function hasClassCaseInsensitive(element, wantedClass) {
        const target = wantedClass.toLowerCase();

        return Array.from(element.classList).some(
            className =>
                className.toLowerCase() === target
        );
    }


    function getProfileCategory(item) {
        if (
            hasClassCaseInsensitive(
                item,
                categories.male.className
            )
        ) {
            return 'male';
        }

        if (
            hasClassCaseInsensitive(
                item,
                categories.female.className
            )
        ) {
            return 'female';
        }

        if (
            hasClassCaseInsensitive(
                item,
                categories.other.className
            )
        ) {
            return 'other';
        }

        return 'other';
    }


    function getUserItems() {
        return document.querySelectorAll(
            USER_ITEM_SELECTOR
        );
    }


    /* =========================================================
       GENDER COUNTS
       ========================================================= */

    function countInitialProfiles() {
        if (totalsCounted) {
            return;
        }

        const items = getUserItems();

        if (items.length === 0) {
            return;
        }

        for (const category of Object.values(categories)) {
            category.count = 0;
        }

        for (const item of items) {
            const category = getProfileCategory(item);

            categories[category].count++;
        }

        totalsCounted = true;
    }


    function getTotalProfileCount() {
        return Object.values(categories)
            .reduce(
                (total, category) =>
                    total + category.count,
                0
            );
    }


    /* =========================================================
       GENDER FILTER
       ========================================================= */

    function createGenderFilter() {
        const existing =
            document.getElementById(
                GENDER_CONTAINER_ID
            );

        if (existing) {
            return existing;
        }

        const header =
            document.querySelector(
                HEADER_SELECTOR
            );

        if (!header) {
            return null;
        }

        const nativeOptions =
            header.querySelectorAll(
                ':scope > .head_option'
            );

        if (nativeOptions.length < 2) {
            return null;
        }

        const container =
            document.createElement('div');

        container.id =
            GENDER_CONTAINER_ID;

        container.className =
            `head_option ${CONTROLS_CLASS}`;

        const table =
            document.createElement('div');

        table.className =
            'btable';

        const cell =
            document.createElement('div');

        cell.className =
            'bcell_mid tm-filter-cell';

        const inner =
            document.createElement('div');

        inner.className =
            'tm-filter-inner';


        /* -----------------------------------------------------
           THREE GENDER BUTTONS
           ----------------------------------------------------- */

        for (
            const categoryName
            of Object.keys(categories)
        ) {
            const category =
                categories[categoryName];

            const button =
                document.createElement('button');

            button.type = 'button';

            button.className =
                'tm-gender-button';

            button.dataset.category =
                categoryName;

            const emoji =
                document.createElement('span');

            emoji.className =
                'tm-gender-emoji';

            emoji.textContent =
                category.emoji;

            const count =
                document.createElement('span');

            count.className =
                'tm-gender-count';

            count.textContent =
                category.count;

            button.appendChild(emoji);
            button.appendChild(count);


            button.addEventListener(
                'click',
                event => {
                    event.preventDefault();
                    event.stopPropagation();

                    activeGender =
                        categoryName;

                    applyFilters();
                }
            );

            inner.appendChild(button);
        }


        /* -----------------------------------------------------
           RESET / ALL BUTTON
           ----------------------------------------------------- */

        const resetButton =
            document.createElement('button');

        resetButton.type = 'button';

        resetButton.className =
            'tm-gender-button';

        resetButton.dataset.category =
            'reset';

        const resetEmoji =
            document.createElement('span');

        resetEmoji.className =
            'tm-gender-emoji';

        resetEmoji.textContent =
            '✳️';

        const resetCount =
            document.createElement('span');

        resetCount.className =
            'tm-gender-count';

        resetCount.textContent =
            getTotalProfileCount();

        resetButton.appendChild(resetEmoji);
        resetButton.appendChild(resetCount);


        resetButton.addEventListener(
            'click',
            event => {
                event.preventDefault();
                event.stopPropagation();

                activeGender = null;

                applyFilters();
            }
        );

        inner.appendChild(resetButton);


        cell.appendChild(inner);
        table.appendChild(cell);
        container.appendChild(table);


        nativeOptions[0].insertAdjacentElement(
            'afterend',
            container
        );

        return container;
    }


    function updateGenderButtons() {
        const container =
            document.getElementById(
                GENDER_CONTAINER_ID
            );

        if (!container) {
            return;
        }

        /*
         * Update the three gender counts.
         */
        for (
            const [
                categoryName,
                category
            ]
            of Object.entries(categories)
        ) {
            const button =
                container.querySelector(
                    `[data-category="${categoryName}"]`
                );

            if (!button) {
                continue;
            }

            const emoji =
                button.querySelector(
                    '.tm-gender-emoji'
                );

            const count =
                button.querySelector(
                    '.tm-gender-count'
                );

            if (emoji) {
                emoji.textContent =
                    category.emoji;
            }

            if (count) {
                count.textContent =
                    category.count;
            }
        }


        /*
         * Update the ✳️ total count.
         *
         * This is the sum of the same initial counts
         * used by the three gender buttons.
         */
        const resetButton =
            container.querySelector(
                '[data-category="reset"]'
            );

        if (resetButton) {
            const count =
                resetButton.querySelector(
                    '.tm-gender-count'
                );

            if (count) {
                count.textContent =
                    getTotalProfileCount();
            }
        }
    }


    function applyGenderFilter() {
        for (const item of getUserItems()) {
            const category =
                getProfileCategory(item);

            const shouldHide =
                activeGender !== null &&
                category !== activeGender;

            item.classList.toggle(
                GENDER_HIDDEN_CLASS,
                shouldHide
            );
        }

        updateGenderButtons();
    }


    /* =========================================================
       WORD SEARCH
       ========================================================= */

    function createSearchBar() {
        const existing =
            document.getElementById(
                SEARCH_CONTAINER_ID
            );

        if (existing) {
            return existing;
        }

        const header =
            document.querySelector(
                HEADER_SELECTOR
            );

        if (!header) {
            return null;
        }

        const container =
            document.createElement('div');

        container.id =
            SEARCH_CONTAINER_ID;

        container.className =
            `head_option ${CONTROLS_CLASS}`;

        const inner =
            document.createElement('div');

        inner.className =
            'tm-word-search-inner';

        const input =
            document.createElement('input');

        input.id =
            SEARCH_INPUT_ID;

        input.type =
            'text';

        input.autocomplete =
            'off';

        input.spellcheck =
            false;

        input.placeholder =
            'Search usernames...';


        input.addEventListener(
            'input',
            scheduleFilter
        );


        input.addEventListener(
            'keydown',
            event => {
                if (event.key !== 'Escape') {
                    return;
                }

                input.value = '';

                applyFilters();

                input.blur();
            }
        );


        inner.appendChild(input);
        container.appendChild(inner);


        const genderFilter =
            document.getElementById(
                GENDER_CONTAINER_ID
            );

        if (
            genderFilter &&
            genderFilter.parentElement === header
        ) {
            genderFilter.insertAdjacentElement(
                'afterend',
                container
            );
        } else {
            const nativeOptions =
                header.querySelectorAll(
                    ':scope > .head_option'
                );

            if (nativeOptions.length > 0) {
                nativeOptions[0].insertAdjacentElement(
                    'afterend',
                    container
                );
            } else {
                header.appendChild(container);
            }
        }

        return container;
    }


    function applySearchFilter() {
        const input =
            document.getElementById(
                SEARCH_INPUT_ID
            );

        if (!input) {
            return;
        }

        const keyword =
            input.value
                .trim()
                .toLocaleLowerCase();

        for (const item of getUserItems()) {
            const username =
                item.querySelector(
                    USERNAME_SELECTOR
                );

            if (!username) {
                continue;
            }

            const usernameText =
                username.textContent
                    .trim()
                    .toLocaleLowerCase();

            const matches =
                keyword === '' ||
                usernameText.includes(keyword);

            item.classList.toggle(
                SEARCH_HIDDEN_CLASS,
                !matches
            );
        }
    }


    /* =========================================================
       LIST VISIBILITY
       ========================================================= */

    function isElementVisible(element) {
        if (!(element instanceof Element)) {
            return false;
        }

        for (
            let current = element;
            current &&
            current !== document.documentElement;
            current = current.parentElement
        ) {
            const style =
                getComputedStyle(current);

            if (
                style.display === 'none' ||
                style.visibility === 'hidden' ||
                style.visibility === 'collapse'
            ) {
                return false;
            }
        }

        const rect =
            element.getBoundingClientRect();

        return (
            rect.width > 0 &&
            rect.height > 0
        );
    }


    function isUserListVisible() {
        const items =
            getUserItems();

        for (const item of items) {
            const container =
                item.parentElement || item;

            if (
                isElementVisible(container)
            ) {
                return true;
            }
        }

        return false;
    }


    function updateControlVisibility() {
        const gender =
            document.getElementById(
                GENDER_CONTAINER_ID
            );

        const search =
            document.getElementById(
                SEARCH_CONTAINER_ID
            );

        if (!gender || !search) {
            return;
        }

        const visible =
            isUserListVisible();

        gender.classList.toggle(
            'tm-visible',
            visible
        );

        search.classList.toggle(
            'tm-visible',
            visible
        );
    }


    /* =========================================================
       CONTROL POSITION
       ========================================================= */

    function keepControlsTogether() {
        const header =
            document.querySelector(
                HEADER_SELECTOR
            );

        if (!header) {
            return;
        }

        const gender =
            document.getElementById(
                GENDER_CONTAINER_ID
            );

        const search =
            document.getElementById(
                SEARCH_CONTAINER_ID
            );

        if (!gender || !search) {
            return;
        }

        if (
            gender.parentElement !== header ||
            search.parentElement !== header
        ) {
            return;
        }

        if (
            search.previousElementSibling !== gender
        ) {
            gender.insertAdjacentElement(
                'afterend',
                search
            );
        }
    }


    /* =========================================================
       COMBINED FILTERING
       ========================================================= */

    function applyFilters() {
        filterScheduled = false;

        createGenderFilter();
        createSearchBar();

        countInitialProfiles();

        applyGenderFilter();
        applySearchFilter();

        keepControlsTogether();
        updateControlVisibility();
    }


    function scheduleFilter() {
        if (filterScheduled) {
            return;
        }

        filterScheduled = true;

        requestAnimationFrame(
            applyFilters
        );
    }


    /* =========================================================
       MUTATION DETECTION
       ========================================================= */

    function mutationContainsUser(mutation) {
        for (const node of mutation.addedNodes) {
            if (!(node instanceof Element)) {
                continue;
            }

            if (
                node.id === GENDER_CONTAINER_ID ||
                node.id === SEARCH_CONTAINER_ID
            ) {
                continue;
            }

            if (
                node.matches(
                    USER_ITEM_SELECTOR
                ) ||
                node.querySelector(
                    USER_ITEM_SELECTOR
                )
            ) {
                return true;
            }
        }

        return false;
    }


    /* =========================================================
       OBSERVER
       ========================================================= */

    function observePage() {
        if (observer) {
            return;
        }

        observer =
            new MutationObserver(
                mutations => {
                    let usersChanged = false;
                    let controlsMissing = false;

                    for (const mutation of mutations) {
                        if (
                            mutation.type ===
                                'childList' &&
                            mutationContainsUser(
                                mutation
                            )
                        ) {
                            usersChanged = true;
                        }
                    }

                    if (
                        !document.getElementById(
                            GENDER_CONTAINER_ID
                        ) ||
                        !document.getElementById(
                            SEARCH_CONTAINER_ID
                        )
                    ) {
                        controlsMissing = true;
                    }

                    if (controlsMissing) {
                        createGenderFilter();
                        createSearchBar();

                        updateGenderButtons();
                    }

                    if (usersChanged) {
                        scheduleFilter();
                        return;
                    }

                    keepControlsTogether();
                    updateControlVisibility();
                }
            );


        observer.observe(
            document.body,
            {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: [
                    'class',
                    'style',
                    'hidden'
                ]
            }
        );


        document.addEventListener(
            'click',
            () => {
                requestAnimationFrame(() => {
                    keepControlsTogether();
                    updateControlVisibility();
                });
            },
            true
        );
    }


    /* =========================================================
       INITIALIZATION
       ========================================================= */

    function init() {
        injectStyles();

        createGenderFilter();
        createSearchBar();

        applyFilters();

        observePage();
    }


    if (
        document.readyState ===
        'loading'
    ) {
        document.addEventListener(
            'DOMContentLoaded',
            init,
            { once: true }
        );
    } else {
        init();
    }

})();