EmeraldX

Tweaks to Emeraldchat: gender info, auto rejoin, auto-karma, and keeping chat input focused.

As of 30.10.2025. See ბოლო ვერსია.

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

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            EmeraldX
// @namespace       https://greasyfork.org/
// @version         1.10
// @description     Tweaks to Emeraldchat: gender info, auto rejoin, auto-karma, and keeping chat input focused.
// @author          Zach
// @license         GPL-3.0
// @icon            https://emeraldchat.com/logo7.svg
// @match           https://emeraldchat.com/app
// @grant           none
// @compatible      Firefox
// @compatible      Violentmonkey
// ==/UserScript==

'use strict';

// === SETTINGS MENU ===
let uiVisible = true;
let autoNextEnabled = false;
let autoKarmaEnabled = false;
let genderFilter = "all";
let currentUserGender = null;

function createSettingsButton() {
    const nav = document.querySelector(".navigation-notification-icons");
    if (!nav) return;

    nav.insertAdjacentHTML("afterbegin", `
        <span id="settings-toggle" class="material-icons navigation-notification-unit">tune</span>
        <div id="settings-menu">
            <label class="menu-btn menu-toggle">
                <span>Show Top UI</span>
                <label class="switch"><input type="checkbox" id="ui-checkbox" checked><span class="slider"></span></label>
            </label>

            <label class="menu-btn menu-toggle">
                <span>Auto Rejoin</span>
                <label class="switch"><input type="checkbox" id="auto-next-checkbox"><span class="slider"></span></label>
            </label>

            <div class="menu-btn" id="gender-filter">
                <span>Gender Preference</span>
                <div class="segmented">
                    <label><input type="radio" name="gender" value="all" checked><span>All</span></label>
                    <label><input type="radio" name="gender" value="men"><span>Men</span></label>
                    <label><input type="radio" name="gender" value="women"><span>Women</span></label>
                </div>
            </div>

            <label class="menu-btn menu-toggle">
                <span>Auto Karma</span>
                <label class="switch"><input type="checkbox" id="auto-karma-checkbox"><span class="slider"></span></label>
            </label>

            <div class="menu-btn" id="blacklist-row">
                <span>Blacklist</span>
                <input id="blacklist-input" type="text" placeholder="Enter keywords...">
            </div>
            <style>
            #settings-menu {
                position: fixed;
                display: none;
                flex-direction: column;
                gap: 0.7em;
                background: rgba(200,200,255,.12);
                border: 1px solid rgba(255,255,255,.18);
                backdrop-filter: blur(10px);
                border-radius: 0.9em;
                padding: 0.9em;
                min-width: 16em;
                color: #fff;
                font-family: "Segoe UI", Roboto, sans-serif;
                font-size: 15px;
                z-index: 1000;
                box-sizing: border-box;
                user-select: none;
            }

            .menu-btn {
                display: flex;
                justify-content: space-between;
                align-items: center;
                border: none;
                border-radius: 0.6em;
                padding: 1em 0.9em;
                line-height: 1.1;
                background: rgba(255,255,255,.1);
                color: #fff;
                cursor: pointer;
                width: 100%;
                font-size: 1em;
                -webkit-tap-highlight-color: transparent;
            }
            .menu-btn:active, .menu-btn:focus { background: rgba(255,255,255,.1); }

            #blacklist-input {
                flex: 1;
                margin-left: 0.7em;
                border: none;
                outline: none;
                border-radius: 0.6em;
                padding: 0.5em 0.8em;
                background: rgba(255,255,255,.15);
                color: #fff;
                font-size: 1em;
                font-family: inherit;
            }

            .switch {
                position: relative;
                width: 3.36em;
                height: 1.68em;
                flex-shrink: 0;
                pointer-events: none;
            }
            .switch input { opacity: 0; width: 0; height: 0; }
            .slider {
                position: absolute;
                top: 0; left: 0;
                width: 100%; height: 100%;
                background: rgba(255,255,255,.35);
                border-radius: 2em;
                transition: .25s;
            }
            .slider:before {
                content: "";
                position: absolute;
                width: 1.2em; height: 1.2em;
                left: 0.24em; top: 0.24em;
                background: white;
                border-radius: 50%;
                transition: .25s;
            }
            input:checked + .slider { background: rgba(0,150,255,.5); }
            input:checked + .slider:before { transform: translateX(1.68em); }

            .segmented {
                display: flex;
                width: 100%;
                border-radius: 0.6em;
                overflow: hidden;
                background: rgba(255,255,255,.08);
                margin-left: 0.6em;
                border: 1px solid rgba(255,255,255,.15);
            }
            .segmented label {
                flex: 1;
                display: flex;
                justify-content: center;
                align-items: center;
                cursor: pointer;
                user-select: none;
                padding: 0.45em 0;
                font-size: 1em;
                border-right: 1px solid rgba(255,255,255,.15);
                transition: background .2s, font-weight .2s;
            }
            .segmented label:last-child { border-right: none; }
            .segmented input { display: none; }
            .segmented label:hover { background: rgba(255,255,255,.12); }
            .segmented label:has(input:checked) { background: rgba(0,150,255,.4); font-weight: 500; }
            </style>
        </div>
    `);

    const toggleBtn = document.getElementById("settings-toggle");
    const menu = document.getElementById("settings-menu");
    const uiCheckbox = menu.querySelector("#ui-checkbox");
    const autoNextCheckbox = menu.querySelector("#auto-next-checkbox");
    const autoKarmaCheckbox = menu.querySelector("#auto-karma-checkbox");
    const genderRadios = menu.querySelectorAll('input[name="gender"]');

    const positionMenu = () => {
        const r = toggleBtn.getBoundingClientRect(), w = menu.offsetWidth || 240, pad = 10;
        let l = r.left + r.width / 2 - w / 2, t = r.bottom + 6;
        l = Math.min(Math.max(pad, l), innerWidth - w - pad);
        menu.style.left = l + "px";
        menu.style.top = t + "px";
    };

    document.addEventListener("click", e => {
        if (e.target === toggleBtn) {
            const show = menu.style.display !== "flex";
            menu.style.display = show ? "flex" : "none";
            if (show) positionMenu();
        } else if (!menu.contains(e.target)) {
            menu.style.display = "none";
        }
    });

    new ResizeObserver(() => menu.style.display === "flex" && positionMenu()).observe(document.body);
    addEventListener("scroll", () => menu.style.display === "flex" && positionMenu(), true);

    // Simple change event listeners
    uiCheckbox.addEventListener("change", () => {
        uiVisible = uiCheckbox.checked;
        applyTopUIVisibility();
    });

    autoNextCheckbox.addEventListener("change", () => {
        autoNextEnabled = autoNextCheckbox.checked;
    });

    autoKarmaCheckbox.addEventListener("change", () => {
        autoKarmaEnabled = autoKarmaCheckbox.checked;
    });

    genderRadios.forEach(r =>
        r.addEventListener("change", () => {
            genderFilter = r.value;
        })
    );
}
createSettingsButton();

