// ==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 https://greasyfork.org/users/1591289 // @downloadURL https://update.greasyfork.icu/scripts/576091/Fiu%20Garticio%20Chat%20Fix.user.js // @updateURL https://update.greasyfork.icu/scripts/576091/Fiu%20Garticio%20Chat%20Fix.meta.js // ==/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 = `
Fiu Chat v${SCRIPT_VERSION}
---
`; 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 = ` 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 ? `${unreadCount > 99 ? "99+" : unreadCount}` : ""; 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(); } })();