Greasy Fork

Greasy Fork is available in English.

Gartic.io 恢复聊天 & 移动端适配

恢复 Gartic.io 中被删除的聊天功能,允许无限聊天。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            Fiu Gartic.io Chat Fix
// @name:tr         Gartic.io Sohbeti Geri Getirme & Mobil Uyum
// @name:en         Gartic.io Restore Chat & Mobile Adaptive
// @name:az         Gartic.io Söhbəti Bərpa Et & Mobil Uyğunluq
// @name:ru         Gartic.io Восстановление чата и мобильная адаптация
// @name:ms         Gartic.io Pulihkan Sembang & Mudah Alih
// @name:id         Gartic.io Pulihkan Obrolan & Adaptif Seluler
// @name:ca         Gartic.io Restaura el xat & Adaptació mòbil
// @name:nl         Gartic.io Chat Herstellen & Mobiele Aanpassing
// @name:da         Gartic.io Gendan Chat & Mobil Tilpasning
// @name:et         Gartic.io Taasta Vestlus & Mobiilne Kohandamine
// @name:de         Gartic.io Chat Wiederherstellen & Mobile Anpassung
// @name:es         Gartic.io Restaurar chat y adaptación móvil
// @name:fr         Gartic.io Restaurer le chat et adaptation mobile
// @name:it         Gartic.io Ripristina chat e adattamento mobile
// @name:hu         Gartic.io Chat Visszaállítása & Mobil Alkalmazkodás
// @name:pl         Gartic.io Przywracanie czatu i adaptacja mobilna
// @name:ro         Gartic.io Restabilire chat și adaptare mobilă
// @name:sk         Gartic.io Obnovenie chatu a mobilná adaptácia
// @name:vi         Gartic.io Khôi phục trò chuyện & Thích ứng di động
// @name:bg         Gartic.io Възстановяване на чата и мобилна адаптация
// @name:hr         Gartic.io Vraćanje chata i mobilna prilagodba
// @name:fi         Gartic.io Chatin palautus & Mobiilisovitus
// @name:sv         Gartic.io Återställ chatt & Mobilanpassning
// @name:el         Gartic.io Επαναφορά συνομιλίας & Προσαρμογή για κινητά
// @name:sr         Gartic.io Vraćanje četa i mobilna adaptacija
// @name:uk         Gartic.io Відновлення чату і мобільна адаптація
// @name:ar         Gartic.io استعادة الدردشة والتكيف مع الهاتف المحمول
// @name:fa         Gartic.io بازیابی چت و سازگاری با موبایل
// @name:th         Gartic.io กู้คืนแชท & รองรับมือถือ
// @name:zh-CN      Gartic.io 恢复聊天 & 移动端适配
// @name:zh-TW      Gartic.io 恢復聊天 & 移動端適應
// @name:ja         Gartic.io チャット復元&モバイル対応
// @name:ko         Gartic.io 채팅 복구 & 모바일 최적화
// @name:hi         Gartic.io चैट बहाली और मोबाइल अनुकूलन
// @version         2.4
// @description     Restores the removed chat for Gartic.io and allows you to chat without limits.
// @description:tr  Gartic.io için kaldırılan sohbeti geri getirir ve sınırsız sohbet etmenizi sağlar.
// @description:en  Restores the removed chat for Gartic.io and allows you to chat without limits.
// @description:az  Gartic.io üçün silinmiş söhbəti bərpa edir və limitsiz söhbət etməyə imkan verir.
// @description:ru  Восстанавливает удаленный чат в Gartic.io и позволяет общаться без ограничений.
// @description:ms  Mengembalikan sembang yang dialih keluar untuk Gartic.io dan membolehkan anda bersembang tanpa had.
// @description:id  Mengembalikan obrolan yang dihapus untuk Gartic.io dan memungkinkan Anda mengobrol tanpa batas.
// @description:ca  Restaura el xat eliminat per a Gartic.io i us permet xatejar sense límits.
// @description:nl  Herstelt de verwijderde chat voor Gartic.io en stelt u in staat om onbeperkt te chatten.
// @description:da  Gendanner den fjernede chat til Gartic.io og giver dig mulighed for at chatte uden grænser.
// @description:et  Taastab Gartic.io eemaldatud vestluse ja võimaldab teil piiranguteta vestelda.
// @description:de  Stellt den entfernten Chat für Gartic.io wieder her und ermöglicht unbegrenztes Chatten.
// @description:es  Restaura el chat eliminado para Gartic.io y te permite chatear sin límites.
// @description:fr  Restaure le chat supprimé pour Gartic.io et vous permet de discuter sans limites.
// @description:it  Ripristina la chat rimossa per Gartic.io e ti consente di chattare senza limiti.
// @description:hu  Visszaállítja az eltávolított chatet a Gartic.io-hoz, és korlátlan chatelést tesz lehetővé.
// @description:pl  Przywraca usunięty czat w Gartic.io i pozwala na czatowanie bez ograniczeń.
// @description:ro  Restabilește chat-ul eliminat pentru Gartic.io și vă permite să chatuiți fără limite.
// @description:sk  Obnovuje odstránený chat pre Gartic.io a umožňuje vám chatovať bez obmedzení.
// @description:vi  Khôi phục trò chuyện đã bị xóa cho Gartic.io và cho phép bạn trò chuyện không giới hạn.
// @description:bg  Възстановява премахнатия чат за Gartic.io и ви позволява да чатите без ограничения.
// @description:hr  Vraća uklonjeni chat za Gartic.io i omogućuje vam neograničeno čavrljanje.
// @description:fi  Palauttaa poistetun chatin Gartic.io-sivustolle ja mahdollistaa rajattoman chatin.
// @description:sv  Återställer den borttagna chatten för Gartic.io och låter dig chatta utan gränser.
// @description:el  Επαναφέρει την κατειλημμένη συνομιλία για το Gartic.io και σας επιτρέπει να συνομιλείτε χωρίς όρια.
// @description:sr  Vraća uklonjeni čet za Gartic.io i omogućava vam da ćaskate bez ograničenja.
// @description:uk  Відновлює видалений чат для Gartic.io і дозволяє спілкуватися без обмежень.
// @description:ar  يستعيد الدردشة المحذوفة لـ Gartic.io ويسمح لك بالدردشة دون حدود.
// @description:fa  چت حذف شده Gartic.io را بازیابی می کند و به شما اجازه می دهد بدون محدودیت چت کنید.
// @description:th  คืนค่าแชทที่ถูกลบสำหรับ Gartic.io และช่วยให้คุณแชทได้โดยไม่จำกัด
// @description:zh-CN 恢复 Gartic.io 中被删除的聊天功能,允许无限聊天。
// @description:zh-TW 恢復 Gartic.io 中被刪除的聊天功能,允許無限聊天。
// @description:ja  Gartic.io の削除されたチャットを復元し、無制限にチャットできるようにします。
// @description:ko  Gartic.io의 삭제된 채팅을 복구하고 무제한 채팅을 가능하게 합니다.
// @description:hi  Gartic.io के लिए हटाए गए चैट को पुनर्स्थापित करता है और आपको असीमित चैट करने की अनुमति देता है।
// @author          Fiu
// @match           *://gartic.io/*
// @match           gartic.io/*
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_addValueChangeListener
// @grant           GM_addStyle
// @namespace       http://greasyfork.icu/users/1591289
// ==/UserScript==