function createUI() {
    const html = `
        <div id="top-ui">
            <div id="profile-info" class="section">
                <p id="user-gender">Gender: Not Available</p>
                <p id="user-interests">Interests: None</p>
            </div>
            <style>
                #top-ui {
                    width: 100%;
                    background: rgba(200,200,255,0.1);
                    border-bottom: 1px solid rgba(255,255,255,0.15);
                    backdrop-filter: blur(6px);
                    -webkit-backdrop-filter: blur(6px);
                    font-family: "Segoe UI", Roboto, sans-serif;
                    transition: all 0.2s ease;
                }

                .hidden {
                    height: 0;
                    opacity: 0;
                    padding: 0;
                    margin: 0;
                }

                .section {
                    display: flex;
                    flex-direction: column;
                    padding: 6px 12px;
                    gap: 4px;
                }

                #user-gender, #user-interests {
                    background: rgba(255,255,255,0.2);
                    border-radius: 8px;
                    padding: 6px 10px;
                    font-size: 13px;
                    color: #fff;
                }

                #user-gender.male { background: rgba(0,120,255,0.25); }
                #user-gender.female { background: rgba(255,105,180,0.25); }
            </style>
        </div>
    `;

    const insertUI = () => {
        const messages = document.getElementById("messages");
        if (messages && !document.getElementById("top-ui")) {
            messages.insertAdjacentHTML("beforebegin", html);
            applyTopUIVisibility();
        }
    };

    new MutationObserver(insertUI).observe(document.body, { childList: true, subtree: true });
}
createUI();

