Gender Filter

Filter chats.gr users by male, female, and other, with buttons. Counter numbers update when you reload. Made using Chatgpt for Tampermonkey Legacy.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

Advertisement:

// ==UserScript==
// @name         Gender Filter
// @namespace    tampermonkey
// @version      1.0
// @description  Filter chats.gr users by male, female, and other, with buttons. Counter numbers update when you reload. Made using Chatgpt for Tampermonkey Legacy.
// @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';

  const ITEM_SELECTOR = '.gprivate.user_item';
  const HEADER_SELECTOR = '#chat_head';
  const NATIVE_BUTTON_SELECTOR = ':scope > .head_option';

  const FILTER_CONTAINER_ID = 'tm-gender-filter-container';
  const STYLE_ID = 'tm-gender-filter-styles';
  const HIDDEN_CLASS = 'tm-gender-filter-hidden';

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

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

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

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

  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 injectStyles() {
    if (document.getElementById(STYLE_ID)) return;

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

    style.textContent = `
      .${HIDDEN_CLASS} {
        display: none !important;
      }

      #${FILTER_CONTAINER_ID} {
        display: none !important;
        float: left !important;

        width: 190px !important;
        height: 43px !important;

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

        box-sizing: border-box !important;
        overflow: hidden !important;

        background: rgb(55, 151, 233) !important;
        border-radius: 0 !important;
      }

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

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

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

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

      #${FILTER_CONTAINER_ID} .tm-filter-cell {
        display: table-cell !important;
        vertical-align: middle !important;

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

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

      #${FILTER_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;
      }

      #${FILTER_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;
      }

      #${FILTER_CONTAINER_ID} .tm-gender-button.tm-disabled {
        background: rgba(20, 20, 20, 0.35) !important;
        color: rgba(255, 255, 255, 0.85) !important;

        opacity: 0.65 !important;
        text-decoration: line-through !important;
      }

      #${FILTER_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;
      }

      #${FILTER_CONTAINER_ID} .tm-gender-count {
        margin-left: 1px !important;
        font-family: Arial, sans-serif !important;
      }

      @media (max-width: 480px) {
        #${FILTER_CONTAINER_ID} {
          width: 180px !important;
          padding-left: 3px !important;
          padding-right: 3px !important;
        }

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

        #${FILTER_CONTAINER_ID} .tm-gender-button {
          padding-left: 2px !important;
          padding-right: 2px !important;
          font-size: 10px !important;
        }

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

    document.documentElement.appendChild(style);
  }

  function createFilterContainer() {
    const existing = document.getElementById(
      FILTER_CONTAINER_ID
    );

    if (existing) return existing;

    const header = document.querySelector(HEADER_SELECTOR);

    if (!header) return null;

    const nativeButtons = header.querySelectorAll(
      NATIVE_BUTTON_SELECTOR
    );

    if (nativeButtons.length < 2) return null;

    const container = document.createElement('div');
    container.id = FILTER_CONTAINER_ID;
    container.className = 'head_option';

    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';

    for (const categoryName of Object.keys(categories)) {
      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';

      const count = document.createElement('span');
      count.className = 'tm-gender-count';

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

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

        categories[categoryName].enabled =
          !categories[categoryName].enabled;

        filterProfiles();
      });

      inner.appendChild(button);
    }

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

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

    return container;
  }

  function countInitialProfiles() {
    if (totalsCounted) return;

    const items = document.querySelectorAll(ITEM_SELECTOR);

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

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

    for (const item of items) {
      categories[getProfileCategory(item)].count++;
    }

    totalsCounted = true;
  }

  function updateButtons() {
    const container = document.getElementById(
      FILTER_CONTAINER_ID
    );

    if (!container) return;

    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;
      }

      button.classList.toggle(
        'tm-disabled',
        !category.enabled
      );

      button.setAttribute(
        'aria-pressed',
        String(category.enabled)
      );
    }
  }

  function filterProfiles() {
    filterScheduled = false;

    createFilterContainer();
    countInitialProfiles();

    for (const item of document.querySelectorAll(ITEM_SELECTOR)) {
      const category = categories[
        getProfileCategory(item)
      ];

      /*
       * Only toggle this script's own class.
       *
       * This preserves display:none set by the username-hiding
       * script or by chats.gr itself.
       */
      item.classList.toggle(
        HIDDEN_CLASS,
        !category.enabled
      );
    }

    updateButtons();
    updateFilterVisibility();
  }

  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() {
    for (const item of document.querySelectorAll(ITEM_SELECTOR)) {
      const listContainer =
        item.parentElement || item;

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

    return false;
  }

  function updateFilterVisibility() {
    const container = createFilterContainer();

    if (!container) return;

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

  function scheduleFilter() {
    if (filterScheduled) return;

    filterScheduled = true;
    requestAnimationFrame(filterProfiles);
  }

  function mutationContainsProfiles(mutation) {
    for (const node of mutation.addedNodes) {
      if (!(node instanceof Element)) continue;
      if (node.id === FILTER_CONTAINER_ID) continue;

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

    return false;
  }

  function observePage() {
    if (observer) return;

    observer = new MutationObserver(mutations => {
      let profilesChanged = false;

      for (const mutation of mutations) {
        if (
          mutation.type === 'childList' &&
          mutationContainsProfiles(mutation)
        ) {
          profilesChanged = true;
          break;
        }
      }

      if (!document.getElementById(FILTER_CONTAINER_ID)) {
        createFilterContainer();
        updateButtons();
      }

      if (profilesChanged) {
        scheduleFilter();
      } else {
        updateFilterVisibility();
      }
    });

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

    document.addEventListener(
      'click',
      () => requestAnimationFrame(
        updateFilterVisibility
      ),
      true
    );
  }

  function init() {
    injectStyles();
    createFilterContainer();
    filterProfiles();
    observePage();
  }

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