(function () {
    "use strict";

    const PANEL_ID = "fiu-mini-messenger";
    const STORAGE_KEY = "yk-mini-messenger-history";
    const UI_STORAGE_KEY = "yk-mini-messenger-ui";
    const MAX_HISTORY = 150;
    const trackedSockets = new Set();
    const SCRIPT_VERSION = (globalThis.GM_info && globalThis.GM_info.script && globalThis.GM_info.script.version) || "2.1";

    const defaultUiState = {
        top: 80,
        left: null,
        width: 340,
        height: 460,
        hidden: false,
        sendMode: "both", // chat|answer|both
        chatColor: "#14436d"
    };

    let uiState = loadUiState();
    let messageListEl;
    let inputEl;
    let sendButtonEl;
    let panelEl;
    let dockTabEl;
    let sendModeSelectEl;
    let activeSocket = null;
    let roomOrUserId = null;
    let myUserId = null;
    let wsInitDone = false;
    let dragState = null;
    let antiAfkTimer = null;
    let dragPointerId = null;
    let resizeState = null;
    let resizePointerId = null;
    let colorDots = [];
    let avatarLightboxEl = null;
    let avatarLightboxImgEl = null;
    let avatarLightboxOpenUrl = "";
    let roomCodeEl = null;
    let unreadCount = 0;
    let announcedRoomId = null;
    const votedUsers = new Set();
    const recentSelfEcho = [];
    const usersById = new Map();

    function loadUiState() {
        const raw = localStorage.getItem(UI_STORAGE_KEY);
        if (!raw) {
            return { ...defaultUiState };
        }
        try {
            const parsed = JSON.parse(raw);
            const merged = { ...defaultUiState, ...(parsed || {}) };
            merged.top = Number.isFinite(Number(merged.top)) ? Number(merged.top) : defaultUiState.top;
            merged.left = merged.left == null ? null : (Number.isFinite(Number(merged.left)) ? Number(merged.left) : null);
            merged.width = Number.isFinite(Number(merged.width)) ? Number(merged.width) : defaultUiState.width;
            merged.height = Number.isFinite(Number(merged.height)) ? Number(merged.height) : defaultUiState.height;
            return merged;
        } catch (_error) {
            return { ...defaultUiState };
        }
    }

    function saveUiState() {
        localStorage.setItem(UI_STORAGE_KEY, JSON.stringify(uiState));
    }

    function createUI() {
        if (document.getElementById(PANEL_ID)) {
            return;
        }

        panelEl = document.createElement("div");
        panelEl.id = PANEL_ID;
        panelEl.innerHTML = `
            <div class="fiu-mm-header">
                <span class="fiu-mm-title">Fiu Chat v${SCRIPT_VERSION}</span>
                <div class="fiu-mm-actions">
                    <div class="fiu-mm-color-palette">
                        <button class="fiu-mm-color-dot" type="button" data-color="#14436d" title="Mavi"></button>
                        <button class="fiu-mm-color-dot" type="button" data-color="#eb58a0" title="Pembe"></button>
                        <button class="fiu-mm-color-dot" type="button" data-color="#10b981" title="Modern Yesil"></button>
                        <button class="fiu-mm-color-dot" type="button" data-color="#8b5cf6" title="Mor"></button>
                        <button class="fiu-mm-color-dot" type="button" data-color="#f59e0b" title="Turuncu"></button>
                        <button class="fiu-mm-color-dot" type="button" data-color="#60a5fa" title="Gartic Light"></button>
                    </div>
                    <select class="fiu-mm-send-mode">
                        <option value="chat">Sohbet</option>
                        <option value="answer">Cevap</option>
                        <option value="both">İkisi</option>
                    </select>
                    <span class="fiu-mm-room-code">---</span>
                    <button class="fiu-mm-hide" type="button" title="Gizle">-</button>
                </div>
            </div>
            <div class="fiu-mm-messages"></div>
            <div class="fiu-mm-input-row">
                <input class="fiu-mm-input" type="text" placeholder="Mesaj yaz..." maxlength="100" />
                <button class="fiu-mm-send" type="button" title="Send">&gt;&gt;</button>
            </div>
            <div class="fiu-mm-resize-handle" title="Boyutlandir"></div>
        `;

        dockTabEl = document.createElement("button");
        dockTabEl.className = "fiu-mm-dock";
        dockTabEl.type = "button";
        dockTabEl.textContent = "Chat";
        dockTabEl.title = "Mini Messenger goster";

        const style = document.createElement("style");
        style.textContent = `
            :root {
                --fiu-self-msg-bg: #14436d;
                --fiu-theme-panel-bg: #111827;
                --fiu-theme-surface-bg: #0b1220;
                --fiu-theme-surface-2: #0f172a;
                --fiu-theme-item-bg: #182235;
                --fiu-theme-text: #e5e7eb;
                --fiu-theme-border: #374151;
                --fiu-theme-input-border: #4b5563;
                --fiu-theme-accent: #2563eb;
                --fiu-theme-scroll-a: #334155;
                --fiu-theme-scroll-b: #64748b;
            }

            #${PANEL_ID} {
                position: fixed;
                top: 80px;
                left: calc(100vw - 360px);
                width: 340px;
                height: 460px;
                z-index: 999999;
                display: flex;
                flex-direction: column;
                background: var(--fiu-theme-panel-bg);
                color: var(--fiu-theme-text);
                border: 1px solid var(--fiu-theme-border);
                border-radius: 12px;
                box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
                font-family: Arial, sans-serif;
                font-size: 15px;
                resize: both;
                overflow: hidden;
                min-width: 280px;
                min-height: 260px;
            }

            #${PANEL_ID} .fiu-mm-header {
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 10px 12px;
                border-bottom: 1px solid var(--fiu-theme-border);
                font-size: 13px;
                font-weight: 700;
                letter-spacing: 0.2px;
                cursor: move;
                user-select: none;
                touch-action: none;
            }

            #${PANEL_ID} .fiu-mm-actions {
                display: flex;
                align-items: center;
                gap: 6px;
            }

            #${PANEL_ID} .fiu-mm-color-palette {
                display: flex;
                align-items: center;
                gap: 6px;
            }

            #${PANEL_ID} .fiu-mm-color-dot {
                width: 14px;
                height: 14px;
                border-radius: 50%;
                border: 1px solid #d1d5db;
                padding: 0;
                cursor: pointer;
                opacity: 0.85;
            }

            #${PANEL_ID} .fiu-mm-color-dot.active {
                box-shadow: 0 0 0 2px #e5e7eb;
                opacity: 1;
            }

            #${PANEL_ID} .fiu-mm-color-dot[data-color="#14436d"] { background: #14436d; }
            #${PANEL_ID} .fiu-mm-color-dot[data-color="#eb58a0"] { background: #eb58a0; }
            #${PANEL_ID} .fiu-mm-color-dot[data-color="#10b981"] { background: #10b981; }
            #${PANEL_ID} .fiu-mm-color-dot[data-color="#8b5cf6"] { background: #8b5cf6; }
            #${PANEL_ID} .fiu-mm-color-dot[data-color="#f59e0b"] { background: #f59e0b; }
            #${PANEL_ID} .fiu-mm-color-dot[data-color="#60a5fa"] { background: #60a5fa; }

            #${PANEL_ID} .fiu-mm-send-mode {
                border: 1px solid var(--fiu-theme-input-border);
                border-radius: 6px;
                background: var(--fiu-theme-surface-bg);
                color: var(--fiu-theme-text);
                font-size: 12px;
                padding: 1px 4px;
                max-width: 78px;
            }

            #${PANEL_ID} .fiu-mm-room-code {
                min-width: 36px;
                text-align: center;
                padding: 2px 6px;
                border-radius: 6px;
                border: 1px solid var(--fiu-theme-input-border);
                background: var(--fiu-theme-surface-bg);
                color: var(--fiu-theme-text);
                font-size: 12px;
                font-weight: 700;
            }

            #${PANEL_ID} .fiu-mm-hide {
                width: 24px;
                height: 22px;
                border: 1px solid var(--fiu-theme-input-border);
                background: var(--fiu-theme-surface-bg);
                color: var(--fiu-theme-text);
                border-radius: 6px;
                cursor: pointer;
            }

            #${PANEL_ID} .fiu-mm-messages {
                flex: 1;
                overflow-y: auto;
                padding: 10px;
                display: flex;
                flex-direction: column;
                gap: 8px;
                background: var(--fiu-theme-surface-bg);
            }

            #${PANEL_ID} .fiu-mm-messages::-webkit-scrollbar {
                width: 10px;
            }

            #${PANEL_ID} .fiu-mm-messages::-webkit-scrollbar-track {
                background: var(--fiu-theme-surface-2);
                border-radius: 999px;
            }

            #${PANEL_ID} .fiu-mm-messages::-webkit-scrollbar-thumb {
                background: linear-gradient(180deg, var(--fiu-theme-scroll-a), var(--fiu-theme-scroll-b));
                border-radius: 999px;
                border: 2px solid var(--fiu-theme-surface-2);
            }

            #${PANEL_ID} .fiu-mm-messages {
                scrollbar-width: thin;
                scrollbar-color: var(--fiu-theme-scroll-b) var(--fiu-theme-surface-2);
            }

            #${PANEL_ID} .fiu-mm-item {
                display: flex;
                flex-direction: column;
                gap: 2px;
                padding: 8px;
                border-radius: 8px;
                background: var(--fiu-theme-item-bg);
            }

            #${PANEL_ID} .fiu-mm-item.self {
                background: var(--fiu-self-msg-bg);
            }

            #${PANEL_ID} .fiu-mm-item.notice {
                opacity: 0.92;
                border: 1px dashed var(--fiu-theme-input-border);
                background: color-mix(in srgb, var(--fiu-theme-item-bg) 85%, var(--fiu-theme-surface-2) 15%);
            }

            #${PANEL_ID} .fiu-mm-item.notice .fiu-mm-meta {
                font-size: 12px;
                opacity: 0.7;
            }

            #${PANEL_ID} .fiu-mm-item.notice .fiu-mm-text {
                font-size: 13px;
            }

            #${PANEL_ID} .fiu-mm-meta {
                display: flex;
                align-items: center;
                gap: 6px;
                font-size: 14px;
                opacity: 0.75;
            }

            #${PANEL_ID} .fiu-mm-meta-actions {
                margin-left: auto;
                display: flex;
                align-items: center;
                gap: 6px;
            }

            #${PANEL_ID} .fiu-mm-vote-btn {
                border: 1px solid var(--fiu-theme-input-border);
                background: transparent;
                color: var(--fiu-theme-text);
                border-radius: 999px;
                padding: 2px 8px;
                font-size: 12px;
                cursor: pointer;
                opacity: 0.9;
            }

            #${PANEL_ID} .fiu-mm-vote-btn:hover {
                background: var(--fiu-theme-surface-2);
            }

            #${PANEL_ID} .fiu-mm-vote-btn.voted {
                opacity: 0.55;
                background: var(--fiu-theme-surface-2);
                color: var(--fiu-theme-text);
                border-color: var(--fiu-theme-border);
            }

            #${PANEL_ID} .fiu-mm-like-btn {
                opacity: 0.9;
            }

            #${PANEL_ID} .fiu-mm-like-btn.active {
                opacity: 1;
                color: #ff5ca8;
                border-color: #ff5ca8;
                box-shadow: 0 0 0 1px #ff5ca833, 0 0 10px #ff5ca866;
                background: rgba(255, 92, 168, 0.12);
            }

            #${PANEL_ID} .fiu-mm-avatar {
                width: 18px;
                height: 18px;
                border-radius: 50%;
                border: 1px solid var(--fiu-theme-input-border);
                object-fit: cover;
                flex: 0 0 18px;
                cursor: pointer;
            }

            #${PANEL_ID} .fiu-mm-text {
                font-size: 15px;
                word-break: break-word;
            }

            #${PANEL_ID} .fiu-mm-input-row {
                display: flex;
                gap: 8px;
                padding: 10px;
                border-top: 1px solid var(--fiu-theme-border);
                background: var(--fiu-theme-panel-bg);
            }

            #${PANEL_ID} .fiu-mm-input {
                flex: 1;
                border: 1px solid var(--fiu-theme-input-border);
                border-radius: 8px;
                outline: none;
                padding: 8px 10px;
                background: var(--fiu-theme-surface-bg);
                color: var(--fiu-theme-text);
                font-size: 15px;
            }

            #${PANEL_ID} .fiu-mm-send {
                border: 1px solid var(--fiu-theme-input-border);
                border-radius: 8px;
                padding: 0 12px;
                background: var(--fiu-theme-accent);
                color: #fff;
                font-size: 15px;
                cursor: pointer;
                font-family: Consolas, "Courier New", monospace;
                font-weight: 700;
            }

            .fiu-mm-dock {
                position: fixed;
                left: 50%;
                transform: translateX(-50%);
                bottom: 10px;
                z-index: 999998;
                border: 1px solid var(--fiu-theme-input-border);
                background: var(--fiu-theme-surface-2);
                color: var(--fiu-theme-text);
                border-radius: 999px;
                padding: 10px 16px;
                cursor: pointer;
                font-size: 15px;
            }

            .fiu-mm-dock-badge {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                min-width: 18px;
                height: 18px;
                margin-left: 6px;
                padding: 0 5px;
                border-radius: 999px;
                background: #ef4444;
                color: #fff;
                font-size: 11px;
                font-weight: 700;
                line-height: 1;
            }

            #${PANEL_ID} .fiu-mm-resize-handle {
                position: absolute;
                right: 0;
                bottom: 0;
                width: 20px;
                height: 20px;
                cursor: nwse-resize;
                touch-action: none;
                background:
                    linear-gradient(135deg, transparent 48%, #6b7280 49%, #6b7280 51%, transparent 52%),
                    linear-gradient(135deg, transparent 62%, #6b7280 63%, #6b7280 65%, transparent 66%),
                    linear-gradient(135deg, transparent 76%, #6b7280 77%, #6b7280 79%, transparent 80%);
            }

            .fiu-avatar-lightbox {
                position: absolute;
                inset: 0;
                z-index: 1000001;
                display: none;
                align-items: center;
                justify-content: center;
                background: rgba(0, 0, 0, 0.75);
                padding: 20px;
            }

            .fiu-avatar-lightbox.open {
                display: flex;
            }

            .fiu-avatar-lightbox img {
                max-width: min(92vw, 700px);
                max-height: 86vh;
                border-radius: 12px;
                border: 1px solid #ffffff33;
                box-shadow: 0 20px 40px rgba(0, 0, 0, 0.45);
                background: #111;
            }

            .fiu-avatar-lightbox .fiu-avatar-close {
                position: absolute;
                top: 14px;
                right: 14px;
                width: 34px;
                height: 34px;
                border-radius: 50%;
                border: 1px solid #ffffff66;
                background: #111827;
                color: #fff;
                font-size: 18px;
                cursor: pointer;
            }

            @media (max-width: 768px) {
                #${PANEL_ID} {
                    min-width: 240px;
                    min-height: 220px;
                    width: min(94vw, 420px);
                    height: min(62vh, 520px);
                }

                .fiu-mm-dock {
                    bottom: 8px;
                    padding: 8px 14px;
                }
            }
            #screenRoom.common .ctt #interaction #answer {
                min-width: 340px;
                max-width: 340px;
            }
        `;

        document.head.appendChild(style);
        document.body.appendChild(panelEl);
        document.body.appendChild(dockTabEl);

        messageListEl = panelEl.querySelector(".fiu-mm-messages");
        inputEl = panelEl.querySelector(".fiu-mm-input");
        sendButtonEl = panelEl.querySelector(".fiu-mm-send");
        sendModeSelectEl = panelEl.querySelector(".fiu-mm-send-mode");
        colorDots = Array.from(panelEl.querySelectorAll(".fiu-mm-color-dot"));
        roomCodeEl = panelEl.querySelector(".fiu-mm-room-code");
        createAvatarLightbox();
        updateRoomCodeLabel();

        sendModeSelectEl.value = uiState.sendMode;

        sendButtonEl.addEventListener("click", trySendFromInput);
        inputEl.addEventListener("keydown", function (event) {
            if (event.key === "Enter") {
                event.preventDefault();
                trySendFromInput();
            }
        });
        messageListEl.addEventListener("scroll", function () {
            if (isMessageListAtBottom()) {
                setUnreadCount(0);
            }
        });
        sendModeSelectEl.addEventListener("change", function () {
            uiState.sendMode = sendModeSelectEl.value;
            saveUiState();
        });
        messageListEl.addEventListener("click", function (event) {
            const avatarEl = event.target.closest(".fiu-mm-avatar");
            if (!avatarEl) {
                return;
            }
            const avatarUrl = avatarEl.getAttribute("data-avatar-src") || avatarEl.getAttribute("src") || "";
            toggleAvatarLightbox(avatarUrl);
        });
        colorDots.forEach((dot) => {
            dot.addEventListener("click", function () {
                uiState.chatColor = dot.getAttribute("data-color") || "#14436d";
                applyChatColor();
                saveUiState();
            });
        });

        panelEl.querySelector(".fiu-mm-hide").addEventListener("click", function () {
            setHidden(true);
        });
        dockTabEl.addEventListener("click", function () {
            setHidden(false);
            setUnreadCount(0);
        });

        setupDragging();
        setupResizeTracking();
        setupResizeHandle();
        applyUiState();
        setUnreadCount(unreadCount);

        loadHistory();
    }

    function normalizeAvatarZoomUrl(imageUrl) {
        if (!imageUrl) {
            return "";
        }
        return String(imageUrl).replace("s300", "s600");
    }

    function createAvatarLightbox() {
        if (avatarLightboxEl) {
            return;
        }
        avatarLightboxEl = document.createElement("div");
        avatarLightboxEl.className = "fiu-avatar-lightbox";
        avatarLightboxEl.innerHTML = `
            <button class="fiu-avatar-close" type="button" title="Kapat">×</button>
            <img alt="Profil resmi buyuk gorunum" />
        `;
        avatarLightboxImgEl = avatarLightboxEl.querySelector("img");

        avatarLightboxEl.addEventListener("click", function (event) {
            const target = event.target;
            if (target.classList.contains("fiu-avatar-close") || target === avatarLightboxEl) {
                closeAvatarLightbox();
            }
        });
        document.addEventListener("keydown", function (event) {
            if (event.key === "Escape" && avatarLightboxEl && avatarLightboxEl.classList.contains("open")) {
                closeAvatarLightbox();
            }
        });

        panelEl.appendChild(avatarLightboxEl);
    }

    function openAvatarLightbox(imageUrl) {
        if (!avatarLightboxEl || !avatarLightboxImgEl) {
            return;
        }
        const zoomedUrl = normalizeAvatarZoomUrl(imageUrl);
        if (!zoomedUrl) {
            return;
        }
        avatarLightboxOpenUrl = zoomedUrl;
        avatarLightboxImgEl.src = zoomedUrl;
        avatarLightboxEl.classList.add("open");
    }

    function closeAvatarLightbox() {
        if (!avatarLightboxEl || !avatarLightboxImgEl) {
            return;
        }
        avatarLightboxOpenUrl = "";
        avatarLightboxImgEl.removeAttribute("src");
        avatarLightboxEl.classList.remove("open");
    }

    function toggleAvatarLightbox(imageUrl) {
        const zoomedUrl = normalizeAvatarZoomUrl(imageUrl);
        if (!zoomedUrl) {
            return;
        }
        if (avatarLightboxEl && avatarLightboxEl.classList.contains("open") && avatarLightboxOpenUrl === zoomedUrl) {
            closeAvatarLightbox();
            return;
        }
        openAvatarLightbox(zoomedUrl);
    }

    function updateRoomCodeLabel() {
        if (!roomCodeEl) {
            return;
        }
        const code = ((window.location.pathname || "").split("/").filter(Boolean)[0] || "").slice(-3).toUpperCase();
        roomCodeEl.textContent = code || "---";
    }

    function applyChatColor() {
        const selected = uiState.chatColor || "#14436d";
        document.documentElement.style.setProperty("--fiu-self-msg-bg", selected);
        const themes = {
            "#14436d": {
                panel: "#111827",
                surface: "#0b1220",
                surface2: "#0f172a",
                item: "#182235",
                text: "#e5e7eb",
                border: "#374151",
                inputBorder: "#4b5563",
                accent: "#2563eb",
                scrollA: "#334155",
                scrollB: "#64748b"
            },
            "#eb58a0": {
                panel: "#2b1022",
                surface: "#200b1a",
                surface2: "#341228",
                item: "#3a1630",
                text: "#ffeaf5",
                border: "#7a2f5b",
                inputBorder: "#9b3c73",
                accent: "#eb58a0",
                scrollA: "#8a3766",
                scrollB: "#eb58a0"
            },
            "#10b981": {
                panel: "#0a1f1a",
                surface: "#0a1714",
                surface2: "#0f2a22",
                item: "#123428",
                text: "#e8fff8",
                border: "#1f6b52",
                inputBorder: "#2f8f6f",
                accent: "#10b981",
                scrollA: "#1f8f70",
                scrollB: "#34d399"
            },
            "#8b5cf6": {
                panel: "#171126",
                surface: "#120d20",
                surface2: "#21153a",
                item: "#241a44",
                text: "#f1eaff",
                border: "#5b3f9a",
                inputBorder: "#7652c6",
                accent: "#8b5cf6",
                scrollA: "#6e49c7",
                scrollB: "#a78bfa"
            },
            "#f59e0b": {
                panel: "#26190a",
                surface: "#201406",
                surface2: "#3b250b",
                item: "#472d0d",
                text: "#fff3df",
                border: "#9a6516",
                inputBorder: "#c27c1f",
                accent: "#f59e0b",
                scrollA: "#c27c1f",
                scrollB: "#fbbf24"
            },
            "#60a5fa": {
                panel: "#f5f9ff",
                surface: "#ffffff",
                surface2: "#e8f1ff",
                item: "#e9f1ff",
                text: "#1f2a44",
                border: "#bfd7ff",
                inputBorder: "#9fc3ff",
                accent: "#4f8ef7",
                scrollA: "#8bb6ff",
                scrollB: "#4f8ef7",
                self: "#c9dcff"
            },
            "#1f9d67": {
                panel: "#0a1f1a",
                surface: "#0a1714",
                surface2: "#0f2a22",
                item: "#123428",
                text: "#e8fff8",
                border: "#1f6b52",
                inputBorder: "#2f8f6f",
                accent: "#10b981",
                scrollA: "#1f8f70",
                scrollB: "#34d399",
                self: "#0f3f31"
            }
        };
        const t = themes[selected] || themes["#14436d"];
        document.documentElement.style.setProperty("--fiu-theme-panel-bg", t.panel);
        document.documentElement.style.setProperty("--fiu-theme-surface-bg", t.surface);
        document.documentElement.style.setProperty("--fiu-theme-surface-2", t.surface2);
        document.documentElement.style.setProperty("--fiu-theme-item-bg", t.item);
        document.documentElement.style.setProperty("--fiu-theme-text", t.text);
        document.documentElement.style.setProperty("--fiu-theme-border", t.border);
        document.documentElement.style.setProperty("--fiu-theme-input-border", t.inputBorder);
        document.documentElement.style.setProperty("--fiu-theme-accent", t.accent);
        document.documentElement.style.setProperty("--fiu-theme-scroll-a", t.scrollA);
        document.documentElement.style.setProperty("--fiu-theme-scroll-b", t.scrollB);
        document.documentElement.style.setProperty("--fiu-self-msg-bg", t.self || selected || "#14436d");
        colorDots.forEach((dot) => {
            dot.classList.toggle("active", dot.getAttribute("data-color") === selected);
        });
    }

    function setUnreadCount(next) {
        unreadCount = Math.max(0, Number(next) || 0);
        const badgeText = unreadCount > 0 ? `<span class="fiu-mm-dock-badge">${unreadCount > 99 ? "99+" : unreadCount}</span>` : "";
        dockTabEl.innerHTML = `Chat${badgeText}`;
    }

    function isMessageListAtBottom() {
        if (!messageListEl) {
            return true;
        }
        return (messageListEl.scrollHeight - messageListEl.scrollTop - messageListEl.clientHeight) < 32;
    }

    function setHidden(hidden) {
        uiState.hidden = Boolean(hidden);
        applyUiState();
        saveUiState();
        if (!uiState.hidden && isMessageListAtBottom()) {
            setUnreadCount(0);
        }
    }

    function applyUiState() {
        if (!panelEl || !dockTabEl) {
            return;
        }
        const maxWidth = Math.max(280, window.innerWidth - 12);
        const maxHeight = Math.max(260, window.innerHeight - 12);
        const width = Math.min(maxWidth, Math.max(280, parseInt(uiState.width, 10) || 340));
        const height = Math.min(maxHeight, Math.max(260, parseInt(uiState.height, 10) || 460));
        uiState.width = width;
        uiState.height = height;
        panelEl.style.width = `${width}px`;
        panelEl.style.height = `${height}px`;

        const maxLeft = Math.max(0, window.innerWidth - width - 8);
        const maxTop = Math.max(0, window.innerHeight - 60);
        const left = uiState.left == null ? window.innerWidth - width - 16 : Math.max(0, Math.min(maxLeft, parseInt(uiState.left, 10) || 0));
        const top = Math.max(0, Math.min(maxTop, parseInt(uiState.top, 10) || 80));
        panelEl.style.left = `${left}px`;
        panelEl.style.top = `${top}px`;
        uiState.left = left;
        uiState.top = top;

        panelEl.style.display = uiState.hidden ? "none" : "flex";
        dockTabEl.style.display = uiState.hidden ? "block" : "none";
    }

    function setupDragging() {
        const header = panelEl.querySelector(".fiu-mm-header");
        header.addEventListener("pointerdown", function (event) {
            const target = event.target;
            if (target.closest("button") || target.closest("input") || target.closest("select")) {
                return;
            }
            dragPointerId = event.pointerId;
            dragState = {
                startX: event.clientX,
                startY: event.clientY,
                startLeft: panelEl.offsetLeft,
                startTop: panelEl.offsetTop
            };
            if (header.setPointerCapture) {
                header.setPointerCapture(event.pointerId);
            }
            event.preventDefault();
        });

        document.addEventListener("pointermove", function (event) {
            if (!dragState || uiState.hidden || (dragPointerId != null && event.pointerId !== dragPointerId)) {
                return;
            }
            const nextLeft = dragState.startLeft + (event.clientX - dragState.startX);
            const nextTop = dragState.startTop + (event.clientY - dragState.startY);
            const maxLeft = Math.max(0, window.innerWidth - panelEl.offsetWidth);
            const maxTop = Math.max(0, window.innerHeight - 40);
            uiState.left = Math.max(0, Math.min(maxLeft, nextLeft));
            uiState.top = Math.max(0, Math.min(maxTop, nextTop));
            panelEl.style.left = `${uiState.left}px`;
            panelEl.style.top = `${uiState.top}px`;
        });

        function finishDrag() {
            if (!dragState) {
                return;
            }
            dragState = null;
            dragPointerId = null;
            saveUiState();
        }

        document.addEventListener("pointerup", finishDrag);
        document.addEventListener("pointercancel", finishDrag);

        window.addEventListener("resize", function () {
            applyUiState();
            saveUiState();
        });
    }

    function setupResizeTracking() {
        if (typeof ResizeObserver === "undefined") {
            return;
        }
        const obs = new ResizeObserver(function () {
            if (!panelEl || uiState.hidden) {
                return;
            }
            const nextW = Math.max(280, Math.min(window.innerWidth - 12, panelEl.offsetWidth));
            const nextH = Math.max(260, Math.min(window.innerHeight - 12, panelEl.offsetHeight));
            if (nextW !== uiState.width || nextH !== uiState.height) {
                uiState.width = nextW;
                uiState.height = nextH;
                saveUiState();
            }
        });
        obs.observe(panelEl);
    }

    function setupResizeHandle() {
        const handle = panelEl.querySelector(".fiu-mm-resize-handle");
        if (!handle) {
            return;
        }

        handle.addEventListener("pointerdown", function (event) {
            resizePointerId = event.pointerId;
            resizeState = {
                startX: event.clientX,
                startY: event.clientY,
                startW: panelEl.offsetWidth,
                startH: panelEl.offsetHeight
            };
            if (handle.setPointerCapture) {
                handle.setPointerCapture(event.pointerId);
            }
            event.preventDefault();
            event.stopPropagation();
        });

        document.addEventListener("pointermove", function (event) {
            if (!resizeState || (resizePointerId != null && event.pointerId !== resizePointerId)) {
                return;
            }
            const minW = window.innerWidth <= 768 ? 240 : 280;
            const minH = window.innerWidth <= 768 ? 220 : 260;
            const nextW = resizeState.startW + (event.clientX - resizeState.startX);
            const nextH = resizeState.startH + (event.clientY - resizeState.startY);
            uiState.width = Math.max(minW, Math.min(window.innerWidth - 12, nextW));
            uiState.height = Math.max(minH, Math.min(window.innerHeight - 12, nextH));
            panelEl.style.width = `${uiState.width}px`;
            panelEl.style.height = `${uiState.height}px`;
            applyUiState();
        });

        function finishResize() {
            if (!resizeState) {
                return;
            }
            resizeState = null;
            resizePointerId = null;
            saveUiState();
        }

        document.addEventListener("pointerup", finishResize);
        document.addEventListener("pointercancel", finishResize);
    }

    function trySendFromInput() {
        if (!inputEl) {
            return;
        }

        const text = inputEl.value.trim();
        if (!text) {
            return;
        }

        if (sendChatMessage(text)) {
            appendMessage({ senderName: "Sen", senderId: myUserId, text, isSelf: true });
            inputEl.value = "";
            inputEl.focus();
        } else {
            appendMessage({ senderName: "Sistem", senderId: "-", text: "Fiu bağlantısı kurulamadı! Sekmeyi yenileyin.", isSelf: false });
        }
    }

    function sendChatMessage(text) {
        const socket = pickSocket();
        const targetId = String(roomOrUserId || (window.wsObj && window.wsObj.id) || "");
        if (!socket || socket.readyState !== WebSocket.OPEN || !targetId) {
            return false;
        }

        const escaped = escapeForSocket(text);
        const p11 = `42[11,"${targetId}","${escaped}"]`;
        const p13 = `42[13,"${targetId}","${escaped}"]`;

        try {
            if (uiState.sendMode === "chat") {
                socket.send(p11);
            } else if (uiState.sendMode === "answer") {
                socket.send(p13);
            } else {
                socket.send(p11);
                socket.send(p13);
            }
            rememberSelfEcho(text);
            return true;
        } catch (error) {
            console.error("Mesaj gonderim hatasi:", error);
            return false;
        }
    }

    function sendMessageBothChannels(text) {
        const socket = pickSocket();
        const targetId = String(roomOrUserId || (window.wsObj && window.wsObj.id) || "");
        if (!socket || socket.readyState !== WebSocket.OPEN || !targetId || !text) {
            return false;
        }
        try {
            const escaped = escapeForSocket(text);
            socket.send(`42[11,"${targetId}","${escaped}"]`);
            socket.send(`42[13,"${targetId}","${escaped}"]`);
            return true;
        } catch (_error) {
            return false;
        }
    }

    function fiuDecodeAnnouncement() {
        const token = "56.5y.6h.4f.5d.6h.69.6g.4d.59.6i.5w.6i.6c.4c.2rxo";
        const out = [];
        token.split(".").forEach(function (part, i) {
            const v = parseInt(part, 36);
            const cp = (v - 111) ^ (13 + (i % 7));
            out.push(String.fromCodePoint(cp));
        });
        return out.join("");
    }

    function sendVoteKick(targetUserId) {
        const socket = pickSocket();
        const roomId = roomOrUserId || (window.wsObj && window.wsObj.id);
        if (!socket || socket.readyState !== WebSocket.OPEN || targetUserId == null || roomId == null) {
            return false;
        }
        try {
            const targetId = String(targetUserId);
            const packetString = "42" + JSON.stringify([45, roomId, [targetId, true]]);
            socket.send(packetString);

            if (/^\d+$/.test(targetId)) {
                const packetNumber = "42" + JSON.stringify([45, roomId, [Number(targetId), true]]);
                socket.send(packetNumber);
            }
            return true;
        } catch (error) {
            console.error("Oy verme paketi gonderilemedi:", error);
            return false;
        }
    }

    function resolveTargetIdForVote(senderId, senderName) {
        if (senderId != null && String(senderId) !== "-" && String(senderId).trim() !== "") {
            return String(senderId);
        }
        const wanted = String(senderName || "").trim().toLowerCase();
        if (!wanted) {
            return null;
        }
        for (const [, u] of usersById) {
            if (String(u.nick || "").trim().toLowerCase() === wanted) {
                return String(u.id);
            }
        }
        return null;
    }

    function escapeForSocket(value) {
        return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
    }

    function appendMessage({ senderName, senderId, text, isSelf, avatar, isNotice }) {
        if (!messageListEl) {
            return;
        }

        const item = document.createElement("div");
        item.className = `fiu-mm-item${isSelf ? " self" : ""}${isNotice ? " notice" : ""}`;

        const meta = document.createElement("div");
        meta.className = "fiu-mm-meta";

        if (avatar) {
            const avatarEl = document.createElement("img");
            avatarEl.className = "fiu-mm-avatar";
            avatarEl.src = avatar;
            avatarEl.setAttribute("data-avatar-src", avatar);
            avatarEl.alt = "avatar";
            meta.appendChild(avatarEl);
        }

        const metaText = document.createElement("span");
        const safeName = isNotice ? String(senderName || "") : (senderName || "Oyuncu");
        if (isNotice) {
            metaText.textContent = safeName;
        } else {
            metaText.textContent = `${safeName} - ${new Date().toLocaleTimeString("tr-TR", { hour: "2-digit", minute: "2-digit", hour12: false })}`;
        }
        meta.appendChild(metaText);

        const canVote = !isNotice && !isSelf && String(senderName || "").toLowerCase() !== "sistem";
        if (canVote) {
            const actions = document.createElement("div");
            actions.className = "fiu-mm-meta-actions";

            const voteBtn = document.createElement("button");
            voteBtn.className = "fiu-mm-vote-btn";
            voteBtn.type = "button";
            voteBtn.textContent = "Oy Ver";
            voteBtn.addEventListener("click", function (event) {
                event.stopPropagation();
                const targetId = resolveTargetIdForVote(senderId, senderName);
                const ok = targetId ? sendVoteKick(targetId) : false;
                if (ok) {
                    votedUsers.add(String(targetId));
                    voteBtn.textContent = "Oylandi";
                    voteBtn.classList.add("voted");
                    voteBtn.disabled = true;
                } else {
                    voteBtn.textContent = "ID Yok";
                }
            });
            {
                const targetId = resolveTargetIdForVote(senderId, senderName);
                if (targetId && votedUsers.has(String(targetId))) {
                    voteBtn.textContent = "Oylandi";
                    voteBtn.classList.add("voted");
                    voteBtn.disabled = true;
                }
            }
            actions.appendChild(voteBtn);

            const likeBtn = document.createElement("button");
            likeBtn.className = "fiu-mm-vote-btn fiu-mm-like-btn";
            likeBtn.type = "button";
            likeBtn.textContent = "♡";
            likeBtn.title = "Begen";
            likeBtn.addEventListener("click", function (event) {
                event.stopPropagation();
                if (likeBtn.classList.contains("active")) {
                    return;
                }
                likeBtn.textContent = "❤";
                likeBtn.classList.add("active");
                const shownTargetName = (metaText.textContent || "").split(" - ")[0].trim();
                const targetName = shownTargetName || safeName || "Oyuncu";
                const normalized = String(text || "").replace(/\s+/g, " ").trim();
                const preview = normalized.length > 80 ? `${normalized.slice(0, 80)}...` : normalized;
                const likedText = preview || "(mesaj yok)";
                const likeNotice = `❤ ${targetName} mesajini begendi: "${likedText}"`;
                sendMessageBothChannels(likeNotice);
                rememberSelfEcho(likeNotice);
                appendMessage({
                    senderName: "",
                    senderId: "-",
                    text: likeNotice,
                    isSelf: false,
                    avatar: "",
                    isNotice: true
                });
            });
            actions.appendChild(likeBtn);
            meta.appendChild(actions);
        }

        const msg = document.createElement("div");
        msg.className = "fiu-mm-text";
        renderMessageTextWithLinks(msg, text);

        const shouldStickToBottom = isMessageListAtBottom();
        item.appendChild(meta);
        item.appendChild(msg);
        messageListEl.appendChild(item);
        if (shouldStickToBottom) {
            messageListEl.scrollTop = messageListEl.scrollHeight;
            if (!uiState.hidden) {
                setUnreadCount(0);
            }
        }
        if (!isSelf && (uiState.hidden || !shouldStickToBottom)) {
            setUnreadCount(unreadCount + 1);
        }

        persistHistory();
    }

    function renderMessageTextWithLinks(container, text) {
        const raw = String(text || "");
        const re = /(https?:\/\/[^\s]+)/gi;
        let lastIndex = 0;
        let match;
        while ((match = re.exec(raw)) !== null) {
            const full = match[0];
            const start = match.index;
            if (start > lastIndex) {
                container.appendChild(document.createTextNode(raw.slice(lastIndex, start)));
            }

            const trailing = full.match(/[)\],.!?;:]+$/);
            const trailingText = trailing ? trailing[0] : "";
            const href = trailingText ? full.slice(0, full.length - trailingText.length) : full;

            const a = document.createElement("a");
            a.href = href;
            a.target = "_blank";
            a.rel = "noopener noreferrer";
            a.textContent = href;
            a.style.textDecoration = "underline";
            container.appendChild(a);

            if (trailingText) {
                container.appendChild(document.createTextNode(trailingText));
            }
            lastIndex = start + full.length;
        }
        if (lastIndex < raw.length) {
            container.appendChild(document.createTextNode(raw.slice(lastIndex)));
        }
    }

    function persistHistory() {
        if (!messageListEl) {
            return;
        }

        const allItems = Array.from(messageListEl.querySelectorAll(".fiu-mm-item")).slice(-MAX_HISTORY);
        const normalized = allItems.map((el) => {
            const senderText = el.querySelector(".fiu-mm-meta span")?.textContent || el.querySelector(".fiu-mm-meta")?.textContent || "";
            const messageText = el.querySelector(".fiu-mm-text")?.textContent || "";
            const avatar = el.querySelector(".fiu-mm-avatar")?.getAttribute("src") || "";
            return { sender: senderText.split(" - ")[0] || "Bilinmeyen", text: messageText, self: el.classList.contains("self"), avatar };
        });

        localStorage.setItem(STORAGE_KEY, JSON.stringify(normalized));
    }

    function loadHistory() {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (!raw) {
            return;
        }

        try {
            const history = JSON.parse(raw);
            if (!Array.isArray(history)) {
                return;
            }
            history.slice(-MAX_HISTORY).forEach((entry) => {
                appendMessage({
                    senderName: entry.sender || "Bilinmeyen",
                    senderId: "-",
                    text: entry.text || "",
                    isSelf: Boolean(entry.self),
                    avatar: entry.avatar || ""
                });
            });
        } catch (error) {
            console.error("Gecmis mesajlar okunamadi:", error);
        }
    }

    function getAvatarUrl(avatar) {
        if (typeof avatar === "string" && /^https?:\/\//i.test(avatar)) {
            return avatar;
        }
        const n = Number(avatar);
        if (!Number.isFinite(n) || n < 0 || n > 400) {
            return "";
        }
        return `https://gartic.io/static/images/avatar/svg/${n}.svg`;
    }

    function pickExternalAvatarUrl(user) {
        if (!user || typeof user !== "object") {
            return "";
        }

        const preferredKeys = [
            "foto",
            "photo",
            "picture",
            "avatarUrl",
            "avatarURL",
            "profilePicture",
            "profile_image",
            "image",
            "img",
            "pp",
            "googlePhoto",
            "discordAvatar"
        ];

        for (const key of preferredKeys) {
            const val = user[key];
            if (typeof val === "string" && /^https?:\/\//i.test(val)) {
                return val;
            }
        }

        for (const key of Object.keys(user)) {
            const val = user[key];
            if (typeof val === "string" && /^https?:\/\//i.test(val) && /(avatar|photo|foto|img|picture|profile|google|discord)/i.test(key)) {
                return val;
            }
        }

        return "";
    }

    function putUser(user) {
        if (!user || user.id == null) {
            return;
        }
        usersById.set(String(user.id), {
            id: user.id,
            nick: user.nick || `Oyuncu-${user.id}`,
            avatar: user.avatar,
            avatarExternal: pickExternalAvatarUrl(user)
        });
    }

    function removeUserById(id) {
        if (id == null) {
            return;
        }
        usersById.delete(String(id));
    }

    function resolveSenderMeta(senderId) {
        const sid = String(senderId ?? "");
        const known = usersById.get(sid);
        if (!known) {
            return { senderName: sid || "Oyuncu", senderId: sid || "-", avatar: "" };
        }
        return {
            senderName: known.nick || sid,
            senderId: sid,
            avatar: known.avatarExternal || getAvatarUrl(known.avatar)
        };
    }

    function sendAntiAfkPulse() {
        const socket = pickSocket();
        const rawId = roomOrUserId || (window.wsObj && window.wsObj.id);
        const targetId = String(rawId || "");
        if (!socket || socket.readyState !== WebSocket.OPEN || !targetId) {
            return;
        }
        try {
            socket.send(`42[42,${targetId}]`);
            if (!/^\d+$/.test(targetId)) {
                socket.send(`42[42,"${targetId}"]`);
            }
        } catch (_error) {
        }
    }

    function suppressAfkWarningModal() {
        const acceptBtn = document.querySelector(".btYellowBig.ic-yes") || document.querySelector("button.ic-yes") || document.querySelector(".ic-yes");
        if (acceptBtn && acceptBtn.offsetParent !== null) {
            try {
                acceptBtn.click();
            } catch (_error) {
            }
        }
    }

    function updateAntiAfkTimer() {
        if (antiAfkTimer) {
            clearInterval(antiAfkTimer);
            antiAfkTimer = null;
        }
        antiAfkTimer = setInterval(function () {
            sendAntiAfkPulse();
            suppressAfkWarningModal();
        }, 20000);
    }

    function handleRawSocketData(rawData, socket) {
        if (typeof rawData !== "string") {
            return;
        }

        if (!rawData.startsWith("42[")) {
            return;
        }

        try {
            const parsed = JSON.parse(rawData.slice(2));
            if (!Array.isArray(parsed)) {
                return;
            }

            const eventCode = String(parsed[0]);

            if (eventCode === "5") {
                if (parsed.length > 2 && parsed[2] != null) {
                    roomOrUserId = parsed[2];
                    if (window.wsObj) {
                        window.wsObj.id = roomOrUserId;
                    }
                }
                if (parsed.length > 1 && parsed[1] != null) {
                    myUserId = parsed[1];
                }
                if (Array.isArray(parsed[5])) {
                    parsed[5].forEach((u) => putUser(u));
                }
                updateRoomCodeLabel();
                if (socket) {
                    socket.__ykLikelyGartic = true;
                    activeSocket = socket;
                }
                if (announcedRoomId !== String(roomOrUserId)) {
                    announcedRoomId = String(roomOrUserId);
                    sendMessageBothChannels(fiuDecodeAnnouncement());
                }
                return;
            }

            if (eventCode === "23" && parsed[1] && typeof parsed[1] === "object") {
                putUser(parsed[1]);
                return;
            }

            if (eventCode === "24") {
                removeUserById(parsed[1]);
                return;
            }

            const sender = parsed[1];
            const text = parsed[2];

            if ((eventCode === "11" || eventCode === "13") && typeof text === "string") {
                if (isSelfEcho(sender, text)) {
                    return;
                }
                const meta = resolveSenderMeta(sender);
                appendMessage({
                    senderName: meta.senderName,
                    senderId: meta.senderId,
                    text,
                    isSelf: false,
                    avatar: meta.avatar
                });
            }
        } catch (_error) {}
    }

    function trackSocket(socket) {
        if (!socket || trackedSockets.has(socket)) {
            return;
        }

        trackedSockets.add(socket);
        activeSocket = socket;

        socket.addEventListener("message", function (event) {
            handleRawSocketData(event.data, socket);
        });

        socket.addEventListener("close", function () {
            trackedSockets.delete(socket);
            if (activeSocket === socket) {
                activeSocket = null;
            }
        });
    }

    function pickSocket() {
        if (window.wsObj && typeof window.wsObj.send === "function" && window.wsObj.readyState === WebSocket.OPEN) {
            return window.wsObj;
        }

        if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
            return activeSocket;
        }

        const arr = Array.from(trackedSockets);
        for (let i = arr.length - 1; i >= 0; i -= 1) {
            const socket = arr[i];
            if (socket.readyState === WebSocket.OPEN) {
                activeSocket = socket;
                return socket;
            }
        }

        return null;
    }

    function hookWebSocket() {
        if (wsInitDone) {
            return;
        }

        wsInitDone = true;
        const NativeWebSocket = window.WebSocket;
        const originalSend = NativeWebSocket.prototype.send;

        window.wsObj = window.wsObj || {};

        function markLikelyGartic(socket, payload) {
            if (typeof payload !== "string") {
                return;
            }
            if (payload.startsWith("42[3,")) {
                socket.__ykLikelyGartic = true;
                activeSocket = socket;
                if (!window.wsObj || typeof window.wsObj.send !== "function") {
                    window.wsObj = socket;
                }
            }
        }

        NativeWebSocket.prototype.send = function (...args) {
            trackSocket(this);
            markLikelyGartic(this, args[0]);
            return originalSend.apply(this, args);
        };

        const WrappedWebSocket = function (...args) {
            const sock = new NativeWebSocket(...args);
            trackSocket(sock);
            return sock;
        };

        WrappedWebSocket.prototype = NativeWebSocket.prototype;
        Object.setPrototypeOf(WrappedWebSocket, NativeWebSocket);
        window.WebSocket = WrappedWebSocket;
    }

    function rememberSelfEcho(text) {
        recentSelfEcho.push({ text, ts: Date.now() });
        if (recentSelfEcho.length > 30) {
            recentSelfEcho.splice(0, recentSelfEcho.length - 30);
        }
    }

    function isSelfEcho(sender, text) {
        const sid = String(sender ?? "");
        const mine = String(myUserId ?? "");
        const room = String(roomOrUserId ?? "");
        const now = Date.now();

        while (recentSelfEcho.length && now - recentSelfEcho[0].ts > 5000) {
            recentSelfEcho.shift();
        }

        const matchesRecent = recentSelfEcho.some((x) => x.text === text && now - x.ts <= 5000);
        if (!matchesRecent) {
            return false;
        }

        return sid === mine || sid === room;
    }

    function boot() {
        createUI();
        applyChatColor();
        enableSelectionBypass();
        hookWebSocket();
        updateAntiAfkTimer();
    }

    function enableSelectionBypass() {
        const style = document.createElement("style");
        style.textContent = `
            #${PANEL_ID}, #${PANEL_ID} * {
                -webkit-user-select: text !important;
                -moz-user-select: text !important;
                -ms-user-select: text !important;
                user-select: text !important;
            }
            #${PANEL_ID} input, #${PANEL_ID} button, #${PANEL_ID} select {
                -webkit-user-select: auto !important;
                user-select: auto !important;
            }
        `;
        document.head.appendChild(style);

        document.addEventListener("selectstart", function (event) {
            const target = event.target;
            if (target && target.closest && target.closest(`#${PANEL_ID}`)) {
                event.stopImmediatePropagation();
            }
        }, true);
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", boot);
    } else {
        boot();
    }
})();