PageSourceSearch

https://worldswave.com/themes/wowonder/javascript/ww-chat-troll.js?v=ww5

js worldswave.com collected 2026-09-25 03:15:23 UTC 6,062 bytes, 151 lines download raw bytes

1/* ww-chat-troll.js - the "TROLL" label for live chat, every surface.
2 *
3 * A moderator marks a chatter as a troll (chat mod menu). The set is stored per
4 * stream in Redis by wavedeck_pin.php (24h TTL = this broadcast, never on the
5 * account), read here by polling wavedeck_pin.php on the popout's ~5s cadence, so
6 * it needs no socket and works on the watch page, the WaveDeck docks and the app.
7 *
8 * The chat is rendered with different markup depending on the surface, so the row
9 * and name selectors below mirror ww-chat-contract.js: the main watch page uses
10 * `vy_lv_comm_uid_<id>` / `.vy_lv_comment` with the name in `.vy_lv_comment3a`,
11 * the popout/hero uses `.live-now-hero-chat-msg` / `a.ww-chat-name-link > b`.
12 */
13(function () {
14  'use strict';
15
16  var ROW = '[class*="vy_lv_comm_uid_"], .vy_lv_comment, .live-now-hero-chat-msg';
17  var UID_ATTR = ['data-ww-user-id', 'data-uid'];
18  var POLL_MS = 5000;
19
20  var uids = Object.create(null);
21  var sid  = 0;
22
23  function getSID() {
24    if (sid) { return sid; }
25    try { sid = parseInt(window.WW_WC_SID || 0, 10) || 0; } catch (e) {}
26    if (!sid) { try { sid = parseInt(window.parent.WW_WC_SID || 0, 10) || 0; } catch (e) {} }
27    if (!sid) {
28      var h = document.querySelector('[data-ww-stream-id]');
29      if (h) { sid = parseInt(h.getAttribute('data-ww-stream-id'), 10) || 0; }
30    }
31    /* main watch page: vy_lvst holds the stream id */
32    if (!sid) { try { sid = parseInt((window.vy_lvst && (window.vy_lvst.streamId || window.vy_lvst.broadcast_id)) || 0, 10) || 0; } catch (e) {} }
33    if (!sid) { try { sid = parseInt((window.parent.vy_lvst && (window.parent.vy_lvst.streamId || window.parent.vy_lvst.broadcast_id)) || 0, 10) || 0; } catch (e) {} }
34    return sid;
35  }
36
37  function norm(uid) { return String(parseInt(uid, 10) || uid); }
38
39  function ensureCss() {
40    if (document.getElementById('wwtroll-css')) { return; }
41    var s = document.createElement('style');
42    s.id = 'wwtroll-css';
43    s.textContent =
44        '@keyframes wwtroll-pulse{0%,100%{filter:brightness(.82)}50%{filter:brightness(1.5)}}'
45      + '.wwtroll-name{text-transform:uppercase !important;font-weight:800 !important;'
46      + 'letter-spacing:.6px !important;animation:wwtroll-pulse 1.05s ease-in-out infinite !important}';
47    (document.head || document.documentElement).appendChild(s);
48  }
49
50  /* uid from a row: data attr first, else the vy_lv_comm_uid_<id> class. */
51  function rowUid(row) {
52    for (var i = 0; i < UID_ATTR.length; i++) {
53      var v = row.getAttribute(UID_ATTR[i]);
54      if (v) { return norm(v); }
55    }
56    var m = String(row.className || '').match(/vy_lv_comm_uid_(\d+)/);
57    if (m) { return m[1]; }
58    return '';
59  }
60
61  /* The element holding the visible name TEXT, across every chat markup. */
62  function nameEl(row) {
63    return row.querySelector('a.ww-chat-name-link > b')
64        || row.querySelector('a.ww-chat-name-link b')
65        || row.querySelector('[class*="-SpanNickName"]')
66        || row.querySelector('.vy_lv_comment3a.js__comment_author')
67        || row.querySelector('.js__comment_author')
68        || row.querySelector('.vy_lv_comment3a')
69        || row.querySelector('a.ww-chat-name-link')
70        || null;
71  }
72
73  function label(el) {
74    if (!el || el.classList.contains('wwtroll-name')) { return; }
75    if (!el.hasAttribute('data-wwtroll-orig')) { el.setAttribute('data-wwtroll-orig', el.textContent); }
76    if (!el.children.length) { el.textContent = 'TROLL'; }
77    el.classList.add('wwtroll-name');
78    /* inline !important beats any role/name color the site sets with !important */
79    try { el.style.setProperty('color', '#33c46b', 'important');
80          el.style.setProperty('-webkit-text-fill-color', '#33c46b', 'important'); } catch (e) {}
81  }
82  function unlabel(el) {
83    if (!el || !el.classList.contains('wwtroll-name')) { return; }
84    if (el.hasAttribute('data-wwtroll-orig') && !el.children.length) {
85      el.textContent = el.getAttribute('data-wwtroll-orig');
86    }
87    el.removeAttribute('data-wwtroll-orig');
88    el.classList.remove('wwtroll-name');
89    try { el.style.removeProperty('color'); el.style.removeProperty('-webkit-text-fill-color'); } catch (e) {}
90  }
91
92  function applyOne(row) {
93    var el = nameEl(row);
94    if (!el) { return; }
95    var u = rowUid(row);
96    if (u && uids[u]) { label(el); } else { unlabel(el); }
97  }
98
99  function applyAll(root) {
100    var scope = (root && root.querySelectorAll) ? root : document;
101    var rows = scope.querySelectorAll(ROW);
102    for (var i = 0; i < rows.length; i++) { applyOne(rows[i]); }
103    if (root && root.matches && root.matches(ROW)) { applyOne(root); }
104  }
105
106  window.wwTroll = {
107    has: function (uid) { return !!uids[norm(uid)]; },
108    add: function (uid) { ensureCss(); uids[norm(uid)] = true; applyAll(); },
109    remove: function (uid) { delete uids[norm(uid)]; applyAll(); },
110    set: function (list) {
111      ensureCss();
112      var next = Object.create(null), i;
113      if (list && list.length) { for (i = 0; i < list.length; i++) { next[norm(list[i])] = true; } }
114      uids = next;
115      applyAll();
116    }
117  };
118
119  function poll() {
120    var s = getSID();
121    if (!s) { return; }
122    fetch('/wavedeck_pin.php?stream_id=' + s, { credentials: 'same-origin' })
123      .then(function (r) { return r.json(); })
124      .then(function (j) { if (j && j.ok && Array.isArray(j.trolled)) { window.wwTroll.set(j.trolled); } })
125      .catch(function () {});
126  }
127
128  function start() {
129    if (!getSID()) { return; }
130    ensureCss();
131    poll();
132    setInterval(poll, POLL_MS);
133    try {
134      var mo = new MutationObserver(function (muts) {
135        for (var i = 0; i < muts.length; i++) {
136          var added = muts[i].addedNodes;
137          for (var k = 0; k < added.length; k++) {
138            if (added[k].nodeType === 1) { applyAll(added[k]); }
139          }
140        }
141      });
142      mo.observe(document.body, { childList: true, subtree: true });
143    } catch (e) {}
144  }
145
146  if (document.readyState === 'loading') {
147    document.addEventListener('DOMContentLoaded', start);
148  } else {
149    start();
150  }
151})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.