// ==UserScript== // @name ChatsApp – וואטסאפ לגוגל צ'אט // @namespace chatsapp // @version 1.3.2 // @description גוגל צ'אט בעיצוב וואטסאפ: בועות ירוקות/לבנות עם זנב, שעה ו-VV בתוך הבועה, ציטוטים, מסגרות לתמונות, רקע הוואטסאפ המקורי ומצב כהה // @homepageURL https://github.com/TOP-SHEBAMITMACHIM/chatsapp // @downloadURL https://raw.githubusercontent.com/TOP-SHEBAMITMACHIM/chatsapp/main/chatsapp.user.js // @updateURL https://raw.githubusercontent.com/TOP-SHEBAMITMACHIM/chatsapp/main/chatsapp.user.js // @match https://chat.google.com/* // @match https://mail.google.com/chat/* // @match https://mail.google.com/mail/* // @run-at document-idle // @grant none // ==/UserScript== (function () { 'use strict'; /* ================= ירוק בצד (משותף לצ'אט ול-Gmail) ================= */ // השורה הנבחרת ברשימת הצ'אטים ובפס של Gmail (דואר/צ'אט/Meet): ירוק בהיר במקום התכלת של גוגל. שמות מרחבים בירוק. // (האייקונים עצמם נשארים בצבעים המקוריים) const ICON_GREEN = '#008069', ICON_SEL = '#d9fdd3', ICON_SEL_TEXT = '#00684a'; const ICON_CSS = ` .wa-navsel { box-shadow: inset 0 0 0 100vmax ${ICON_SEL} !important; } .wa-navsel, .wa-navsel *, .wa-navtxt { -webkit-text-fill-color: ${ICON_SEL_TEXT}; } [data-group-id^="space/"][role="listitem"] [jsname="dfY7oc"] { -webkit-text-fill-color: ${ICON_GREEN}; } `; // הצבע עצמו לא נדרס (רק צל פנימי ו-text-fill), כך שאפשר להמשיך לקרוא מה גוגל מסמנת כנבחר const rgbOf = (c) => { const m = c && c.match(/rgba?\(([^)]+)\)/); if (!m) return null; const p = m[1].split(/[ ,\/]+/).filter(Boolean).map(Number); return { r: p[0], g: p[1], b: p[2], a: p.length > 3 ? p[3] : 1 }; }; const isSelBg = (c) => !!c && c.a > 0.5 && c.b >= 235 && c.r >= 170 && c.b - c.r >= 15; // התכלת של "נבחר" const isSelText = (c) => !!c && c.b - c.r >= 60 && (c.r * 0.299 + c.g * 0.587 + c.b * 0.114) < 140; // טקסט כחול כהה let iconsLast = 0, iconsTimer = 0; function iconsFix() { const now = Date.now(); if (now - iconsLast < 1500) { if (!iconsTimer) iconsTimer = setTimeout(() => { iconsTimer = 0; iconsFix(); }, 1550); return; } iconsLast = now; // רשימת הצ'אטים (גם כשהיא ב-iframe נפרד בתוך Gmail) + הפס של Gmail (לא רשימת התיקיות של הדואר) const cand = document.querySelectorAll('[jsname="yPINCb"] [jsname], [jsname="yPINCb"] [role], [role="listitem"][data-group-id], [role="listitem"][data-group-id] [role="link"], .V6'); const todo = [...cand].map((e) => { const cs = getComputedStyle(e); return [e, isSelBg(rgbOf(cs.backgroundColor)), isSelText(rgbOf(cs.color))]; }); todo.forEach(([e, sel, tx]) => { if (sel !== e.classList.contains('wa-navsel')) e.classList.toggle('wa-navsel', sel); if (tx !== e.classList.contains('wa-navtxt')) e.classList.toggle('wa-navtxt', tx); }); } function iconsCleanup() { clearTimeout(iconsTimer); iconsTimer = 0; document.querySelectorAll('.wa-navsel, .wa-navtxt').forEach((e) => e.classList.remove('wa-navsel', 'wa-navtxt')); } // בדף של Gmail עצמו רק האייקונים שבצד; הצ'אט שבתוך Gmail נטען ב-iframe של chat.google.com ומעוצב שם במלואו if (location.hostname === 'mail.google.com' && !location.pathname.startsWith('/chat')) gmailMain(); else chatsappMain(); function gmailMain() { const W = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; if (W.waStop) { try { W.waStop(); } catch (e) {} } const st = document.createElement('style'); const n = document.querySelector('style[nonce]'); if (n && n.nonce) st.nonce = n.nonce; st.textContent = ICON_CSS; document.head.appendChild(st); const mo = new MutationObserver(iconsFix); mo.observe(document.body || document.documentElement, { subtree: true, childList: true }); document.addEventListener('click', iconsFix, true); iconsFix(); W.waStop = () => { mo.disconnect(); document.removeEventListener('click', iconsFix, true); st.remove(); iconsCleanup(); delete W.waStop; }; } function chatsappMain() { const VERSION = '1.3.2'; const W = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; if (W.waStop) { try { W.waStop(); } catch (e) {} } /* ================= הגדרות שאפשר לשנות ================= */ // סוג הרקע: 'auto' = תמונה, ואם מסנן תוכן מחליף אותה בסימן "התמונה לא נבדקה" - עובר לדוגמה וקטורית ואז לצבע חלק. // אפשר לקבע ידנית: 'image' | 'svg' | 'flat' const BG_MODE = 'auto'; // איפה הוי וי בשורת השעה שבתחתית הבועה: // 'bubble-start' = וי וי בצד ימין של הבועה והשעה בצד שמאל (ברירת מחדל) // 'beside-time' = צמודים: "19:11 ✓✓" (וי וי מימין לשעה) // 'whatsapp' = כמו בוואטסאפ בעברית: "✓✓ 19:11" (וי וי משמאל לשעה, בפינה השמאלית) const TICKS_LAYOUT = 'bubble-start'; const BG_WIDTH = 380; // רוחב אריח הרקע בפיקסלים (קטן = דוגמה צפופה יותר) const MAX_BUBBLE = 520; // רוחב מקסימלי של בועה const TAIL_W = 10, TAIL_H = 16; // גודל הזנב (פיקסלים) const TICK_SENT = '#8696a0'; // וי וי אפורים: נשלח const TICK_READ = '#53bdeb'; // וי וי כחולים: נקרא const TICKS_SHIFT_LEFT = 6; // (הודעות מדיה בלבד) הזזת הוי וי שמאלה /* סלקטורים - מקום אחד לתקן אם גוגל משנה משהו */ const SEL = { message: 'div[jsname="bgckF"]', // תוכן ההודעה; ההורה הישיר שלו הוא הבועה group: '[role="group"][data-user-id]', // מכיל את מזהה השולח scroller: '[jsname="iyUusd"]', // אזור גלילת ההודעות composer: '[role="textbox"][contenteditable="true"]', header: 'header', sidebar: '[jsname="yPINCb"]', quote: '', // סלקטור מדויק לציטוט (אם ידוע). ריק = זיהוי לפי מבנה quoteBox: '[jsname="d9SUbc"]', // המיכל החיצוני של ציטוט אצל גוגל (שם + אייקון + טקסט) - עדיף על פני חלק פנימי שלו receipts: '[read-receipts-list="true"]', // רשימת הקריאה של גוגל (מכילה את האווטאר של "נקרא") toolbar: '[jsname="zgWRWd"]', // סרגל הפעולות שמופיע במעבר עכבר על הודעה }; // אווטארים שהם לא "נקרא": בתוך תגובת אימוג'י (כפתור) או בחלונית שקופצת במעבר עליה (מי הגיב), תפריט, חלון. // (האווטארים האמיתיים של "נקרא" יושבים בתוך קבוצת ההודעה, אף פעם לא בתוך כפתור או חלונית) const NOT_RECEIPT = 'button, [role="button"], [role="tooltip"], [role="dialog"], [role="menu"], [role="listbox"], [aria-haspopup], ' + SEL.toolbar; /* ================= תמונות ================= */ // רקע הדוגמה המקורי של וואטסאפ (760x1396, WebP) const IMG_LIGHT = 'data:image/webp;base64,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'; const svgRaw = (stroke, op) => 'data:image/svg+xml,' + encodeURIComponent( '' + ''); const SVG_LIGHT = svgRaw('#808080', '.14'); // גיבוי, אם מסנן תוכן חוסם את התמונה const SVG_DARK = svgRaw('#ffffff', '.06'); // זנב הבועה: הצד נקבע לפי כיוון הממשק. בעברית (RTL) ההודעות שלי משמאל והנכנסות מימין; באנגלית להפך. const TAIL_BASE = '.wa-bubble[data-wa-tail="true"]'; const tailSide = (rtl, own) => (own === rtl ? 'left' : 'right'); // RTL: שלי=שמאל, אחרים=ימין. LTR: להפך // הצורה: הזנב המעוקל של וואטסאפ (קצה מעוגל למעלה וקו מתעקל אל הבועה), מצויר כמסכה בצבע הבועה. // "R" = הזנב יוצא מימין לבועה (מחובר בצד שמאל שלו), "L" = יוצא משמאל (מחובר בצד ימין שלו) const tailMask = (d) => 'url("data:image/svg+xml,' + encodeURIComponent( '') + '")'; const TAIL_R = tailMask('M6.467 2.568 0 11.193V0h5.188c1.77 0 2.338 1.156 1.279 2.568z'); const TAIL_L = tailMask('M1.533 2.568 8 11.193V0H2.812C1.042 0 .474 1.156 1.533 2.568z'); const tailCss = [true, false].map((rtl) => [true, false].map((own) => { const root = rtl ? 'html[data-wa-rtl="true"]' : 'html:not([data-wa-rtl="true"])'; const sel = root + ' ' + TAIL_BASE + '[data-wa-own="' + own + '"]'; const side = tailSide(rtl, own), color = own ? 'var(--wa-out)' : 'var(--wa-in)'; const mask = side === 'right' ? TAIL_R : TAIL_L; const other = side === 'right' ? 'left' : 'right'; const shape = side + ':-' + (TAIL_W - 1) + 'px !important;' + other + ':auto !important;background:' + color + ' !important;' + '-webkit-mask:' + mask + ' no-repeat 0 0/100% 100% !important;mask:' + mask + ' no-repeat 0 0/100% 100% !important;'; return sel + '{border-top-' + side + '-radius:0 !important;}\n ' + sel + '::after{' + shape + '}'; }).join('\n ')).join('\n ') + // גוגל משתמשת בעצמה ב-::after של הבועה (אפקט מעבר עכבר, שקוף כברירת מחדל) - לכן כל המאפיינים כאן עם !important '\n ' + TAIL_BASE + '::after{content:"" !important;display:block !important;position:absolute !important;top:0 !important;bottom:auto !important;' + 'width:' + TAIL_W + 'px !important;height:' + TAIL_H + 'px !important;opacity:1 !important;visibility:visible !important;transform:none !important;' + 'border:0 !important;border-radius:0 !important;box-shadow:none !important;background-image:none !important;transition:none !important;pointer-events:none;}'; /* ================= CSS ================= */ const css = ` :root { --wa-header:#008069; --wa-header-text:#fff; --wa-bg:#e5ddd5; --wa-img:url("${IMG_LIGHT}"); --wa-img-svg:url("${SVG_LIGHT}"); --wa-in:#fff; --wa-out:#d9fdd3; --wa-text:#111b21; --wa-sub:#667781; --wa-panel:#fff; --wa-hover:#f5f6f6; --wa-border:#e9edef; --wa-input:#fff; --wa-link:#027eb5; --wa-footer:#f0f2f5; --wa-quote-bg:rgba(0,0,0,.06); --wa-quote-bar:#ffffff; --wa-quote-me:#06cf9c; --wa-shadow:0 1px .5px rgba(11,20,26,.13); } /* ---- רקע: הדוגמה של וואטסאפ, קבועה במקום (לא זזה בגלילה) ---- */ .wa-scroller, .wa-main { background-color: var(--wa-bg) !important; background-image: var(--wa-img) !important; background-size: ${BG_WIDTH}px auto !important; background-repeat: repeat !important; background-attachment: fixed !important; } /* גיבוי אם מסנן תוכן מחליף את תמונת הרקע בסימן "התמונה לא נבדקה" */ html[data-wa-bg="svg"] .wa-scroller, html[data-wa-bg="svg"] .wa-main { background-image: var(--wa-img-svg) !important; background-size: 80px 80px !important; } html[data-wa-bg="flat"] .wa-scroller, html[data-wa-bg="flat"] .wa-main { background-image: none !important; } .wa-clear { background: transparent !important; } /* הפריסה (באיזה צד כל הודעה) נשארת כפי שגוגל קובעת: בעברית ההודעות שלי משמאל והנכנסות מימין, כמו בוואטסאפ בעברית */ /* ---- הבועה ---- */ .wa-bubble { position: relative !important; overflow: visible !important; unicode-bidi: plaintext; border-radius: 8px !important; box-shadow: var(--wa-shadow) !important; color: var(--wa-text) !important; width: max-content !important; max-width: min(${MAX_BUBBLE}px, calc(var(--wa-scw, 100vw) * .75)) !important; /* לפי רוחב אזור ההודעות - גם בחלון קטן/קופץ */ min-width: 0 !important; overflow-wrap: break-word !important; word-break: normal !important; } .wa-bubble * { word-break: normal !important; } .wa-bubble [jsname="bgckF"] { background: transparent !important; } /* גוגל קובעת צבע טקסט על כל אלמנט פנימי, ולכן הצבע של הבועה לא עובר בירושה (במצב כהה הטקסט נשאר כהה) */ .wa-bubble [jsname="bgckF"], .wa-bubble [jsname="bgckF"] * { color: var(--wa-text) !important; } .wa-bubble a, .wa-bubble [jsname="bgckF"] a { color: var(--wa-link) !important; } .wa-bubble[data-wa-own="true"] { background: var(--wa-out) !important; } .wa-bubble[data-wa-own="false"] { background: var(--wa-in) !important; } /* ---- זנב: רק בהודעה הראשונה ברצף של אותו שולח. הצד תלוי בכיוון הממשק (שלי: שמאל בעברית, ימין באנגלית) ---- */ ${tailCss} /* ---- האווטאר של "נקרא" של גוגל: מוסתר (הוי וי עברו לתוך הבועה, ליד השעה) ---- */ .wa-seen { visibility: hidden !important; pointer-events: none !important; } /* לא display:none - זה גורם לגוגל לבנות מחדש את האלמנט (ריצוד) */ /* האווטאר של "נקרא" (והטבעת סביבו) מוסתר כבר ב-CSS, מהפריים הראשון - בלי לחכות לסקריפט, אחרת כשנכנסים לשיחה רואים אותו לרגע לפני שהוא מתחלף ל-VV. מזהים אותו לפי data-hide-hovercard (רק לו יש את זה); מוסתר ולא מוסר, כך שהסקריפט עדיין מוצא אותו ומודד אותו */ :is([jsname="iyUusd"], .wa-scroller) [data-member-id][data-hide-hovercard]:not(button *, [role="tooltip"] *, [role="dialog"] *), :is([jsname="iyUusd"], .wa-scroller) :is([jsname="A6ktte"], [jsname="r1RgJc"]):has([data-member-id][data-hide-hovercard]):not(:has([jsname="bgckF"])) { visibility: hidden !important; } .wa-bubble .wa-seen { position: absolute !important; } .wa-seen::before, .wa-seen::after { display: none !important; content: none !important; } /* ---- פריסה פנימית ---- */ .wa-bubble { display: flex !important; flex-direction: column !important; align-items: stretch !important; height: auto !important; min-height: 0 !important; max-height: none !important; /* אם גוגל מגבילה גובה - השעה יוצאת מהמסגרת */ } /* שורת השעה+וי וי ממוקמת בצורה מוחלטת בתוך הקופסה המצוירת של הבועה (לכן אי אפשר שתצא ממנה), ומקום לה נשמר ב-padding: בתחתית (below), או בצד (inline) להודעות קצרות */ .wa-bubble[data-wa-meta="below"] { padding-bottom: 21px !important; min-width: calc(var(--wa-mw, 60px) + 20px) !important; } /* הודעה קצרה (תו אחד): הבועה לא צרה משורת השעה */ .wa-bubble[data-wa-meta="inline"] { padding-inline-end: calc(var(--wa-mw, 56px) + 18px) !important; } /* ---- שעה + וי וי ---- */ .wa-bubble > .wa-meta { position: absolute !important; z-index: 1; bottom: 4px !important; inset-inline: 10px !important; display: flex !important; align-items: center; gap: 4px; box-sizing: border-box !important; margin: 0 !important; padding: 0 !important; width: auto !important; font-size: 11px !important; line-height: 14px !important; white-space: nowrap; color: var(--wa-sub) !important; user-select: none; pointer-events: none; } .wa-bubble[data-wa-meta="inline"] > .wa-meta { bottom: 5px !important; inset-inline-start: auto !important; } .wa-meta > .wa-time { margin-inline-start: auto; } /* bubble-start: וי וי בצד אחד, שעה בצד השני */ html[data-wa-ticks="beside-time"] .wa-meta, html[data-wa-ticks="whatsapp"] .wa-meta { justify-content: flex-end; } html[data-wa-ticks="beside-time"] .wa-meta > .wa-time, html[data-wa-ticks="whatsapp"] .wa-meta > .wa-time { margin-inline-start: 0; } html[data-wa-ticks="whatsapp"] .wa-meta > .wa-tk { order: 2; } .wa-tk { display: inline-block; flex: none; width: 16px; height: 10px; color: ${TICK_SENT}; overflow: visible; } .wa-tk[data-state="read"] { color: ${TICK_READ}; } .wa-tk[data-hidden="true"] { display: none; } .wa-time:empty { display: none; } /* קבוצה: כמה קראו - תמיד ליד ה-VV */ .wa-rc { font-size: 11px; color: var(--wa-sub); margin-inline-end: -2px; } .wa-rc:empty { display: none; } /* מעבר עכבר על המספר / הוי וי מציג מי קרא, בתווית כהה מעל השורה (השורה עצמה לא תופסת לחיצות - רק הם) */ .wa-meta[data-who] > .wa-rc, .wa-meta[data-who] > .wa-tk { pointer-events: auto; cursor: default; } .wa-meta[data-who]:has(> .wa-rc:hover, > .wa-tk:hover)::after { /* נפתחת לכיוון פנים החלון (ההודעות שלי בקצה - בעברית משמאל), כדי שלא תיחתך */ content: attr(data-who); position: absolute; bottom: calc(100% + 6px); inset-inline-end: -4px; z-index: 5; padding: 5px 9px; border-radius: 7px; background: rgba(17,27,33,.92); color: #fff; font-size: 12.5px; line-height: 16px; white-space: nowrap; box-shadow: 0 2px 6px rgba(0,0,0,.25); pointer-events: none; } /* ---- ציטוט (תשובה להודעה), כמו בוואטסאפ: תיבה כהה שקופה עם פס צבעוני בצד ימין, שם מודגש וטקסט אפור ---- */ /* הציטוט וכל העוטפים שלו נמתחים לכל רוחב הבועה (גוגל משאירה להם רווחים/רוחב מקסימלי) */ .wa-bubble .wa-qwrap, .wa-bubble .wa-quote { width: 100% !important; inline-size: 100% !important; max-width: none !important; min-width: 0 !important; flex: 1 1 auto !important; align-self: stretch !important; box-sizing: border-box !important; } .wa-bubble .wa-qwrap { margin-inline: 0 !important; } /* הציטוט עצמו ברוחב אוטומטי: כך השוליים השליליים (fitQuote) מותחים אותו עד קצוות הבועה */ .wa-bubble .wa-quote { display: block !important; width: auto !important; inline-size: auto !important; } .wa-bubble .wa-quote { background: var(--wa-quote-bg) !important; border-radius: 7px !important; border: 0 !important; border-inline-start: 4px solid var(--wa-quote-bar) !important; box-shadow: none !important; padding: 5px 9px !important; margin: 0 0 5px !important; overflow: hidden !important; max-height: 84px; color: var(--wa-sub) !important; } .wa-bubble .wa-quote[data-wa-me="true"] { border-inline-start-color: var(--wa-quote-me) !important; } /* הציטוט נמצא בתוך תוכן ההודעה: הספציפיות כאן גבוהה מכלל צבע הטקסט של ההודעה, כדי שהציטוט יישאר אפור */ .wa-bubble .wa-quote, .wa-bubble .wa-quote * { color: var(--wa-sub) !important; box-shadow: none !important; } .wa-bubble .wa-quote * { background: transparent !important; } .wa-bubble .wa-quote .wa-qname { font-weight: 600 !important; color: var(--wa-text) !important; } .wa-bubble .wa-quote[data-wa-me="true"] .wa-qname { color: var(--wa-quote-me) !important; } .wa-bubble .wa-qglyph { display: none !important; } /* אייקון הגרשיים הוורוד של גוגל (גם אם הוא מחוץ לאלמנט שזוהה כציטוט) */ /* ---- הודעה קולית: בועה של וואטסאפ (במקום הכרטיס עם המסגרת האפורה) ---- */ .wa-voicebox { border-color: transparent !important; border-radius: 8px !important; box-shadow: var(--wa-shadow) !important; position: relative !important; padding-bottom: 22px !important; /* מקום לשעה ול-VV בתחתית */ } .wa-voicebox[data-wa-own="true"] { background: var(--wa-out) !important; } .wa-voicebox[data-wa-own="false"] { background: var(--wa-in) !important; } .wa-voicebox hr { border-color: rgba(0,0,0,.08) !important; background: rgba(0,0,0,.08) !important; } .wa-voicebox button[aria-label] { color: var(--wa-sub) !important; } /* כפתור הנגינה: אפור כמו בוואטסאפ */ .wa-voicebox > .wa-mmeta { /* שעה + VV: כמו בבועת טקסט, לא כתווית על תמונה */ background: transparent; color: var(--wa-sub); padding: 0; bottom: 4px; inset-inline-end: 10px; } .wa-voicebox > .wa-mmeta .wa-tk:not([data-state="read"]) { color: ${TICK_SENT}; } /* ---- הודעה עם קובץ/סרטון/תמונה ---- */ .wa-bubble.wa-media { width: auto !important; max-width: none !important; } /* תמונה/סרטון בלי טקסט: מסגרת בצבע של השולח */ .wa-mediabox { padding: 3px !important; border-radius: 8px !important; box-shadow: var(--wa-shadow) !important; box-sizing: content-box !important; overflow: hidden !important; } .wa-mediabox[data-wa-own="true"] { background: var(--wa-out) !important; } .wa-mediabox[data-wa-own="false"] { background: var(--wa-in) !important; } .wa-mediabox img, .wa-mediabox video { border-radius: 6px !important; } /* לגוגל יש מסגרת אפורה פנימית (קו 1.6px עם פינות 20px) - בלי קו, ועם פינות שמתאימות למסגרת שלנו */ .wa-mediabox *:has(img, video) { border-color: transparent !important; border-radius: 6px !important; } .wa-mediabox { position: relative !important; } .wa-mmeta { position: absolute; z-index: 2; bottom: 8px; inset-inline-end: 9px; display: flex; align-items: center; gap: 3px; padding: 1px 6px; border-radius: 10px; background: rgba(0,0,0,.38); color: #fff; font-size: 11px; line-height: 15px; white-space: nowrap; pointer-events: none; user-select: none; } .wa-mmeta[data-empty="true"] { display: none; } .wa-mmeta .wa-tk:not([data-state="read"]) { color: #fff; } /* הוי וי הכחולים הישנים שמתחת לכרטיס - מיותרים כשהם כבר בתוך המסגרת */ .wa-ticks-anchor:has(.wa-mediabox) { margin-bottom: 0 !important; } .wa-ticks-anchor:has(.wa-mediabox) > .wa-ticks { display: none !important; } /* הודעת מדיה בלי טקסט (אין בועה): הוי וי כחולים מוצבים מתחת לכרטיס, מחוץ לו */ .wa-ticks-anchor { position: relative !important; overflow: visible !important; margin-bottom: 18px !important; } .wa-ticks { position: absolute; inset-inline-end: ${4 + TICKS_SHIFT_LEFT}px; top: calc(100% + 3px); width: 18px; height: 11px; color: ${TICK_READ}; pointer-events: none; } .wa-ticks > svg { display: block; width: 100%; height: 100%; } /* ---- מפרידי תאריכים ---- */ .wa-scroller [role="separator"] span, .wa-scroller [data-date-header] span { background: var(--wa-panel) !important; color: var(--wa-sub) !important; padding: 4px 12px !important; border-radius: 8px !important; box-shadow: var(--wa-shadow) !important; font-size: 12.5px !important; } /* ---- סרגל צד / כותרת / תיבת כתיבה ---- */ ${SEL.sidebar} { background: var(--wa-panel) !important; color: var(--wa-text) !important; } ${SEL.sidebar} [role="treeitem"]:hover, ${SEL.sidebar} a:hover { background: var(--wa-hover) !important; } ${SEL.header} { background: var(--wa-header) !important; color: var(--wa-header-text) !important; box-shadow: none !important; } ${SEL.header} * { color: var(--wa-header-text) !important; } ${SEL.header} .wa-hdbg { background-color: rgba(255,255,255,.16) !important; } /* תיבת ההקלדה: הכדור בצבע של וואטסאפ, המשטח סביבו בצבע הפס התחתון, ושדה הטקסט שקוף */ ${SEL.composer} { background: transparent !important; color: var(--wa-text) !important; caret-color: var(--wa-text); } .wa-compose::before { background: var(--wa-input) !important; opacity: 1 !important; } .wa-compose-panel { background-color: var(--wa-footer) !important; } /* ---- "לא נקראו" ו"כותב/ת...": בלי הפס הלבן, תווית מעוגלת כמו מפרידי התאריכים ---- */ .wa-clearbar { background: transparent !important; box-shadow: none !important; border-color: transparent !important; } .wa-clearbar::before, .wa-clearbar::after { box-shadow: none !important; } .wa-unread, .wa-typing { background: var(--wa-panel) !important; color: var(--wa-sub) !important; border: 0 !important; border-radius: 8px !important; padding: 4px 12px !important; box-shadow: var(--wa-shadow) !important; font-size: 12.5px !important; width: fit-content !important; max-width: 100% !important; box-sizing: border-box !important; } .wa-unread { position: relative !important; inset: auto !important; transform: none !important; margin: 0 auto !important; display: block !important; height: auto !important; line-height: 16px !important; text-transform: none !important; } .wa-unread-row { background: transparent !important; box-shadow: none !important; display: flex !important; justify-content: center !important; align-items: center !important; height: auto !important; width: 100% !important; padding: 6px 0 !important; } .wa-barline { display: none !important; } .wa-typing { border-radius: 16px !important; margin: 2px 8px !important; } .wa-unread * { background: transparent !important; } ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-thumb { background: rgba(134,150,160,.45); border-radius: 3px; } ::-webkit-scrollbar-track { background: transparent; } /* ---- מצב כהה: רק בתוך חלון השיחה (הרשימה והסרגלים נשארים בהירים) ---- */ html[data-wa-dark="true"] .wa-card { --wa-bg:#0b141a; --wa-img:var(--wa-img-dark, none); --wa-img-svg:url("${SVG_DARK}"); --wa-in:#202c33; --wa-out:#005c4b; --wa-text:#e9edef; --wa-sub:#8696a0; --wa-panel:#182229; --wa-hover:#202c33; --wa-border:#222d34; --wa-input:#2a3942; --wa-link:#53bdeb; --wa-footer:#202c33; --wa-quote-bg:rgba(0,0,0,.22); --wa-quote-bar:#8696a0; --wa-shadow:0 1px .5px rgba(0,0,0,.4); color: var(--wa-text); } /* משטחים וטקסט של גוגל בתוך החלון (סרגל השם, תיבת הכתיבה, הודעות מערכת...) - מסומנים פעם אחת לפי הצבע שלהם. ספציפיות נמוכה בכוונה, כדי שהעיצוב של הבועות/המסגרות תמיד יגבר */ :where(html[data-wa-dark="true"]) .wa-dk-bg { background-color: var(--wa-footer) !important; } :where(html[data-wa-dark="true"]) .wa-dk-in { background-color: var(--wa-input) !important; } :where(html[data-wa-dark="true"]) .wa-dk-tx { color: var(--wa-text) !important; } :where(html[data-wa-dark="true"]) .wa-dk-sub { color: var(--wa-sub) !important; } :where(html[data-wa-dark="true"]) .wa-dk-bd { border-color: var(--wa-border) !important; } :where(html[data-wa-dark="true"]) .wa-dk-sh { box-shadow: none !important; } :where(html[data-wa-dark="true"]) .wa-dk-psh::before, :where(html[data-wa-dark="true"]) .wa-dk-psh::after { box-shadow: none !important; } html[data-wa-dark="true"] .wa-card ::placeholder { color: var(--wa-sub) !important; } /* ---- ירוק של וואטסאפ במקום הכחול של גוגל בחלון השיחה (צ'יפים, כפתורים נבחרים, "הודעות חדשות"...) ---- צל פנימי במקום רקע ו-text-fill במקום צבע, כך שאפשר להמשיך לקרוא מה גוגל מסמנת (נבחר/לא נבחר) */ :root { --wa-acc:#00a884; --wa-acc-l:#d9fdd3; --wa-acc-t:#008069; --wa-icon:#54656f; } html[data-wa-dark="true"] .wa-card { --wa-acc-l:rgba(0,168,132,.22); --wa-acc-t:#00a884; --wa-icon:#aebac1; } .wa-acc-l { box-shadow: inset 0 0 0 100vmax var(--wa-acc-l) !important; } .wa-acc { box-shadow: inset 0 0 0 100vmax var(--wa-acc) !important; } .wa-acc-tx { -webkit-text-fill-color: var(--wa-acc-t); } .wa-acc-tx svg, .wa-acc-l svg { color: var(--wa-acc-t) !important; } .wa-acc svg { color: #fff !important; } .wa-acc-b::before { background-color: var(--wa-acc) !important; } .wa-compose.wa-compose::before { background: var(--wa-input) !important; } /* הקופסה של נעיצה/שרשורים/קבצים בפינה העליונה */ [jsname="Zpe2Q"] { background-color: var(--wa-acc-l) !important; } [jsname="Zpe2Q"] svg, [jsname="Zpe2Q"] i { color: var(--wa-acc-t) !important; -webkit-text-fill-color: var(--wa-acc-t); } /* ---- הפס העליון של השיחה (עם מי מתכתבים): ירוק של וואטסאפ, ובמצב כהה אפור-כהה ---- */ :root { --wa-head:#008069; --wa-head-text:#fff; } html[data-wa-dark="true"] .wa-card { --wa-head:#202c33; --wa-head-text:#e9edef; } .wa-chathead { background-color: var(--wa-head) !important; box-shadow: none !important; } .wa-chathead, .wa-chathead * { color: var(--wa-head-text) !important; -webkit-text-fill-color: var(--wa-head-text); } .wa-chathead svg, .wa-chathead i { color: var(--wa-head-text) !important; } .wa-chathead .wa-acc-l, .wa-chathead .wa-acc { box-shadow: inset 0 0 0 100vmax rgba(255,255,255,.16) !important; } .wa-chathead .wa-headbtn { background-color: rgba(255,255,255,.16) !important; } .wa-chathead .wa-headclear { background-color: transparent !important; box-shadow: none !important; } .wa-card .wa-footclear { background-color: transparent !important; box-shadow: none !important; border-color: transparent !important; } .wa-chathead .wa-darkbtn, .wa-darkbtn.wa-float { color: var(--wa-head-text); } .wa-darkbtn.wa-float { position: fixed; z-index: 3; margin: 0; } .wa-darkbtn.wa-float:hover { background: rgba(255,255,255,.12); } .wa-chathead .wa-darkbtn:hover { background: rgba(255,255,255,.12); } /* קופסת הנעיצה/השרשורים כשהיא יושבת על הפס הירוק */ [jsname="Zpe2Q"].wa-onhead { background-color: rgba(255,255,255,.16) !important; } [jsname="Zpe2Q"].wa-onhead, [jsname="Zpe2Q"].wa-onhead * { color: var(--wa-head-text) !important; -webkit-text-fill-color: var(--wa-head-text); } [jsname="Zpe2Q"].wa-onhead svg { color: var(--wa-head-text) !important; } /* ---- תיבת הכתיבה כמו בוואטסאפ ---- כדור לבן מעוגל על רקע הטפט: אימוג'י בצד ההתחלה, הטקסט, ובסוף עיצוב/GIF/צירוף קובץ. מחוץ לכדור עיגול ירוק: מיקרופון כשהתיבה ריקה (לחיצה = הקלטת הודעה קולית של גוגל), וחץ שליחה כשיש טקסט. הפיצ'רים של גוגל נשארים: "+" (כלי Workspace) בצד, וחץ "תזמון השליחה" ליד העיגול כשיש טקסט */ .wa-compose-panel { background-color: transparent !important; box-shadow: none !important; } .wa-compose::before { background: var(--wa-input) !important; opacity: 1 !important; border-radius: 26px !important; box-shadow: 0 1px .5px rgba(11,20,26,.13) !important; } .wa-compose button, .wa-compose button svg { color: var(--wa-icon) !important; } .wa-compose [role="textbox"][contenteditable="true"] { font-size: 15px !important; } /* האימוג'י עובר לתחילת הכדור (כמו בוואטסאפ) והטקסט זז כדי לפנות לו מקום. המבנה של גוגל לא משתנה - רק המיקום */ .wa-compose [jsname="bXurze"] { position: static !important; } /* (בתחתית, בשורה של שאר האייקונים - כשמצרפים תמונה הכדור גדל, ובאמצע הוא היה יושב על התמונה) */ .wa-compose [jsname="bXurze"] > div > :has([jsname="JlEEbd"]) { position: absolute !important; inset-inline-start: 10px; bottom: 16px; z-index: 1; } .wa-compose > div:has([role="textbox"]) { padding-inline-start: 38px !important; } .wa-compose [jsname="bXurze"] > div > :has([jsname="EATp9"]) { display: none !important; } /* המיקרופון עובר לעיגול הירוק */ /* "+" (כלי Workspace): אייקון אפור בלי עיגול */ [jsname="kVb47c"] { box-shadow: none !important; background: transparent !important; } [jsname="kVb47c"], [jsname="kVb47c"] * { -webkit-text-fill-color: var(--wa-icon); } [jsname="kVb47c"] svg { color: var(--wa-icon) !important; } /* העיגול הירוק */ .wa-sendgrp { display: flex !important; align-items: center !important; gap: 2px; width: auto !important; } .wa-sendgrp, .wa-sendgrp :is(div, span) { background-color: transparent !important; box-shadow: none !important; } .wa-sendgrp :is(div, span)::before { opacity: 0 !important; } /* שכבת ה"מצב" של גוגל (ריבוע צבעוני שקוף) */ [jsname="TFQmwf"]::after { box-shadow: none !important; } /* הקו האפור מעל תיבת הכתיבה */ .wa-sendgrp [jsname="GBTyxb"] { background: var(--wa-acc) !important; color: #fff !important; border-radius: 50% !important; opacity: 1 !important; width: 48px !important; height: 48px !important; min-width: 48px !important; padding: 0 !important; margin: 0 4px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; position: relative !important; box-shadow: 0 1px .5px rgba(11,20,26,.2) !important; } .wa-sendgrp [jsname="GBTyxb"] svg { color: #fff !important; fill: #fff !important; } .wa-sendgrp [jsname="GBTyxb"] *::before, .wa-sendgrp [jsname="GBTyxb"]::before { opacity: 0 !important; } .wa-sendgrp [jsname="GBTyxb"]:hover { filter: brightness(1.06); } /* תיבה ריקה: מיקרופון במקום החץ, והלחיצה עוברת לכפתור ההקלטה של גוגל */ .wa-sendgrp[data-wa-empty="true"] { cursor: pointer; } .wa-sendgrp[data-wa-empty="true"] [jsname="GBTyxb"] { pointer-events: none !important; } .wa-sendgrp[data-wa-empty="true"] [jsname="GBTyxb"] > * { visibility: hidden !important; } .wa-sendgrp[data-wa-empty="true"] [jsname="GBTyxb"]::after { content: ""; position: absolute; inset: 0; margin: auto; width: 24px; height: 24px; background: #fff; -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M12%2014a3%203%200%200%200%203-3V5a3%203%200%200%200-6%200v6a3%203%200%200%200%203%203zm5.3-3a.9.9%200%200%200-.9.8A4.4%204.4%200%200%201%2012%2015.6a4.4%204.4%200%200%201-4.4-3.8.9.9%200%200%200-.9-.8.9.9%200%200%200-.9%201A6.2%206.2%200%200%200%2011%2017.4V20a1%201%200%200%200%202%200v-2.6a6.2%206.2%200%200%200%205.2-5.4.9.9%200%200%200-.9-1z%22%2F%3E%3C%2Fsvg%3E") center / contain no-repeat; } .wa-sendgrp[data-wa-empty="true"] [jsname="ssKfee"] { display: none !important; } .wa-sendgrp [jsname="ssKfee"] { background: transparent !important; box-shadow: none !important; color: var(--wa-icon) !important; } .wa-sendgrp [jsname="ssKfee"] *::before, .wa-sendgrp [jsname="ssKfee"]::before { opacity: 0 !important; } /* כפתור הירח בסרגל העליון של השיחה */ .wa-darkbtn { display: inline-flex; align-items: center; justify-content: center; flex: none; box-sizing: border-box; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; color: var(--wa-sub); margin: 0 2px; } .wa-darkbtn:hover { background: rgba(0,0,0,.06); } html[data-wa-dark="true"] .wa-darkbtn:hover { background: rgba(255,255,255,.08); } .wa-darkbtn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } html[data-wa-dark="true"] .wa-darkbtn svg { fill: currentColor; } ${ICON_CSS} `; function addStyle(text) { let el = null; if (typeof GM_addStyle === 'function') { try { el = GM_addStyle(text); } catch (e) {} } if (!(el instanceof Element)) { el = document.createElement('style'); const n = document.querySelector('style[nonce]'); if (n && n.nonce) el.nonce = n.nonce; el.textContent = text; document.head.appendChild(el); } return el; } const styleEl = addStyle(css); /* ================= זיהוי ================= */ // המזהה של החשבון שלי (נמצא בנתוני הדף) const MY_ID = (() => { for (const sc of document.scripts) { const m = sc.textContent && sc.textContent.match(/"W3Yyqf"\s*:\s*"(\d{8,})"/); if (m) return m[1]; } return null; })(); let stopped = false; const colorCache = new WeakMap(); let lastSend = 0; // מתי שלחתי הודעה לאחרונה const sentTexts = []; // הטקסטים האחרונים ששלחתי (עם זמן) - לזיהוי ההודעה שלי לפני שגוגל מסמנת את השולח const norm = (t) => (t || '').replace(/\s+/g, ' ').trim(); const mineBySend = new WeakSet(); // הודעות שזוהו כשלי לפי הטקסט ששלחתי const firstSeen = new WeakMap(); // מתי כל הודעה הופיעה לראשונה בדף const panes = new WeakMap(); // אזור הגלילה של כל הודעה (נשמר בין סבבים) function parseRGB(c) { const m = c && c.match(/rgba?\(([^)]+)\)/); if (!m) return null; const p = m[1].split(/[ ,\/]+/).filter(Boolean).map(Number); return { r: p[0], g: p[1], b: p[2], a: p.length > 3 ? p[3] : 1 }; } // גיבוי אחרון: צבע הבועה המקורית (כחלחל = שלי). נבדק רק לפני שהעיצוב חל. function colorOwn(el) { if (colorCache.has(el)) return colorCache.get(el); const b = el.parentElement; if (!b || b.classList.contains('wa-bubble')) return null; const c = parseRGB(getComputedStyle(b).backgroundColor); if (!c || c.a < 0.05) return null; const own = c.b - c.r >= 25; colorCache.set(el, own); return own; } function findScroller(el) { const s = el.closest(SEL.scroller); if (s) return s; for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) { if (/(auto|scroll)/.test(getComputedStyle(p).overflowY) && p.scrollHeight > p.clientHeight + 5) return p; } return null; } // לאן "מצמידים" את הוי וי כשהאווטאר לא בתוך בועת טקסט (למשל כרטיס סרטון/קובץ) // אזור הגלילה של הודעה, שמור בין סבבים (findScroller קורא מידות, וזה יקר לעשות לכל הודעה בכל סבב) const scrollers = new WeakMap(); function scrollerOf(el) { const c = scrollers.get(el); if (c && c.contains(el)) return c; const s = findScroller(el); if (s) scrollers.set(el, s); return s; } function pickAnchor(c, scroller) { const cluster = c.closest('[jsname="o7uNDd"]'); if (cluster) return cluster; const maxW = scroller.clientWidth * 0.85; for (let n = c.parentElement, k = 0; n && n !== scroller && k < 8; n = n.parentElement, k++) { const w = n.offsetWidth, h = n.offsetHeight; if (w >= 120 && w <= maxW && h >= 40) return n; // הכרטיס עצמו: צר מהשורה כולה } return null; } // כתיבה ל-DOM רק כשיש שינוי אמיתי: כל כתיבה מיותרת מעוררת את הקוד של גוגל ויוצרת לולאת ריצוד const addCls = (el, c) => { if (!el.classList.contains(c)) el.classList.add(c); }; const delCls = (el, c) => { if (el.classList.contains(c)) el.classList.remove(c); }; const setCls = (el, c, on) => (on ? addCls(el, c) : delCls(el, c)); const setAttr = (el, n, v) => { if (el.getAttribute(n) !== v) el.setAttribute(n, v); }; const delAttr = (el, n) => { if (el.hasAttribute(n)) el.removeAttribute(n); }; // בונים אלמנטים רק עם DOM API: גוגל אוכפת Trusted Types, ולכן innerHTML (מחרוזת HTML) זורק שגיאה ושובר את כל הריצה. // הוי וי הם SVG מוטבע (לא בקשת תמונה), כך שגם מסנן תמונות לא יכול להחליף אותם. const SVGNS = 'http://www.w3.org/2000/svg'; function makeTicks(cls) { const svg = document.createElementNS(SVGNS, 'svg'); svg.setAttribute('viewBox', '0 0 17.5 11'); if (cls) svg.setAttribute('class', cls); svg.setAttribute('aria-hidden', 'true'); ['M1.5 6.2 4.6 9.2 11 1.8', 'M8.3 8.2 9.3 9.2 15.8 1.8'].forEach((d) => { const p = document.createElementNS(SVGNS, 'path'); p.setAttribute('d', d); p.setAttribute('fill', 'none'); p.setAttribute('stroke', 'currentColor'); p.setAttribute('stroke-width', '1.7'); p.setAttribute('stroke-linecap', 'round'); p.setAttribute('stroke-linejoin', 'round'); svg.appendChild(p); }); return svg; } const rcOf = (m) => { // (שורות שנוצרו בגרסה קודמת - בלי האלמנט) let rc = m.querySelector(':scope > .wa-rc'); if (!rc) { rc = document.createElement('span'); rc.className = 'wa-rc'; m.insertBefore(rc, m.firstChild); } return rc; }; function makeMeta() { const m = document.createElement('span'); m.className = 'wa-meta'; const t = document.createElement('span'); t.className = 'wa-time'; const rc = document.createElement('span'); // בקבוצה: כמה קראו rc.className = 'wa-rc'; m.appendChild(rc); m.appendChild(makeTicks('wa-tk')); m.appendChild(t); return m; } // שעת ההודעה: רק אם גוגל חושפת חותמת זמן ב-DOM (אחרת מציגים וי וי בלי שעה) const TS_SEL = '[data-absolute-timestamp], [data-timestamp], time[datetime]'; function fmtTime(ms) { const d = new Date(ms); return isNaN(d) ? '' : d.toLocaleTimeString('he-IL', { hour: '2-digit', minute: '2-digit', hour12: false }); } function msgTime(el) { for (let n = el.parentElement, k = 0; n && k < 10; n = n.parentElement, k++) { if (n.matches('[role="main"]') || n.matches(SEL.scroller)) break; if (n.querySelectorAll(SEL.message).length > 1) break; // המכל כולל כבר הודעות אחרות - אי אפשר לדעת של מי השעה const t = n.querySelector(TS_SEL); if (!t) continue; const v = t.getAttribute('data-absolute-timestamp') || t.getAttribute('data-timestamp') || t.getAttribute('datetime'); let ms = Number(v); if (!ms) ms = Date.parse(v); if (!ms) return ''; if (ms > 1e15) ms = Math.floor(ms / 1000); // מיקרו-שניות if (ms < 1e11) ms *= 1000; // שניות return fmtTime(ms); } return ''; } // ציטוט. גוגל לא חושפת סלקטור קבוע, לכן מזהים לפי מראה: תיבה עם רקע משלה בתוך הבועה, מחוץ לטקסט ההודעה. function isBigMedia(el) { return [...el.querySelectorAll('video, canvas, iframe, img')].some((m) => !(m.tagName === 'IMG' && m.offsetWidth <= 64 && m.offsetHeight <= 64)); } // סרגל הפעולות שגוגל מציגה במעבר עכבר (תגובות מהירות 👍😂🙏, השב, עוד) יושב בתוך הבועה, עם רקע וטקסט - // בדיוק כמו ציטוט. הוא ממוקם בצורה מוחלטת, וכך מבדילים: אלמנט צף (או בתוך אלמנט צף) הוא לא ציטוט. function isOverlay(d, b) { for (let n = d; n && n !== b; n = n.parentElement) { if (n.matches(SEL.toolbar)) return true; const p = getComputedStyle(n).position; if (p === 'absolute' || p === 'fixed') return true; } return false; } function findQuote(b) { if (SEL.quote) return b.querySelector(SEL.quote); const done = b.querySelector('.wa-quote'); // כבר סומן - לא מזהים מחדש (העיצוב שלנו משנה את הרקע) if (done) return done; const box = b.querySelector(SEL.quoteBox); if (box && !isOverlay(box, b)) return box; // רוב הבועות: רק תוכן ההודעה (והשעה שלנו). ציטוט תמיד יושב לצד ההודעה, אז אם אין שם שום טקסט - אין מה לסרוק // (הסריקה שלמטה מודדת ומחשבת סגנון לעשרות אלמנטים בכל בועה, וזה מה שהאט את המעבר בין שיחות) const extra = [...b.children].some((c) => !c.matches(SEL.message) && !c.classList.contains('wa-meta') && !c.classList.contains('wa-seen') && !c.matches(SEL.receipts) && !c.matches(SEL.toolbar) && !c.querySelector(SEL.toolbar) // (סרגל הריחוף - לא ציטוט) && c.textContent.trim()); if (!extra) return null; const minW = b.getBoundingClientRect().width * 0.45; const cands = b.querySelectorAll(':scope > *, :scope > * > *, :scope > * > * > *, :scope > * > * > * > *'); for (const d of cands) { if (d.matches(SEL.message) || d.closest('.wa-meta') || d.closest('.wa-seen') || d.matches(SEL.receipts) || d.closest(SEL.receipts)) continue; if (isOverlay(d, b)) continue; if (!d.textContent.trim() || isBigMedia(d)) continue; // טקסט בעיצוב "ציטוט בלוק" (מתפריט העיצוב) הוא לא תשובה להודעה: תשובה תמיד כוללת שם + טקסט (שתי שורות לפחות). // במעבר עכבר גוגל נותנת לו רקע, ובלי הבדיקה הזו הוא סומן כציטוט והבועה נשברה if (d.closest('blockquote') || d.querySelector('blockquote') || (d.innerText || '').split('\n').filter((l) => l.trim()).length < 2) continue; if (d.offsetWidth < minW || d.offsetHeight < 24) continue; const c = parseRGB(getComputedStyle(d).backgroundColor); if (c && c.a > 0.05) return d; // תיבה עם רקע משלה = ציטוט } // גיבוי: אלמנט עם טקסט שמופיע לפני תוכן ההודעה const bg = [...b.children].find((c) => c.matches(SEL.message)); if (!bg) return null; for (const ch of b.children) { if (ch === bg) break; if (ch.classList.contains('wa-meta') || ch.matches(SEL.receipts) || ch.classList.contains('wa-seen')) continue; if (!ch.textContent.trim() || isBigMedia(ch) || ch.querySelector(SEL.receipts) || isOverlay(ch, b)) continue; return ch; } return null; } const ME_RE = /^(את\s*[\/\\]\s*ה|את|אתה|you|me)$/i; // כך גוגל מציגה אותי ככותב של הודעה מצוטטת // בועה שלא השתנתה ואין בה ציטוט - לא סורקים שוב (הסריקה מריצה getComputedStyle על עשרות אלמנטים בכל בועה, בכל פריים) const quoteSig = new WeakMap(); function markQuote(b) { const sig = b.childElementCount + ':' + b.textContent.length; if (!b.querySelector('.wa-quote') && quoteSig.get(b) === sig) return; quoteSig.set(b, sig); // אם קודם סומן רק חלק פנימי של הציטוט (למשל שורת הטקסט), עוברים למיכל החיצוני. אחרת המתיחה של fitQuote // מושכת את שורת הטקסט למעלה, והאייקון והשם שבשורה שמעליה "עולים" על הציטוט. const box = b.querySelector(SEL.quoteBox), old = b.querySelector('.wa-quote'); if (box && old && old !== box && box.contains(old)) { delCls(old, 'wa-quote'); delAttr(old, 'data-wa-fit'); ['margin-left', 'margin-right', 'margin-top'].forEach((p) => old.style.removeProperty(p)); } const q = findQuote(b); if (!q) return; addCls(q, 'wa-quote'); // אייקון הגרשיים הוורוד: אלמנט ריק שגוגל מעצבת עם mask של quote_decoration - מוסתר בכל מקום בבועה b.querySelectorAll('div:empty, span:empty, i:empty').forEach((e) => { if (e.classList.contains('wa-qglyph')) return; const cs = getComputedStyle(e), m = cs.maskImage || cs.webkitMaskImage || ''; if (m.includes('quote_decoration')) addCls(e, 'wa-qglyph'); }); for (let n = q.parentElement; n && n !== b; n = n.parentElement) addCls(n, 'wa-qwrap'); // העוטפים מתחת לבועה - גם הם נמתחים // שם הכותב = הטקסט הראשון בציטוט (מסתכלים על 3 הטקסטים הראשונים; אייקון מגופן אייקונים כמו format_quote מדלגים עליו ומסתירים) const tw = document.createTreeWalker(q, NodeFilter.SHOW_TEXT); let nameEl = null, me = false, seen = 0; for (let n = tw.nextNode(); n && seen < 3; n = tw.nextNode()) { const t = n.nodeValue.trim(); if (!t) continue; const pe = n.parentElement; if (/^[a-z][a-z0-9_]*$/.test(t) && (t.includes('_') || /symbols|material|icons/i.test(getComputedStyle(pe).fontFamily))) { addCls(pe, 'wa-qglyph'); // אייקון (למשל format_quote) - לא שם הכותב continue; } seen++; if (!nameEl) nameEl = pe; if (ME_RE.test(t)) { me = true; nameEl = pe; break; } } if (nameEl && nameEl !== q) addCls(nameEl, 'wa-qname'); q.querySelectorAll('svg, img, i').forEach((g) => { if (!g.textContent.trim() && g.offsetWidth <= 24 && g.offsetHeight <= 24) addCls(g, 'wa-qglyph'); }); setAttr(q, 'data-wa-me', String(me)); } // הציטוט נמתח עד 3px מקצוות הבועה (כמו בוואטסאפ), בלי קשר לריפוד ולעוטפים של גוגל: // מודדים איפה הוא יושב בלי התיקון שלנו, ומושכים אותו החוצה בשוליים שליליים const QUOTE_GAP = 3; function fitQuote(b) { const q = b.querySelector('.wa-quote'); if (!q) return; const cur = (q.getAttribute('data-wa-fit') || '0,0,0').split(',').map(Number); // left,right,top שכבר הוחלו const br = b.getBoundingClientRect(), qr = q.getBoundingClientRect(); if (!br.width || !qr.width) return; const natL = qr.left - br.left + cur[0], natR = br.right - qr.right + cur[1], natT = qr.top - br.top + cur[2]; const l = Math.max(0, Math.round(natL - QUOTE_GAP)), r = Math.max(0, Math.round(natR - QUOTE_GAP)); const t = natT < 24 ? Math.max(0, Math.round(natT - QUOTE_GAP)) : 0; // רק אם הציטוט הוא הדבר הראשון בבועה const fit = l + ',' + r + ',' + t; if (fit === q.getAttribute('data-wa-fit')) return; q.setAttribute('data-wa-fit', fit); q.style.setProperty('margin-left', -l + 'px', 'important'); q.style.setProperty('margin-right', -r + 'px', 'important'); q.style.setProperty('margin-top', -t + 'px', 'important'); } // הרוחב הטבעי של שורת השעה+וי וי (לא offsetWidth: היא ממוקמת בצורה מוחלטת ונמתחת לרוחב הבועה) // רוחב שורת השעה: נמדד פעם אחת לכל צורה (אורך השעה + עם/בלי וי וי) ונשמר, במקום למדוד כל בועה בכל סבב const metaWidths = new Map(); function metaWidth(m) { const key = (m.querySelector('.wa-time') || {}).textContent.length + '|' + ((m.querySelector('.wa-tk') || {}).getAttribute?.('data-hidden') || '') + '|' + (m.getAttribute('data-wa-rc') || '').length; if (metaWidths.has(key)) return metaWidths.get(key); const ws = [...m.children].map((c) => c.getBoundingClientRect().width).filter((w) => w > 0); const w = Math.ceil(ws.reduce((a, x) => a + x, 0) + (ws.length > 1 ? 12 : 0)); if (w) metaWidths.set(key, w); return w; } // בדיקה שהתמונה באמת נטענת במידות הצפויות (מסנן שמחליף אותה בסימן יחזיר מידות אחרות או שגיאה) const probe = (url, w, h) => new Promise((res) => { const im = new Image(); const t = setTimeout(() => res(false), 4000); im.onload = () => { clearTimeout(t); res(im.naturalWidth === w && im.naturalHeight === h); }; im.onerror = () => { clearTimeout(t); res(false); }; im.src = url; }); async function pickBackground() { const root = document.documentElement; if (BG_MODE !== 'auto') { setAttr(root, 'data-wa-bg', BG_MODE === 'image' ? 'img' : BG_MODE); return; } let mode; if (await probe(IMG_LIGHT, 760, 1396)) mode = 'img'; else mode = (await probe(SVG_LIGHT, 80, 80)) ? 'svg' : 'flat'; if (!stopped) setAttr(root, 'data-wa-bg', mode); } // בדיקה שנייה, בזמן הריצה: אם הערך המחושב של הרקע כבר לא התמונה שלנו - מישהו החליף אותו let lastBgCheck = 0; function bgWatch() { if (BG_MODE !== 'auto' || Date.now() - lastBgCheck < 3000) return; lastBgCheck = Date.now(); const root = document.documentElement, mode = root.getAttribute('data-wa-bg'); const sc = document.querySelector('.wa-scroller'); if (!sc || !mode || mode === 'flat') return; // לפני שהבדיקה הראשונה הסתיימה - לא מחליטים const bi = getComputedStyle(sc).backgroundImage || ''; if (!bi || bi === 'none') return; if (!bi.includes(mode === 'img' ? 'data:image/webp' : 'data:image/svg')) setAttr(root, 'data-wa-bg', mode === 'img' ? 'svg' : 'flat'); } let isRtl = true; const lum = (c) => c.r * 0.299 + c.g * 0.587 + c.b * 0.114; const PAINT_SKIP = new Set(['IMG', 'VIDEO', 'CANVAS', 'svg', 'SCRIPT', 'STYLE', 'IFRAME', 'LINK', 'META']); function theme() { const root = document.documentElement; isRtl = getComputedStyle(document.body || root).direction === 'rtl'; setAttr(root, 'data-wa-ticks', TICKS_LAYOUT); setAttr(root, 'data-wa-rtl', String(isRtl)); setAttr(root, 'data-chatsapp-version', VERSION); // לאבחון: איזו גרסה רצה בפועל (נראה מכל קונסול) } // בסרגל העליון הטקסט הופך ללבן, אבל לתיבת החיפוש ולכפתור הסטטוס יש רקע לבן משלהם - הם הופכים לשקופים-בהירים const headChecked = new WeakSet(); function headerFix() { const h = document.querySelector(SEL.header); if (!h) return; const todo = []; h.querySelectorAll('*').forEach((e) => { if (headChecked.has(e) || PAINT_SKIP.has(e.tagName)) return; headChecked.add(e); const b = parseRGB(getComputedStyle(e).backgroundColor); if (b && b.a > 0.5 && lum(b) > 170) todo.push(e); }); todo.forEach((e) => addCls(e, 'wa-hdbg')); } // תיבת ההקלדה: גוגל מציירת את הכדור הכחול בפסאודו-אלמנט ::before של אחד העוטפים, ושדה הטקסט עצמו שקוף. // צובעים את הכדור (wa-compose) ואת המשטח שמסביב (wa-compose-panel), ולא את שדה הטקסט - אחרת יש קופסה בתוך קופסה. // "לא נקראו" ו"X כותב/ת...": גוגל מציירת אותם על פס לבן (עם צל) שלא משתלב ברקע של וואטסאפ. // אין להם סלקטור קבוע, לכן מוצאים אותם לפי הטקסט (XPath - חיפוש מהיר של הדפדפן), ואז: // - הפס שמסביב (רקע אטום / צל) נהיה שקוף → wa-clearbar // - הטקסט עצמו מקבל תווית מעוגלת כמו מפרידי התאריכים → wa-unread / wa-typing const TYPING_RE = /כותב|כותבים|מקליד|typing/i; const UNREAD_RE = /^(\d+\s*)?(הודעות\s*)?ש?לא\s*נקראו$|^(\d+\s*)?unread( messages)?$|^new messages$/i; const BAR_XPATH = ".//*[text()[contains(., 'כותב') or contains(., 'מקליד') or contains(., 'typing') or contains(., 'נקראו') or contains(., 'nread') or contains(., 'ew messages')]]"; let lastBars = 0; function barsFix() { if (Date.now() - lastBars < 400) return; lastBars = Date.now(); const main = document.querySelector('.wa-main'); if (!main) return; const mainW = main.getBoundingClientRect().width || 1; const snap = document.evaluate(BAR_XPATH, main, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0; i < snap.snapshotLength; i++) { const el = snap.snapshotItem(i); if (el.closest('.wa-bubble, ' + SEL.message + ', ' + SEL.composer + ', .wa-typing, .wa-unread')) continue; const txt = el.textContent.replace(/\s+/g, ' ').trim(); const kind = UNREAD_RE.test(txt) ? 'wa-unread' : (TYPING_RE.test(txt) && txt.length < 80 ? 'wa-typing' : null); if (!kind) continue; // התווית: הטקסט, או עוטף קטן שלו שכולל גם את הנקודות המונפשות (לא רחב בהרבה מהטקסט עצמו) let pill = el; const textW = el.getBoundingClientRect().width; for (let n = el.parentElement, k = 0; n && k < 3 && n !== main && !n.classList.contains('wa-scroller'); n = n.parentElement, k++) { const w = n.getBoundingClientRect().width; if (w <= textW + 120 && w < mainW * 0.6) pill = n; else break; } addCls(pill, kind); if (kind === 'wa-unread' && pill.parentElement) { // המפריד של גוגל: קופסה לבנה עם קו גלי (svg) והתווית צפה מעליו. בוואטסאפ יש רק תווית במרכז - הקו מוסתר const row = pill.parentElement; addCls(row, 'wa-unread-row'); [...row.children].forEach((c) => { if (c !== pill && !c.textContent.trim()) addCls(c, 'wa-barline'); }); } // הפס שמסביב: רקע אטום או צל עד אזור ההודעות (לא נוגעים בתיבת ההקלדה) for (let n = pill.parentElement, k = 0; n && k < 6 && n !== main && !n.classList.contains('wa-scroller'); n = n.parentElement, k++) { if (n.querySelector(SEL.composer)) break; const cs = getComputedStyle(n), b = parseRGB(cs.backgroundColor); if ((b && b.a > 0.3) || cs.boxShadow !== 'none') addCls(n, 'wa-clearbar'); } } } // תמונות וסרטונים בלי טקסט: לגוגל אין להם בועה. עוטפים אותם במסגרת לבנה/ירוקה לפי השולח, כמו בוואטסאפ. // המסגרת = העוטף הגבוה ביותר שעדיין בגודל של התמונה (כך גם הכפתורים שעל התמונה נשארים בתוכה). function frameMedia(oneSided, seenSlots) { document.querySelectorAll('.wa-scroller img, .wa-scroller video').forEach((m) => { if (m.closest('.wa-bubble, .wa-mediabox, [data-member-id], .wa-quote, ' + SEL.toolbar + ', ' + SEL.receipts)) return; const r = m.getBoundingClientRect(); if (r.width < 80 || r.height < 60) return; // אימוג'י, אווטאר, אייקון - לא const sc = m.closest('.wa-scroller'); let f = m; for (let n = m.parentElement, k = 0; n && k < 6 && n !== sc; n = n.parentElement, k++) { const w = n.getBoundingClientRect().width; if (w <= r.width + 24) f = n; else break; } const own = boxOwn(f, sc, oneSided); if (own === null) return; addCls(f, 'wa-mediabox'); setAttr(f, 'data-wa-own', String(own)); }); } // של מי תמונה / הודעה קולית (אין להן בועה של גוגל): לפי מזהה השולח, ואם אין - לפי הצד (לא במרחב) function boxOwn(f, sc, oneSided) { const sender = senderOf(f); if (MY_ID && sender) return sender === MY_ID; if (!sc || oneSided.has(sc) || inSpace(f)) return null; const s = sc.getBoundingClientRect(), fr = f.getBoundingClientRect(); const dl = fr.left - s.left, dr = s.right - fr.right; if (Math.abs(dl - dr) < 24) return null; return isRtl ? dl < dr : dr < dl; } // שעה + וי וי בתוך מסגרת התמונה (תווית כהה-שקופה בפינה) ובתוך בועת ההודעה הקולית (אפור, בתחתית, כמו בבועת טקסט). // "נקרא" = יש אחריהן אווטאר "נקרא" של מישהו אחר (אותה שיטה כמו בהודעות טקסט). function boxMeta(seenSlots) { document.querySelectorAll('.wa-mediabox, .wa-voicebox').forEach((f) => { let m = f.querySelector(':scope > .wa-mmeta'); if (!m) { m = document.createElement('span'); m.className = 'wa-mmeta'; const t = document.createElement('span'); t.className = 'wa-time'; m.appendChild(makeTicks('wa-tk')); m.appendChild(t); f.appendChild(m); } const time = msgTime(f); const timeEl = m.querySelector('.wa-time'), tk = m.querySelector('.wa-tk'); if (timeEl.textContent !== time) timeEl.textContent = time; if (f.getAttribute('data-wa-own') === 'true' && (!inSpace(f) || seenSlots.length > 0)) { // (קבוצה: ראה ticksHere) const read = seenSlots.some((c) => f.compareDocumentPosition(c) & Node.DOCUMENT_POSITION_FOLLOWING); delAttr(tk, 'data-hidden'); setAttr(tk, 'data-state', read ? 'read' : 'sent'); } else setAttr(tk, 'data-hidden', 'true'); setAttr(m, 'data-empty', String(!time && tk.getAttribute('data-hidden') === 'true')); }); } // ---- הודעה קולית ---- // נגן הודעה קולית = כפתור "הפעל/השהה" שליד יש זמן בצורה 0:00/0:05 const PLAY_BTN = 'button[aria-label^="הפעל"], button[aria-label^="השה"], button[aria-label^="Play"], button[aria-label^="Pause"]'; const VOICE_TIME_RE = /\d+:\d\d\s*\/\s*\d+:\d\d/; function voiceBoxes(oneSided) { // שורת הנגן: גוגל מסמנת אותה data-media-type="audio". גיבוי: כפתור "הפעל" שלידו זמן בצורה 0:00/0:05 const rows = new Set(document.querySelectorAll('[data-media-type="audio"]')); document.querySelectorAll(PLAY_BTN).forEach((btn) => { if (btn.closest('[data-media-type="audio"]')) return; for (let n = btn.parentElement, k = 0; n && k < 3; n = n.parentElement, k++) { if (VOICE_TIME_RE.test(n.textContent)) { rows.add(n); break; } } }); rows.forEach((row) => { // הכרטיס של ההודעה הקולית (אצל גוגל: מסגרת אפורה עם פינות 20px) הופך לבועה ירוקה/לבנה לפי השולח if (row.closest('.wa-voicebox')) return; for (let n = row.parentElement, k = 0; n && k < 4; n = n.parentElement, k++) { const cs = getComputedStyle(n); if (parseFloat(cs.borderTopWidth) > 0 && parseFloat(cs.borderTopLeftRadius) >= 12) { const own = boxOwn(n, n.closest('.wa-scroller'), oneSided); if (own !== null) { addCls(n, 'wa-voicebox'); setAttr(n, 'data-wa-own', String(own)); } break; } } }); } // כל תיבות הכתיבה (גם זו שבחלונית השרשור) function composeFix() { document.querySelectorAll(SEL.composer).forEach((box) => { if (!box.closest('.wa-compose')) composeOne(box); sendCircle(box); }); } // העיגול הירוק של וואטסאפ: כפתור השליחה של גוגל. כשהתיבה ריקה (השליחה מושבתת) הוא מציג מיקרופון, // ולחיצה עליו מפעילה את כפתור ההקלטה של גוגל (שמוסתר מתוך הכדור) const SEND_BTN = '[jsname="GBTyxb"], button[aria-label="שליחת הודעה"], button[aria-label="Send message"]'; const MIC_BTN = '[jsname="EATp9"], button[aria-label="הוספת הודעה קולית"], button[aria-label^="Record"]'; function sendCircle(box) { const pill = box.closest('.wa-compose'); const row = pill && pill.parentElement; const send = row && row.querySelector(SEND_BTN); if (!send) return; let grp = send; while (grp.parentElement && grp.parentElement !== row) grp = grp.parentElement; if (!grp.classList.contains('wa-sendgrp')) { addCls(grp, 'wa-sendgrp'); grp.addEventListener('click', (e) => { if (stopped || grp.getAttribute('data-wa-empty') !== 'true') return; const mic = row.querySelector(MIC_BTN); if (!mic) return; e.preventDefault(); e.stopPropagation(); mic.click(); }, true); } setAttr(grp, 'data-wa-empty', String(!!send.disabled || send.getAttribute('aria-disabled') === 'true')); } function composeOne(box) { let pill = null; for (let n = box.parentElement, k = 0; n && k < 10; n = n.parentElement, k++) { if (!pill) { const p = getComputedStyle(n, '::before'), b = parseRGB(p.backgroundColor); if (p.content !== 'none' && b && b.a > 0 && parseFloat(p.borderRadius) >= 12) { pill = n; addCls(n, 'wa-compose'); } } else { const b = parseRGB(getComputedStyle(n).backgroundColor); if (b && b.a > 0.5) { addCls(n, 'wa-compose-panel'); break; } } } } /* ================= מצב כהה (רק בתוך חלון השיחה) ================= */ // ההגדרה נשמרת ב-localStorage של chat.google.com, כך שהיא משותפת לאתר הצ'אט ולצ'אט שבתוך Gmail const DARK_KEY = 'chatsapp-dark'; const darkOn = () => { try { return localStorage.getItem(DARK_KEY) === '1'; } catch (e) { return false; } }; function applyDark() { const on = darkOn(); setAttr(document.documentElement, 'data-wa-dark', String(on)); if (on) makeDarkBg(); document.querySelectorAll('.wa-darkbtn').forEach((b) => { const t = on ? 'מצב בהיר' : 'מצב כהה'; if (b.title !== t) { b.title = t; b.setAttribute('aria-label', t); } setAttr(b, 'aria-pressed', String(on)); }); } // announce: לספר לחלון הקופץ של התוסף (דרך bridge.js) שהמצב השתנה כאן function setDark(on, announce) { try { localStorage.setItem(DARK_KEY, on ? '1' : '0'); } catch (e) {} applyDark(); dkLast = 0; // בלי השהיה - שהמעבר יראה מיד tag(); if (announce) window.postMessage({ chatsapp: 'dark-changed', on }, '*'); } const toggleDark = () => setDark(!darkOn(), true); const onStorage = (e) => { if (e.key === DARK_KEY) { applyDark(); schedule(); } }; // שינוי בחלון/לשונית אחרים // בגרסת התוסף: החלון הקופץ משנה מצב כהה/בהיר דרך bridge.js, שמעביר הודעה לדף (בסקריפט של Tampermonkey אין גשר - ולא קורה כלום) const onBridge = (e) => { const d = e.data; if (e.source !== window || !d || d.chatsapp !== 'set-dark') return; if (!!d.on !== darkOn()) setDark(!!d.on, false); }; // הרקע הכהה: אותה דוגמה של וואטסאפ, מחושבת מהתמונה הבהירה (רקע כהה, ציורים אפורים עמומים) function makeDarkBg() { if (makeDarkBg.started) return; makeDarkBg.started = true; const img = new Image(); img.onload = () => { try { // בחצי גודל (הרקע מוצג ממילא ברוחב BG_WIDTH): פי 4 פחות פיקסלים לעבד ולקודד const c = document.createElement('canvas'); c.width = Math.round(img.naturalWidth / 2); c.height = Math.round(img.naturalHeight / 2); const x = c.getContext('2d'); x.drawImage(img, 0, 0, c.width, c.height); const d = x.getImageData(0, 0, c.width, c.height), p = d.data, hist = new Uint32Array(256); for (let i = 0; i < p.length; i += 4) hist[(p[i] * 0.299 + p[i + 1] * 0.587 + p[i + 2] * 0.114) | 0]++; let bgL = 0, lo = 0, acc = 0; for (let l = 0; l < 256; l++) if (hist[l] > hist[bgL]) bgL = l; // צבע הרקע = הנפוץ ביותר for (let l = 0; l < 256; l++) { acc += hist[l]; if (acc > p.length / 4 * 0.02) { lo = l; break; } } // הקווים הכהים ביותר const BG = [11, 20, 26], FG = [44, 58, 66], span = Math.max(1, bgL - lo); for (let i = 0; i < p.length; i += 4) { const l = p[i] * 0.299 + p[i + 1] * 0.587 + p[i + 2] * 0.114; const t = Math.max(0, Math.min(1, (bgL - l) / span)); p[i] = BG[0] + (FG[0] - BG[0]) * t; p[i + 1] = BG[1] + (FG[1] - BG[1]) * t; p[i + 2] = BG[2] + (FG[2] - BG[2]) * t; p[i + 3] = 255; } x.putImageData(d, 0, 0); // קידוד אסינכרוני (toBlob) במקום toDataURL, כדי שהקידוד לא יתקע את הדף c.toBlob((blob) => { if (!blob || stopped) return; const fr = new FileReader(); fr.onload = () => { if (!stopped) document.documentElement.style.setProperty('--wa-img-dark', 'url("' + fr.result + '")'); }; fr.readAsDataURL(blob); }, 'image/webp', 0.9); } catch (e) { tag.error = 'darkbg: ' + e; } }; img.src = IMG_LIGHT; } // "חלון השיחה" = המלבן המעוגל החיצוני שמכיל את סרגל השם, ההודעות, תיבת הכתיבה וכפתורי הנעיצה/השרשורים // (יש שני מעוגלים מקוננים; לוקחים את החיצוני, אבל לא משהו שמכיל גם את רשימת הצ'אטים) const cardCache = new WeakMap(); function cardOf(main) { const c = cardCache.get(main); if (c && c.isConnected && c.contains(main)) return c; const card = findCard(main); cardCache.set(main, card); return card; } function findCard(main) { let card = main; for (let n = main, k = 0; n && n !== document.body && k < 8; n = n.parentElement, k++) { if (n.querySelector(SEL.sidebar) || n.querySelector(SEL.header)) break; if (parseFloat(getComputedStyle(n).borderTopLeftRadius) >= 16) card = n; } return card; } // הצבע הראשון בערך CSS (גם בכתיב color(srgb ...) שכרום משתמש בו בצללים) function firstColor(v) { const m = v && v.match(/rgba?\([^)]+\)|color\(srgb [^)]+\)/); if (!m) return null; if (m[0][0] === 'r') return parseRGB(m[0]); const p = m[0].slice(11, -1).split(/[ \/]+/).filter(Boolean).map(Number); return { r: p[0] * 255, g: p[1] * 255, b: p[2] * 255, a: p.length > 3 ? p[3] : 1 }; } // כפתור ירח ליד כפתור שיחת הווידאו שבסרגל העליון של השיחה const MOON = 'M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z'; function darkButton(card) { if (!card) return; const old = card.querySelector('.wa-darkbtn'); if (old && !old.classList.contains('wa-float')) return; // ליד כפתור שיחת הווידאו. במרחבים אין כזה - שם הירח "צף" צמוד לקופסת הנעיצה/השרשורים/הקבצים, // בדיוק במקום שבו בצ'אט אישי יושב הווידאו (placeFloatMoon) const top = card.getBoundingClientRect().top + 72; const vid = [...card.querySelectorAll('button[aria-label], [role="button"][aria-label]')] .find((b) => { const r = b.getBoundingClientRect(); return r.width && r.top < top && /וידאו|video/i.test(b.getAttribute('aria-label')); }); if (old) { if (!vid) { placeFloatMoon(card, old); return; } old.remove(); // עברנו ממרחב לצ'אט אישי - הירח חוזר לצד הווידאו } const btn = makeMoon(); if (vid) { let at = vid; while (at.parentElement && at.parentElement !== card && at.parentElement.childElementCount === 1) at = at.parentElement; at.parentElement.insertBefore(btn, at); } else { // בתוך הפס העליון עצמו (בשכבה של החלון כולו הוא מוסתר מתחת לשכבה של הפס) const head = card.querySelector('.wa-chathead') || card.querySelector('[jsname="uwkwCe"]'); if (!head) return; btn.classList.add('wa-float'); head.appendChild(btn); placeFloatMoon(card, btn); } applyDark(); } function placeFloatMoon(card, btn) { const cr = card.getBoundingClientRect(); const box = [...card.querySelectorAll('[jsname="Zpe2Q"]')].find((z) => { const r = z.getBoundingClientRect(); return r.width && r.top < cr.top + 72; }); if (!box) { btn.style.display = 'none'; return; } const r = box.getBoundingClientRect(); // (position: fixed - קואורדינטות של החלון, בלי לגעת במבנה של גוגל) const x = Math.round(isRtl ? r.right + 4 : r.left - 44), y = Math.round(r.top + (r.height - 40) / 2); const css = 'left:' + x + 'px;top:' + y + 'px;'; if (btn.getAttribute('style') !== css) btn.setAttribute('style', css); } function makeMoon() { const btn = document.createElement('div'); btn.className = 'wa-darkbtn'; btn.setAttribute('role', 'button'); btn.tabIndex = 0; const svg = document.createElementNS(SVGNS, 'svg'), path = document.createElementNS(SVGNS, 'path'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true'); path.setAttribute('d', MOON); svg.appendChild(path); btn.appendChild(svg); btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); toggleDark(); }); btn.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleDark(); } }); return btn; } // הפס העליון של השיחה: המשטח בראש החלון, מעל אזור ההודעות, ברוחב של אזור ההודעות (לא של חלונית השרשור) const headSeen = new WeakSet(); function chatHead(card, main) { const mr = main.getBoundingClientRect(); let head = card.querySelector('[jsname="uwkwCe"]'); // (הפס חופף קצת את אזור ההודעות - אצל גוגל בערך 9 פיקסלים) if (!head || head.getBoundingClientRect().bottom > mr.top + 20) { head = null; for (let n = main.previousElementSibling || main.parentElement; n && n !== card.parentElement; n = n.parentElement) { const h = [...n.children].find((c) => { const r = c.getBoundingClientRect(); return r.bottom <= mr.top + 20 && r.height >= 40 && r.height <= 90 && r.width >= mr.width * 0.8; }); if (h) { head = h; break; } } } document.querySelectorAll('.wa-chathead').forEach((h) => { if (h !== head) delCls(h, 'wa-chathead'); }); if (!head) return; addCls(head, 'wa-chathead'); // כפתורים עם רקע בהיר משלהם (למשל שיחת וידאו) - רקע לבן-שקוף על הפס // ומשטח בהיר שמכסה את רוב הפס (בחלון הקטן/הקופץ גוגל שמה שכבה כזו מעל הירוק, והשם הלבן נעלם) - שקוף const btns = [], clear = [], hw = head.getBoundingClientRect().width; head.querySelectorAll('*').forEach((e) => { if (headSeen.has(e) || e instanceof SVGElement) return; headSeen.add(e); const b = parseRGB(getComputedStyle(e).backgroundColor); if (!b || b.a <= 0.5 || lum(b) <= 200) return; if (e.getBoundingClientRect().width > hw * 0.6) clear.push(e); else if (b.g < 250) btns.push(e); }); btns.forEach((e) => addCls(e, 'wa-headbtn')); clear.forEach((e) => addCls(e, 'wa-headclear')); const hr = head.getBoundingClientRect(); document.querySelectorAll('[jsname="Zpe2Q"]').forEach((z) => { const r = z.getBoundingClientRect(); setCls(z, 'wa-onhead', r.top >= hr.top - 2 && r.bottom <= hr.bottom + 2 && r.left >= hr.left - 2 && r.right <= hr.right + 2); }); } // צביעת משטחים וטקסט של גוגל בתוך החלון (למצב כהה). כל אלמנט נבדק פעם אחת, בזמן פנוי של הדפדפן ובקבוצות קטנות, // כך שזה לא תוקע את הלשונית - וגם במצב בהיר, כדי שהמעבר לכהה יהיה מיידי. תוכן הבועות לא נבדק (הוא נצבע דרך המשתנים). const dkSeen = new WeakSet(); const DK_SKIP = '.wa-darkbtn, [role="menu"], [role="dialog"], [role="tooltip"], [role="listbox"], ' + SEL.toolbar; let dkLast = 0, dkTimer = 0, dkIdle = 0, dkQueue = [], dkHead = 0; const idle = window.requestIdleCallback || ((f) => setTimeout(() => f(null), 60)); function darkFix(cards) { if (!cards.length) return; const now = Date.now(); if (now - dkLast < 1000) { if (!dkTimer) dkTimer = setTimeout(() => { dkTimer = 0; schedule(); }, 1050); return; } dkLast = now; for (const card of cards) { const walk = document.createTreeWalker(card, NodeFilter.SHOW_ELEMENT, { acceptNode: (n) => (n instanceof SVGElement || PAINT_SKIP.has(n.tagName) || n.classList.contains('wa-bubble') ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT), }); for (let e = card; e; e = walk.nextNode()) if (!dkSeen.has(e)) { dkSeen.add(e); dkQueue.push(e); } } darkWork(null); } function darkWork(deadline) { const dark = document.documentElement.getAttribute('data-wa-dark') === 'true'; const budget = deadline ? Math.min(deadline.timeRemaining(), 10) : (dark ? 10 : 0); // במצב בהיר רק בזמן פנוי const until = performance.now() + budget; while (dkHead < dkQueue.length && performance.now() < until) darkOne(dkQueue[dkHead++]); if (dkHead >= dkQueue.length) { dkQueue = []; dkHead = 0; } else if (!dkIdle && !stopped) dkIdle = idle((d) => { dkIdle = 0; darkWork(d); }); } function darkOne(e) { if (!e.isConnected || e.closest(DK_SKIP)) return; const cs = getComputedStyle(e); const bg = parseRGB(cs.backgroundColor); // אלמנטים שהעיצוב שלנו כבר צובע (בועות, רקע, תיבת כתיבה). הסימונים של הירוק/הכהה/החלון לא נחשבים const ours = [...e.classList].some((c) => c.startsWith('wa-') && !/^wa-(acc|dk-|card|navsel|navtxt)/.test(c)); // (לא הבועה המקורית של גוגל - ההורה הישיר של ההודעה - שמקבלת את הצבע שלה מהעיצוב של הבועות) if (!ours && bg && bg.a > 0.5 && lum(bg) > 200 && !e.querySelector(':scope > ' + SEL.message)) addCls(e, e.closest('.wa-compose') ? 'wa-dk-in' : 'wa-dk-bg'); if (!ours) { const bd = ['Top', 'Right', 'Bottom', 'Left'].some((s) => parseFloat(cs['border' + s + 'Width']) > 0 && (parseRGB(cs['border' + s + 'Color']) || { r: 0, g: 0, b: 0 }).r > 150); if (bd) addCls(e, 'wa-dk-bd'); const sh = cs.boxShadow !== 'none' && firstColor(cs.boxShadow); if (sh && sh.a > 0.2 && lum(sh) > 150) addCls(e, 'wa-dk-sh'); const psh = ['::before', '::after'].some((p) => { const v = getComputedStyle(e, p).boxShadow; const c = v !== 'none' && firstColor(v); return c && c.a > 0.2 && lum(c) > 150; }); if (psh) addCls(e, 'wa-dk-psh'); // למשל הקו הבהיר מעל תיבת הכתיבה } const fg = parseRGB(cs.color); if (fg && Math.max(fg.r, fg.g, fg.b) - Math.min(fg.r, fg.g, fg.b) < 60) { // רק אפורים/שחורים - קישורים צבעוניים נשארים const l = lum(fg); if (l < 90) addCls(e, 'wa-dk-tx'); else if (l < 150) addCls(e, 'wa-dk-sub'); } } // האזור שמתחת להודעות כשאין תיבת כתיבה (למשל מרחב שרק מנהלים כותבים בו): גוגל שמה לוח לבן גדול // ובתוכו את ההודעה "רק הבעלים והמנהלים...". הלוח החיצוני נעשה שקוף (רואים את הטפט), וההודעה עצמה נשארת const footSeen = new WeakMap(); // כמה אלמנטים היו באזור בבדיקה הקודמת - בודקים שוב רק כשזה משתנה function footFix(main) { const sc = main.querySelector(':scope > .wa-scroller, :scope .wa-scroller'); if (!sc) return; let after = false; for (const top of main.children) { if (top.contains(sc)) { after = true; continue; } const n = top.getElementsByTagName('*').length; if (!after || footSeen.get(top) === n) continue; footSeen.set(top, n); const light = (e) => { const b = parseRGB(getComputedStyle(e).backgroundColor); return !!b && b.a > 0.5 && lum(b) > 200; }; const walk = (e, d) => { if (d > 8 || e instanceof SVGElement) return; if (light(e)) { if ([...e.querySelectorAll('*')].some((x) => !(x instanceof SVGElement) && light(x))) addCls(e, 'wa-footclear'); return; // החיצוני ביותר בכל ענף - לא נכנסים פנימה } for (const c of e.children) walk(c, d + 1); }; walk(top, 0); } } // הכחול של גוגל בחלון השיחה, מחוץ לאזור ההודעות (סרגל עליון, תיבת כתיבה, חלונית השרשור, תוויות צפות). // נבדק מחדש בכל סבב (בהשהיה), כי "נבחר"/"לא נבחר" משתנה - והצבע המקורי לא נדרס, אז אפשר לקרוא אותו שוב. let accLast = 0, accTimer = 0; const accPseudo = new WeakSet(); // שכבת ה-::before של גוגל נבדקת פעם אחת לכל אלמנט function accentFix(cards) { const now = Date.now(); if (now - accLast < 1000) { if (!accTimer) accTimer = setTimeout(() => { accTimer = 0; schedule(); }, 1050); return; } accLast = now; cards.forEach(accentCard); } function accentCard(card) { const walk = document.createTreeWalker(card, NodeFilter.SHOW_ELEMENT, { acceptNode: (n) => { if (n instanceof SVGElement) return NodeFilter.FILTER_REJECT; if (n.classList.contains('wa-scroller')) { // אזור ההודעות עצמו: רק התוויות הצפות ("הודעות חדשות", "מעבר לתחתית") - לא אלפי האלמנטים של ההודעות n.querySelectorAll('[jsname="lccMsc"], [jsname="ayNidc"]').forEach((p) => addCls(p, 'wa-acc')); return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; }, }); // קודם קוראים את כל הצבעים ורק אחר כך משנים מחלקות - שינוי באמצע מכריח חישוב סגנון מחדש לכל אלמנט const todo = []; for (let e = walk.nextNode(); e; e = walk.nextNode()) { const cs = getComputedStyle(e), bg = parseRGB(cs.backgroundColor), fg = parseRGB(cs.color); let pb = false; if (!accPseudo.has(e) && !e.classList.contains('wa-compose')) { // (הכדור של תיבת הכתיבה נשאר לבן) accPseudo.add(e); const b = getComputedStyle(e, '::before'); if (b.content !== 'none') { const c = parseRGB(b.backgroundColor); pb = !!c && c.b >= 180 && c.b - c.r >= 100; } } todo.push([e, isSelBg(bg), !!bg && bg.a > 0.5 && bg.b >= 180 && bg.b - bg.r >= 100, !!fg && fg.b - fg.r >= 60 && lum(fg) < 160, pb]); } for (const [e, light, strong, tx, pb] of todo) { setCls(e, 'wa-acc-l', light); setCls(e, 'wa-acc', strong); setCls(e, 'wa-acc-tx', tx); if (pb) addCls(e, 'wa-acc-b'); } } // של מי ההודעה - לפי הצד שבו גוגל מציגה אותה: בעברית ההודעות שלי בצד שמאל, באנגלית בצד ימין. // הצד ידוע מהרגע שההודעה מופיעה (גם לפני שהשרת אישר אותה), ולכן הצבע נכון מיד. // null = אי אפשר לדעת (ההודעה לא מוצגת, או באמצע - למשל הודעת מערכת) // מרחב (Space) ולא צ'אט אישי: גוגל מסמנת את אזור השיחה ב-data-group-id="space/..." (בצ'אט אישי "dm/..."). // במרחבים לא מציגים וי וי - "נקרא" שם לא אומר הרבה כשיש הרבה משתתפים. function inSpace(el) { const GID = '[data-group-id^="space/"], [data-group-id^="dm/"]'; // יש גם data-group-id בלי קידומת - מתעלמים ממנו const g = el.closest(GID) || document.querySelector('[role="main"] ' + GID); return !!g && g.getAttribute('data-group-id').startsWith('space/'); } // מזהה השולח: בדרך כלל על קבוצת ההודעות (role="group"), ואם לא - על כל עוטף שיש לו data-user-id function senderOf(el) { const g = el.closest(SEL.group) || el.closest('[data-user-id]'); return g ? g.getAttribute('data-user-id') : null; } function sideOwn(el) { const sc = findScroller(el); // אזור הגלילה של ההודעה עצמה (גם חלונית שרשור), לא של השיחה הראשית const b = el.parentElement; if (!sc || !b) return null; const s = sc.getBoundingClientRect(), r = b.getBoundingClientRect(); if (!r.width || !s.width) return null; const dl = r.left - s.left, dr = s.right - r.right; if (Math.abs(dl - dr) < 24) return null; return isRtl ? dl < dr : dr < dl; } /* ================= העיצוב עצמו ================= */ function tag() { const t0 = performance.now(); try { tagInner(); } catch (e) { tag.error = (e && (e.stack || e.message)) || String(e); if (!tag.warned) { tag.warned = true; console.warn('[ChatsApp] שגיאה בעיצוב (הסקריפט ממשיך):', e); } } tag.ms = Math.round(performance.now() - t0); // לאבחון: כמה זמן לקח הסבב האחרון / הארוך ביותר tag.maxMs = Math.max(tag.maxMs || 0, tag.ms); } function tagInner() { if (stopped) return; const T = {}; let tp = performance.now(); const lap = (k) => { const n = performance.now(); T[k] = Math.round(n - tp); tp = n; }; // לאבחון: זמן כל שלב tag.sections = T; theme(); const msgs = [...document.querySelectorAll(SEL.message)]; const state = []; // לכל הודעה: {el, bubble, own, scroller} const hasGroups = !!document.querySelector(SEL.group); // אזור הגלילה של כל הודעה. ההודעות שבחלונית שרשור לא נמצאות בתוך אזור ההודעות הראשי של גוגל, // אז שם לוקחים את העוטף הגליל הקרוב (גם אם עוד אין בו מספיק הודעות כדי לגלול) const paneOf = (el) => { const c = panes.get(el); if (c !== undefined && (c === null || c.contains(el))) return c; let p = el.closest(SEL.scroller); for (let n = el.parentElement; !p && n && n !== document.body; n = n.parentElement) { if (/(auto|scroll)/.test(getComputedStyle(n).overflowY) && n.clientHeight > 150) p = n; // (לא תיבת קוד וכד' בתוך הודעה) } panes.set(el, p || null); return p || null; }; // אזורי גלילה שבהם כל ההודעות באותו צד למרות שיש כמה שולחים (למשל חלונית שרשור): שם הצד לא אומר מי שלח const oneSided = new Set(), layout = new Map(); msgs.forEach((el) => { if (!firstSeen.has(el)) firstSeen.set(el, Date.now()); const sc = paneOf(el), sender = senderOf(el); if (!sc) return; if (!layout.has(sc)) layout.set(sc, { sides: new Set(), senders: new Set() }); const L = layout.get(sc), side = sideOwn(el); if (side !== null) L.sides.add(side); if (sender) L.senders.add(sender); (L.msgs || (L.msgs = [])).push(el); }); // ההודעה בין 3 האחרונות של אזור הגלילה שלה (חלונית שרשור היא אזור נפרד, ולא בהכרח האחרונה בדף) const lastInPane = (el, i) => { const L = layout.get(paneOf(el)); return L ? L.msgs.indexOf(el) >= L.msgs.length - 3 : i >= msgs.length - 3; }; // הופיעה לראשונה ממש אחרי ששלחתי (Enter או כפתור השליחה) const justSent = (el) => { const t = firstSeen.get(el) || 0; return lastSend && t >= lastSend - 300 && t - lastSend < 10000; }; layout.forEach((L, sc) => { if (L.senders.size > 1 && L.sides.size === 1) oneSided.add(sc); }); // 1) מי שלח כל הודעה: קודם לפי מזהה השולח (הכי אמין, גם בשרשורים), ורק כשאין מזהה - לפי הצד על המסך. // במרחב גוגל מציגה את כל ההודעות באותו צד, ולכן שם הצד לא נלקח בחשבון בכלל // (כשחלונית שרשור פתוחה, אזור ההודעות מצטמצם וההודעות נראות "בצד שלי" - כך כולן נצבעו בירוק). msgs.forEach((el, i) => { const sender = senderOf(el); const prev = el.parentElement && el.parentElement.getAttribute('data-wa-own'); const space = inSpace(el); const side = (space || oneSided.has(paneOf(el))) ? null : sideOwn(el); let own = null; // הופיעה מיד אחרי ששלחתי, עם בדיוק הטקסט ששלחתי - שלי, גם אם גוגל שמה אותה לרגע בקבוצה של מישהו אחר // או עוד בלי מזהה שולח (במרחבים זה קורה, וכך היא נשארה לבנה עד שהשרת אישר) if (!mineBySend.has(el) && !(MY_ID && sender === MY_ID)) { const t = norm(el.textContent), seen = firstSeen.get(el) || 0; if (t && sentTexts.some((x) => seen >= x.at - 300 && seen - x.at < 15000 && (t === x.txt || t.startsWith(x.txt)))) mineBySend.add(el); } if (mineBySend.has(el)) own = true; else if (MY_ID && sender) own = sender === MY_ID; else if (side !== null) own = side; // הודעה שעוד בשליחה (בלי מזהה): הצד ידוע מיד // הודעה מקומית שעוד לא אושרה בשרת: אין לה מזהה שולח בזמן שלשאר ההודעות יש. רק אז היא בוודאות שלי // (אם גוגל לא חושפת מזהים בכלל, "3 האחרונות" היו מסמנות גם הודעות נכנסות כשלי) // במרחב (וגם בשרשור בתוכו) - רק אם היא הופיעה מיד אחרי שליחה שלי, אחרת זו יכולה להיות הודעה של מישהו אחר else if (!sender && hasGroups && lastInPane(el, i) && (!space || justSent(el))) own = true; else { own = colorOwn(el); // הבועה כבר צבועה שלנו ואי אפשר לקרוא את הצבע המקורי: נשארים עם ההחלטה הקודמת if (own === null && prev) own = prev === 'true'; } // (קריאות בלבד בשלב הזה - מדידות אחרי כתיבה מכריחות את הדפדפן לחשב מחדש את כל הדף, פעם לכל הודעה) const b = el.parentElement, g = el.closest(SEL.group); // יש שורת שם/שעה מעל הבועה = תחילת רצף. כמו בוואטסאפ, רק להודעה הראשונה ברצף יש זנב const head = !!(g && b && b.getBoundingClientRect().top - g.getBoundingClientRect().top > 8); const hasMedia = !!b && [...b.querySelectorAll('video, canvas, iframe, img')].some((m) => !m.closest('[data-member-id]') && m.offsetWidth > 60); // אזור הגלילה: findScroller מוצא אותו רק כשכבר יש בו גלילה. שרשור עם 2-3 הודעות עוד לא גולל, ובלי // הגיבוי (העוטף הגליל הקרוב, גם בלי גלילה) לא היה מקבל את רקע הוואטסאפ עד ההודעה השלישית state.push({ el, bubble: b, own, sender, head, hasMedia, scroller: scrollerOf(el) || paneOf(el) }); }); lap('who'); // רוחב כל אזור הודעות (עדיין שלב קריאה) - הבועה מוגבלת ל-75% ממנו const scW = new Map(); state.forEach((s) => { if (s.scroller && !scW.has(s.scroller)) scW.set(s.scroller, s.scroller.clientWidth); }); // 2) סימון בועות, זנב, רקע let prevOwn = null, prevPane = null; for (const s of state) { if (s.own === null || !s.bubble) continue; const b = s.bubble; addCls(b, 'wa-bubble'); setAttr(b, 'data-wa-own', String(s.own)); // זנב: בהודעה הראשונה ברצף (שורת שם/שעה מעליה - אחרי הפסקה או ביום חדש), או כשהשולח מתחלף setAttr(b, 'data-wa-tail', String(s.head || s.own !== prevOwn || s.scroller !== prevPane)); setCls(b, 'wa-media', s.hasMedia); prevOwn = s.own; prevPane = s.scroller; if (s.scroller) { addCls(s.scroller, 'wa-scroller'); const w = scW.get(s.scroller); if (w && s.scroller.style.getPropertyValue('--wa-scw') !== w + 'px') s.scroller.style.setProperty('--wa-scw', w + 'px'); const main = s.scroller.closest('[role="main"]'); if (main && !main.classList.contains('wa-main')) { addCls(main, 'wa-main'); // כשנפתחת חלונית שרשור, החלון הראשי נגלל הצידה (גוגל מחזיקה אלמנטים מוסתרים רחוק מחוץ למסך) - מחזירים main.addEventListener('scroll', () => { if (!stopped && main.scrollLeft !== 0) main.scrollLeft = 0; }); if (main.scrollLeft !== 0) main.scrollLeft = 0; } for (let n = b.parentElement; n && n !== s.scroller; n = n.parentElement) addCls(n, 'wa-clear'); } } lap('bubbles'); // 3) "נקרא": מזהים את האווטאר/הרשימה של גוגל, מסתירים אותם, ומחשבים עד איזו הודעה הצד השני קרא. // זיהוי אחיד בכל ריצה (איחוד של שתי שיטות) כדי שלא יהיה מעבר מצבים שגורם לריצוד. let readIdx = -1, seenCount = 0; // בקבוצה: עד איזו הודעה כל אחד קרא (האווטאר של כל אחד יושב אחרי ההודעה האחרונה שהוא קרא) const readerPos = new Map(), readerName = new Map(); const pid = (id) => (String(id).match(/\d{6,}/) || [String(id)])[0]; // מזהה אחיד (ב-data-member-id יש לפעמים קידומת) const scroller = (state.find((s) => s.scroller) || {}).scroller || document.querySelector(SEL.scroller); const seenSlots = []; const anchors = new Set(); const ghosts = new Set(); // עוטפים קטנים של האווטאר (הטבעת הלבנה) if (scroller) { const cand = new Set(); scroller.querySelectorAll(SEL.receipts).forEach((l) => { if (l.querySelector('[data-member-id], img')) cand.add(l); // רשימה ריקה = אף אחד לא קרא }); scroller.querySelectorAll('[data-member-id]').forEach((h) => { if (h.closest(SEL.receipts)) return; // כבר מכוסה דרך הרשימה שלו if (h.closest(SEL.message)) return; // בתוך תוכן הודעה (למשל האווטאר של כותב בציטוט) - לא "נקרא" if (h.closest(NOT_RECEIPT)) return; // תגובת אימוג'י / החלון שקופץ במעבר עליה (מי הגיב) - לא "נקרא" if (h.textContent.trim()) return; // יש טקסט = כפתור שם, לא אווטאר const w = h.offsetWidth, ht = h.offsetHeight; if (h.classList.contains('wa-seen') || (w > 0 && w <= 28 && ht <= 28)) cand.add(h); // האווטאר הגדול של השולח - לא נוגעים }); cand.forEach((c) => { const ids = c.matches('[data-member-id]') ? [c.getAttribute('data-member-id') || ''] : [...c.querySelectorAll('[data-member-id]')].map((x) => x.getAttribute('data-member-id') || ''); if (MY_ID && ids.length && ids.every((id) => id.includes(MY_ID))) return; // אם כל מי שקרא זה אני - זו לא קבלת קריאה seenCount++; // עד איזו הודעה קראו: ההודעה האחרונה שמופיעה לפני האווטאר for (let i = state.length - 1; i >= 0; i--) { if (state[i].el.compareDocumentPosition(c) & Node.DOCUMENT_POSITION_FOLLOWING) { readIdx = Math.max(readIdx, i); ids.forEach((id) => { const k = pid(id); if (k && !(MY_ID && k.includes(MY_ID))) readerPos.set(k, Math.max(readerPos.get(k) ?? -1, i)); }); // השם של מי שקרא: גוגל שומרת אותו על האווטאר (data-name), ואם לא - בטקסט החלופי של התמונה [c, ...c.querySelectorAll('[data-member-id]')].forEach((a) => { const k = pid(a.getAttribute('data-member-id') || ''), img = a.querySelector('img'), f = a.closest('[data-user-fullname]'); const name = a.getAttribute('data-name') || (img && img.alt) || (f && pid(f.getAttribute('data-user-id') || '') === k && f.getAttribute('data-user-fullname')) || ''; if (k && name && !readerName.has(k)) readerName.set(k, name.trim()); }); break; } } setAttr(c, 'data-wa-state', 'read'); addCls(c, 'wa-seen'); // הטבעת/המתאר הלבן שייך לעוטף הקטן שסביב האווטאר: מסתירים גם אותו (עד 3 רמות, רק קטנים וללא תוכן הודעה) for (let n = c.parentElement, k = 0; n && n !== scroller && k < 3; n = n.parentElement, k++) { if (n.offsetWidth > 40 || n.offsetHeight > 40) break; if (n.textContent.trim()) break; // עוטף עם טקסט (שם קצר וכו') - לא מסתירים if (n.querySelector('video, [jsname="bgckF"]')) break; addCls(n, 'wa-seen'); ghosts.add(n); } // הודעת מדיה בלי בועה: הוי וי כחולים משלנו מתחת לכרטיס - רק אם הכרטיס שלי (לא מתחת לסרטון שמישהו אחר שלח) if (!c.closest('.wa-bubble')) { const anchor = pickAnchor(c, scroller); const g = anchor && anchor.closest(SEL.group); const notMine = !!(g && MY_ID && g.getAttribute('data-user-id') !== MY_ID); if (anchor && !notMine) { let t = anchor.querySelector(':scope > .wa-ticks'); if (!t) { t = document.createElement('span'); t.className = 'wa-ticks'; t.appendChild(makeTicks('')); anchor.appendChild(t); } addCls(anchor, 'wa-ticks-anchor'); anchors.add(anchor); } } seenSlots.push(c); }); } // ניקוי: אלמנטים שכבר לא "נקראו" חוזרים להיות רגילים const live = new Set([...seenSlots, ...ghosts]); document.querySelectorAll('.wa-seen').forEach((x) => { if (live.has(x)) return; delCls(x, 'wa-seen'); delAttr(x, 'data-wa-state'); }); document.querySelectorAll('.wa-ticks-anchor').forEach((a) => { if (anchors.has(a)) return; delCls(a, 'wa-ticks-anchor'); a.querySelectorAll(':scope > .wa-ticks').forEach((x) => x.remove()); }); lap('read'); // VV: לא במרחב (שם "נקרא" לא אומר הרבה). אבל צ'אט קבוצתי (לא מרחב) מקבל אצל גוגל מזהה של מרחב, ושם כן מוצגים // אווטארים של מי שקרא - אז אם יש כאלה בשיחה, זו קבוצה ומציגים VV const ticksHere = (el) => !inSpace(el) || seenCount > 0; // קבוצה: כמה קראו כל הודעה, מתוך כמה אנשים שהסקריפט מכיר בשיחה (כל מי ששלח הודעה או מופיע כקורא, חוץ ממני). // VV כחולים רק כשכולם קראו; עד אז אפורים, ולידם מספר הקוראים const members = new Set(readerPos.keys()); state.forEach((s) => { if (s.own === false && s.sender) members.add(pid(s.sender)); }); // שם שלא נמצא על האווטאר עצמו: מאווטארים אחרים של אותו אדם בדף (data-name) או משורת השולח (data-user-fullname) const nameOf = (k) => { if (readerName.has(k)) return readerName.get(k); const a = [...document.querySelectorAll('[data-member-id*="' + k + '"][data-name], [data-user-id*="' + k + '"][data-user-fullname]')][0]; const n = a ? (a.getAttribute('data-name') || a.getAttribute('data-user-fullname') || '').trim() : ''; readerName.set(k, n || '?'); return n || '?'; }; const readersOf = (i) => [...readerPos].filter(([, p]) => p >= i).map(([k]) => nameOf(k)); // 4) שעה + וי וי בתוך הבועה (בפינה השמאלית התחתונה), וציטוט state.forEach((s, i) => { try { if (!s.bubble || s.own === null) return; const b = s.bubble; let m = b.querySelector(':scope > .wa-meta'); if (!m) { m = makeMeta(); b.appendChild(m); } try { markQuote(b); } catch (e) { tag.error = 'quote: ' + e; } const time = msgTime(s.el); const timeEl = m.querySelector('.wa-time'), tk = m.querySelector('.wa-tk'); if (timeEl.textContent !== time) timeEl.textContent = time; const group = inSpace(s.el) && seenCount > 0; if (s.own === true && ticksHere(s.el)) { delAttr(tk, 'data-hidden'); const who = group ? readersOf(i) : [], n = who.length; setAttr(tk, 'data-state', (group ? n > 0 && n >= members.size : i <= readIdx) ? 'read' : 'sent'); // המספר תמיד ליד ה-VV (גם כשכולם קראו והם כחולים), ובמעבר עכבר - מי קרא if (group && n > 0) setAttr(m, 'data-wa-rc', String(n)); else delAttr(m, 'data-wa-rc'); // מי קרא: מעבר עכבר על המספר או על הוי וי const rc = rcOf(m); rc.textContent = m.hasAttribute('data-wa-rc') ? String(n) : ''; if (n) setAttr(m, 'data-who', 'נקרא ע״י: ' + who.join(', ')); else delAttr(m, 'data-who'); } else { setAttr(tk, 'data-hidden', 'true'); delAttr(m, 'data-wa-rc'); rcOf(m).textContent = ''; delAttr(m, 'data-who'); } // הודעה של מישהו אחר, או הודעה שלי במרחב (לא קבוצה) - בלי וי וי // מצב השורה: inline = על אותה שורה עם הטקסט (הודעה קצרה); below = שורה משלה בתחתית. וי וי בקצה הבועה מחייב below. const txt = (s.el.textContent || '').trim(); const short = TICKS_LAYOUT !== 'bubble-start' && txt.length <= 28 && !/\n/.test(s.el.innerText || '') && !b.querySelector('.wa-quote') && !b.classList.contains('wa-media'); setAttr(b, 'data-wa-meta', short ? 'inline' : 'below'); const mw = metaWidth(m); if (mw && b.style.getPropertyValue('--wa-mw') !== mw + 'px') b.style.setProperty('--wa-mw', mw + 'px'); try { fitQuote(b); } catch (e) { tag.error = 'fitQuote: ' + e; } } catch (e) { tag.error = 'meta: ' + e; } }); lap('meta'); bgWatch(); headerFix(); composeFix(); lap('head+compose'); try { barsFix(); } catch (e) { tag.error = 'bars: ' + e; } lap('bars'); try { frameMedia(oneSided, seenSlots); } catch (e) { tag.error = 'media: ' + e; } try { voiceBoxes(oneSided); } catch (e) { tag.error = 'voice: ' + e; } try { boxMeta(seenSlots); } catch (e) { tag.error = 'boxmeta: ' + e; } lap('media'); try { // חלון השיחה הראשי, וגם חלונית השרשור (חלון נפרד לידו) const main = document.querySelector('.wa-main'); const cards = new Set(); if (main) cards.add(cardOf(main)); document.querySelectorAll('.wa-scroller').forEach((sc) => { if (!main || !main.contains(sc)) cards.add(cardOf(sc)); }); cards.forEach((card) => { addCls(card, 'wa-card'); if (main && card.contains(main)) { darkButton(card); chatHead(card, main); } }); document.querySelectorAll('.wa-main').forEach(footFix); lap('card'); darkFix([...cards]); lap('dark'); accentFix([...cards]); lap('accent'); } catch (e) { tag.error = 'dark: ' + e; } try { iconsFix(); } catch (e) { tag.error = 'icons: ' + e; } lap('icons'); tag.last = { total: msgs.length, seenCount, readUpTo: readIdx, state, seenSlots: seenSlots.length }; traceSend(state); } /* ================= הפעלה ================= */ // אבחון "לוקח זמן עד שההודעה שלי נהיית ירוקה": בכל סבב ב-8 השניות אחרי שליחה רושמים את ההודעות החדשות // (בלי תוכן - רק זמן, אורך, מזהה שולח מקוצר והחלטה), וגם אם הטקסט ששלחתי כבר מופיע בדף בלי להיות "הודעה" let sendTrace = null; function traceSend(state) { const tr = sendTrace; if (!tr || Date.now() - tr.at > 8000 || tr.events.length > 40) return; const t = Date.now() - tr.at; const fresh = state.filter((s) => (firstSeen.get(s.el) || 0) >= tr.at - 300); const last = sentTexts[sentTexts.length - 1]; let loose = 0; // הטקסט ששלחתי בדף, מחוץ להודעה מזוהה (הודעה "בדרך" במבנה אחר) if (last && last.at === tr.at) { for (const sc of document.querySelectorAll('.wa-scroller')) { const w = document.createTreeWalker(sc, NodeFilter.SHOW_TEXT); for (let n = w.nextNode(); n; n = w.nextNode()) if (norm(n.textContent) === last.txt && !n.parentElement.closest(SEL.message)) loose++; } } const ev = t + 'ms: ' + fresh.map((s) => 'len' + norm(s.el.textContent).length + ' from=' + String(s.sender || '-').slice(-4) + ' own=' + s.own + (s.bubble && s.bubble.classList.contains('wa-bubble') ? '' : ' (no-bubble)')).join(' | ') + (loose ? ' | loose=' + loose : ''); const key = ev.slice(ev.indexOf(':') + 1); if (tr.lastKey !== key) { tr.lastKey = key; tr.events.push(ev); } } // סבב עיצוב לכל היותר פעם ב-200ms: גוגל משנה את הדף כל הזמן (שעונים, ריחוף, אנימציות), // וסבב על כל שינוי תקע את הלשונית. אחרי שליחה רצים מיד (onSend), כך שההודעה עדיין נצבעת מהר. const MIN_GAP = 200; let queued = false, lastTag = 0, urgent = 0; function schedule() { if (queued || stopped) return; queued = true; setTimeout(() => { if (!queued) return; queued = false; lastTag = Date.now(); tag(); }, Math.max(16, MIN_GAP - (Date.now() - lastTag))); } // הגיעו הודעות חדשות (מעבר לשיחה, הודעה נכנסת): מעצבים מיד, לפני שהדפדפן מצייר - כדי שלא יראו לרגע בעיצוב של גוגל function scheduleNow() { if (urgent || stopped) return; const run = () => { if (!urgent) return; urgent = 0; queued = false; lastTag = Date.now(); tag(); }; urgent = requestAnimationFrame(run); setTimeout(run, 100); // לשונית ברקע: requestAnimationFrame לא רץ } const hasMessage = (n) => n.nodeType === 1 && (n.matches(SEL.message) || !!n.querySelector(SEL.message)); // גוגל מציירת מחדש בועות (למשל כשהשרת מאשר הודעה ששלחתי) ומוחקת את הסימון שלנו בלי להוסיף אלמנט - // לכן מאזינים גם להסרה של התכונות שלנו מהבועות (ורק להסרה, כדי שהשינויים שלנו עצמנו לא יפעילו סבב נוסף) const OUR_NODES = '.wa-meta, .wa-mmeta, .wa-ticks, .wa-darkbtn'; const observer = new MutationObserver((ms) => { for (const m of ms) { if (m.type === 'childList') { const t = m.target; if (t.nodeType === 1 && t.closest && t.closest(OUR_NODES)) continue; // השעה/הוי וי שלנו if ([...m.addedNodes].some(hasMessage)) { scheduleNow(); return; } schedule(); continue; } if (m.attributeName === 'data-user-id' || m.attributeName === 'data-theme') { schedule(); return; } if (m.attributeName === 'disabled') { if (m.target.closest && m.target.closest('.wa-sendgrp')) { schedule(); return; } continue; } // כפתור השליחה הופעל/הושבת if (m.attributeName === 'class') { // גוגל כתבה מחדש את המחלקות של בועה (למשל כשהשרת מאשר הודעה ששלחתי במרחב) ומחקה את wa-bubble - // בלי זה ההודעה נשארה לבנה עד השינוי הבא בדף. בודקים רק אם נמחקה מחלקה שלנו (בדיקת מחרוזת זולה) const old = m.oldValue; if (old && old.indexOf('wa-bubble') !== -1 && !m.target.classList.contains('wa-bubble')) { scheduleNow(); return; } continue; } if (m.oldValue !== null && !m.target.hasAttribute(m.attributeName)) { schedule(); return; } } }); observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true, attributeOldValue: true, attributeFilter: ['data-user-id', 'data-theme', 'data-wa-own', 'data-wa-tail', 'disabled', 'class'], }); // זיהוי שליחה: מריצים את העיצוב כמה פעמים מיד אחרי השליחה, כדי שההודעה החדשה תצבע בלי לחכות function onSend(e) { let sent = false; if (e.type === 'keydown') { sent = e.key === 'Enter' && !e.shiftKey && e.target && e.target.closest && !!e.target.closest(SEL.composer); } else { const b = e.target && e.target.closest && e.target.closest('[role="button"], button'); sent = !!b && /שלח|שליחה|send/i.test(b.getAttribute('aria-label') || ''); } if (!sent) return; lastSend = Date.now(); // הטקסט שנשלח: התיבה שבה הקישו Enter, או התיבה שבשורה של כפתור השליחה שנלחץ const row = e.target.closest('.wa-compose-panel, [jsname="SFaVIf"]') || e.target.parentElement; const box = e.target.closest(SEL.composer) || (row && row.querySelector(SEL.composer)); const txt = norm(box && box.innerText); if (txt) { sentTexts.push({ txt, at: lastSend }); if (sentTexts.length > 5) sentTexts.shift(); } sendTrace = { at: lastSend, len: txt.length, events: [] }; // לאבחון (waDebug): מה קרה להודעה ב-8 השניות אחרי השליחה [30, 150, 400, 900, 2000, 3500, 6000, 8000, 12000].forEach((ms) => setTimeout(tag, ms)); // (שרת שמאשר לאט) } document.addEventListener('keydown', onSend, true); // הקלדה משנה טקסט קיים (לא מוסיפה אלמנטים), אז ה-observer לא רואה אותה - צריך בשביל העיגול הירוק (מיקרופון/שליחה) const onInput = (e) => { if (e.target && e.target.closest && e.target.closest(SEL.composer)) schedule(); }; document.addEventListener('input', onInput, true); document.addEventListener('click', onSend, true); // תמונה שנטענה אחרי שההודעה כבר הוצגה (עד אז הגודל שלה 0 ואי אפשר למסגר אותה) const onMediaLoad = (e) => { if (e.target && (e.target.tagName === 'IMG' || e.target.tagName === 'VIDEO')) schedule(); }; document.addEventListener('load', onMediaLoad, true); document.addEventListener('loadedmetadata', onMediaLoad, true); window.addEventListener('resize', schedule); window.addEventListener('storage', onStorage); window.addEventListener('message', onBridge); window.postMessage({ chatsapp: 'hello', on: darkOn() }, '*'); // "נקרא": גוגל מחזיקה אווטאר מוסתר ליד כל הודעה ורק מחליפה איזה מהם גלוי (שינוי עיצוב, לא הוספת אלמנט), // כך שה-MutationObserver לא מתעורר כשהצד השני קורא בלי לענות. בדיקה קבועה כל 2 שניות (רק כשהלשונית גלויה). // רק כשיש הודעה שלי שעוד לא נקראה (VV אפורים) - אחרת אין מה לבדוק const readPoll = setInterval(() => { if (!document.hidden && document.querySelector('.wa-tk[data-state="sent"]:not([data-hidden])')) schedule(); }, 2500); applyDark(); // הרקע הכהה מוכן מראש (כשהדפדפן פנוי), כדי שהמעבר לכהה לא יחכה לחישוב התמונה (window.requestIdleCallback || ((f) => setTimeout(f, 1500)))(() => { if (!stopped) makeDarkBg(); }); pickBackground(); tag(); /* ================= כלי אבחון ================= */ W.waDebug = () => { const t = tag.last || { state: [] }; const out = { version: VERSION, metaOutsideBubble: [...document.querySelectorAll('.wa-bubble > .wa-meta')].filter((m) => { const a = m.getBoundingClientRect(), b = m.parentElement.getBoundingClientRect(); return a.bottom > b.bottom + 1 || a.top < b.top - 1; }).length, rtl: document.documentElement.getAttribute('data-wa-rtl'), lastError: tag.error || '-', lastPassMs: tag.ms, slowestPassMs: tag.maxMs, passSections: tag.sections, lastSendTrace: sendTrace ? sendTrace.events : '-', background: document.documentElement.getAttribute('data-wa-bg') || '-', metaRendered: document.querySelectorAll('.wa-meta').length, quotesFound: document.querySelectorAll('.wa-quote').length, myIdFound: !!MY_ID, messages: t.total, inSpace: !!document.querySelector('[role="main"] [data-group-id^="space/"]'), withSenderId: t.state.filter((s) => s.sender).length, mine: t.state.filter((s) => s.own === true).length, theirs: t.state.filter((s) => s.own === false).length, unknown: t.state.filter((s) => s.own === null).length, readAvatarsFound: t.seenCount, ticksInAvatarSlot: t.seenSlots, readUpToIndex: t.readUpTo, scrollerFound: !!document.querySelector('.wa-scroller'), theme: document.body && document.body.getAttribute('data-theme'), }; console.table(out); return out; }; // מבנה בלבד: בלי טקסט הודעות, שמות או מזהים // waSeen(): מציג כל אלמנט שנראה כמו אווטאר "נקרא" ומה הסקריפט החליט לגביו W.waSeen = () => { const sc = document.querySelector('.wa-scroller') || document; const rows = [...sc.querySelectorAll(SEL.receipts + ', [data-member-id]')].map((h) => ({ kind: h.matches(SEL.receipts) ? 'read-receipts-list' : 'member', width: h.offsetWidth, height: h.offsetHeight, hasText: !!h.textContent.trim(), ariaHidden: h.getAttribute('aria-hidden'), insideBubble: !!h.closest('.wa-bubble'), classes: [...h.classList].filter((c) => c.startsWith('wa-')).join(' ') || '-', state: h.getAttribute('data-wa-state') || '-', })); console.table(rows); return rows; }; // waGroups(): מציג את ההודעות האחרונות ומה יש בהן (טקסט / סרטון / תמונה) - לאבחון הודעות עם קבצים W.waGroups = () => { const rows = [...document.querySelectorAll(SEL.group)].slice(-8).map((g, i) => ({ i, mine: !!MY_ID && g.getAttribute('data-user-id') === MY_ID, textBubbles: g.querySelectorAll(SEL.message).length, video: g.querySelectorAll('video').length, images: g.querySelectorAll('img').length, clusters: g.querySelectorAll('[jsname="o7uNDd"]').length, width: g.offsetWidth, height: g.offsetHeight, })); console.table(rows); return rows; }; // waQuote(): מציג את מבנה הילדים של הבועות האחרונות - לאיתור הציטוט (שלח לי את הטבלה אם הציטוט לא מעוצב נכון) W.waQuote = () => { const d = (c) => c.tagName.toLowerCase() + '[' + (c.getAttribute('jsname') || '') + '|' + (c.getAttribute('role') || '') + '] len=' + c.textContent.trim().length + ' w=' + c.offsetWidth + ' bg=' + getComputedStyle(c).backgroundColor + (c.classList.contains('wa-quote') ? ' ' : ''); const rows = [...document.querySelectorAll('.wa-bubble')].slice(-12).map((b, i) => { const bg = [...b.children].find((c) => c.matches(SEL.message)); return { i, own: b.getAttribute('data-wa-own'), bubbleChildren: [...b.children].map(d).join(' || '), textChildren: bg ? [...bg.children].map(d).join(' || ') : '-' }; }); console.table(rows); return rows; }; // waTime(): האם גוגל חושפת חותמת זמן ב-DOM (לשעה ליד הוי וי) W.waTime = () => { const sc = document.querySelector('.wa-scroller') || document; const found = [...sc.querySelectorAll(TS_SEL)].slice(-8).map((t) => ({ tag: t.tagName.toLowerCase(), attrs: [...t.attributes].map((a) => a.name).filter((n) => /time|stamp/i.test(n)).join(','), parsed: msgTime(t) || '(לא ניתן לשייך להודעה אחת)', })); console.log('timestamp elements:', found.length); console.table(found); return found; }; W.waBg = (m) => { setAttr(document.documentElement, 'data-wa-bg', m === 'image' ? 'img' : m); return m; }; // waBg('flat') / waBg('svg') / waBg('image') // waBox(): בודק אם השעה בתוך המסגרת של הבועה ומה הגבלות הגובה (אם עדיין יוצאת מהמסגרת - שלח לי את הטבלה) W.waBox = () => { const rows = [...document.querySelectorAll('.wa-bubble')].slice(-8).map((b, i) => { const cs = getComputedStyle(b), m = b.querySelector(':scope > .wa-meta'); const br = b.getBoundingClientRect(), mr = m && m.getBoundingClientRect(); return { i, own: b.getAttribute('data-wa-own'), mode: b.getAttribute('data-wa-meta'), height: Math.round(br.height), clientH: b.clientHeight, scrollH: b.scrollHeight, boxSizing: cs.boxSizing, cssHeight: cs.height, inlineHeight: b.style.height || '-', parentDisplay: getComputedStyle(b.parentElement).display, parentH: Math.round(b.parentElement.getBoundingClientRect().height), metaOutside: mr ? (mr.bottom > br.bottom + 1 || mr.top < br.top - 1) : '-' }; }); console.table(rows); return rows; }; W.waExport = () => { const rows = [...document.querySelectorAll(SEL.message)].slice(-6).map((el, index) => { const chain = []; for (let n = el, k = 0; n && k < 16; n = n.parentElement, k++) { const c = getComputedStyle(n); chain.push({ tag: n.tagName, jsname: n.getAttribute('jsname'), role: n.getAttribute('role'), bg: c.backgroundColor, w: Math.round(n.getBoundingClientRect().width), dir: c.direction, display: c.display, overflow: c.overflow }); if (n.matches(SEL.scroller)) break; } return { index, chain }; }); const txt = JSON.stringify(rows, null, 1); console.log(txt); return txt; }; W.waStop = () => { stopped = true; observer.disconnect(); window.removeEventListener('resize', schedule); window.removeEventListener('storage', onStorage); window.removeEventListener('message', onBridge); clearInterval(readPoll); clearTimeout(dkTimer); clearTimeout(accTimer); iconsCleanup(); document.querySelectorAll('.wa-darkbtn').forEach((x) => x.remove()); document.documentElement.style.removeProperty('--wa-img-dark'); document.removeEventListener('keydown', onSend, true); document.removeEventListener('input', onInput, true); document.removeEventListener('click', onSend, true); document.removeEventListener('load', onMediaLoad, true); document.removeEventListener('loadedmetadata', onMediaLoad, true); document.querySelectorAll('.wa-ticks, .wa-meta, .wa-mmeta').forEach((x) => x.remove()); document.querySelectorAll('[class*="wa-"]').forEach((x) => { [...x.classList].filter((c) => c.startsWith('wa-')).forEach((c) => x.classList.remove(c)); ['data-wa-own', 'data-wa-tail', 'data-wa-state', 'data-wa-me', 'data-wa-meta'].forEach((a) => x.removeAttribute(a)); if (x.style && x.style.getPropertyValue('--wa-mw')) x.style.removeProperty('--wa-mw'); if (x.hasAttribute('data-wa-fit')) { x.removeAttribute('data-wa-fit'); ['margin-left', 'margin-right', 'margin-top'].forEach((p) => x.style.removeProperty(p)); } }); if (styleEl && styleEl.remove) styleEl.remove(); ['data-wa-rtl', 'data-wa-ticks', 'data-wa-bg', 'data-wa-dark', 'data-chatsapp-version'].forEach((a) => document.documentElement.removeAttribute(a)); delete W.waDebug; delete W.waExport; delete W.waSeen; delete W.waGroups; delete W.waQuote; delete W.waTime; delete W.waBg; delete W.waBox; delete W.waStop; }; } // chatsappMain })();