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.

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 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!)

Advertisement:

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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