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.