EmeraldX

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

Version au 30/10/2025. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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