Filter chats.gr users by male, female, and other, with buttons. Counter numbers update when you reload. Made using Chatgpt for Tampermonkey Legacy.
// ==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();
}
})();