// === CHAT BEHAVIOR ===
function focusChat() {
    const chatInput = document.getElementById("room-input");
    if (!chatInput) return;

    document.body.addEventListener(
        "keydown",
        (event) => {
            if (
                !document.querySelector("#ui-hatch > *, #ui-hatch-2 > *, #interests") &&
                event.key !== "`"
            ) {
                chatInput.focus();
            }
        },
        { once: true }
    );
}

// === FETCH USER INFO ===
let lastMatchedId = null;

function simulateProfileRequest() {
    const matchEl = document.querySelector(
        '#room .room-component-center #messages .room-component-print #matched-message[data-matched-id]'
    );
    const userId = matchEl?.dataset.matchedId;
    if (!userId || userId === lastMatchedId) return;
    lastMatchedId = userId;

    fetch(`https://emeraldchat.com/profile_json?id=${userId}`)
        .then((res) => res.json())
        .then(({ user }) => {
            const genderEl = document.getElementById("user-gender");
            const interestsEl = document.getElementById("user-interests");

            const gender = user.gender === "m" ? "Male" : "Female";
            genderEl.textContent = `Gender: ${gender}`;
            genderEl.className = user.gender === "m" ? "male" : "female";

            const interests = user.interests?.map((i) => i.name).join(", ") || "None";
            interestsEl.textContent = `Interests: ${interests}`;

            // Store current user gender for filtering
            currentUserGender = user.gender;

            // Check if we should skip this user based on gender filter
            checkAndSkipIfNeeded();
        })
        .catch((err) => console.error("Profile fetch failed:", err));
}

// === CHECK GENDER AND SKIP ===
function checkAndSkipIfNeeded() {
    if (genderFilter === "all") return;

    const shouldSkip =
        (genderFilter === "men" && currentUserGender !== "m") ||
        (genderFilter === "women" && currentUserGender !== "f");

    if (shouldSkip) {
        console.log(`Skipping user - Filter: ${genderFilter}, User: ${currentUserGender}`);
        skipToNextChat();
    }
}

function skipToNextChat() {
    const nextButton = document.querySelector("div.ui-button-match");
    if (nextButton) {
        // Click twice to skip to next chat
        simulateClick(nextButton);
        setTimeout(() => {
            simulateClick(nextButton);
        }, 100);
    }
}

// === SIMULATED CLICK ===
function simulateClick(el) {
    ["mousedown", "mouseup", "click"].forEach((type) =>
        el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true }))
    );
}

function nextChat() {
    if (!autoNextEnabled) return;
    const next = document.querySelector("div.ui-button-match-mega");
    if (next) simulateClick(next);
}

function giveKarma() {
    if (!autoKarmaEnabled) return;
    const [good, bad] = document.querySelectorAll("a.ui-button-match-mega");
    if (!good || !bad) return;

    const messages = document.querySelectorAll("#messages > .room-component-message-container").length;
    if (messages > 5) simulateClick(good);
    // else if (messages < 2) simulateClick(bad);
}


// === OBSERVER ===
function observeChanges() {
    const container = document.getElementById("container");
    if (!container) return;

    new MutationObserver(() => {
        giveKarma();
        nextChat();
        simulateProfileRequest();
        focusChat();
    }).observe(container, { childList: true, subtree: true });
}

// === HELPERS ===
function applyTopUIVisibility() {
    const ui = document.getElementById("top-ui");
    if (ui) ui.classList.toggle("hidden", !uiVisible);
}

// === INIT ===
createUI();
observeChanges();