☰

chats.gr toolbar

Filter chats.gr users by gender and username.

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
    }

})();