Skip to content

Commit 8cbee0f

Browse files
committed
Make joined events opt-in globally
1 parent 3c926ba commit 8cbee0f

23 files changed

Lines changed: 168 additions & 108 deletions

docs/agents/13-reference/settings-key-index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ Category key: `custom_injection`. Settings: 25.
402402
| `allmemberchat` | boolean | Mark a message as a memberchat only when its a highlighted membership message |
403403
| `blockpremiumshorts` | boolean | Block donations/memberships from Youtube Shorts. Requires &shorts added to the Youtube chat pop out also. |
404404
| `bttv` | boolean | Enable BTTV emotes - YT/TW channels + globals |
405-
| `capturejoinedevent` | boolean | Don't block 'joined' stream events in TikTok |
405+
| `capturejoinedevent` | boolean | Capture 'joined' stream events from supported sources |
406406
| `capturelikeevent` | boolean | Allow 'liked' stream events in TikTok (high volume) |
407407
| `delaykick` | boolean | Delay capturing messages from Kick to give time for messages to be deleted if needed; 3 extra seconds of delay |
408408
| `delaytwitch` | boolean | Delay capturing messages from Twitch to give time for messages to be deleted if needed; 3 extra seconds of delay |

docs/event-reference.html

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -836,8 +836,8 @@ <h3>TikTok Live &ndash; DOM Capture and TikFinity Feed</h3>
836836
<p class="source-note">Implementation: <code>sources/tiktok.js</code> for native TikTok pages and <code>sources/tikfinity.js</code> for TikFinity's activity-feed widget/iframe. SSApp still has a native TikTok integration with the widest event coverage (see SSApp docs).</p>
837837
<ul class="requirements">
838838
<li>Works on the broadcaster's live page. Gift/like/follow banners only populate when the session is authenticated.</li>
839-
<li>TikTok provides many events via DOM detection <strong>without requiring WebSocket mode</strong> &ndash; gifts, follows, joins, and likes are captured automatically.</li>
840-
<li>TikFinity widget pages at <code>tikfinity.zerody.one/widget/activity-feed*</code> also work. The embedded activity-feed iframe emits the same canonical TikTok payload fields for chat, follows, shares, gifts, subscriptions, joins, and treasure chests.</li>
839+
<li>TikTok provides many events via DOM detection <strong>without requiring WebSocket mode</strong> &ndash; gifts, follows, likes, and opt-in joins are captured from rendered rows.</li>
840+
<li>TikFinity widget pages at <code>tikfinity.zerody.one/widget/activity-feed*</code> also work. The embedded activity-feed iframe emits the same canonical TikTok payload fields for chat, follows, shares, gifts, subscriptions, opt-in joins, and treasure chests.</li>
841841
<li>No additional API authentication required.</li>
842842
<li><strong>SSApp native mode</strong> still adds events beyond the page/widget capture paths: <code>question_new</code>, <code>emote</code>, and <code>viewer_update</code>.</li>
843843
</ul>
@@ -857,7 +857,7 @@ <h3>TikTok Live &ndash; DOM Capture and TikFinity Feed</h3>
857857
</tr>
858858
<tr>
859859
<td><code>joined</code></td>
860-
<td>Join notifications (requires <em>Capture Stream Events</em> and optional join toggle).</td>
860+
<td>Join notifications when the global <em>Capture "joined" stream events</em> setting is enabled.</td>
861861
<td>Skips share notifications; <code>chatname</code> might be empty for some system strings.</td>
862862
</tr>
863863
<tr>
@@ -900,7 +900,7 @@ <h3>Whatnot</h3>
900900
<p class="source-note">Implementation: <code>sources/whatnot.js</code></p>
901901
<ul class="requirements">
902902
<li>Open the live Whatnot show page with chat visible; websocket transport now assists chat, raids, donations, and fast viewer updates, while product/giveaway snapshots still rely on DOM sections rendered in the show view.</li>
903-
<li><em>Capture Stream Events</em> controls Whatnot system events plus auction/catalog metadata updates; viewer counts still follow the viewer/hype toggles.</li>
903+
<li><em>Capture Stream Events</em> controls Whatnot system events plus auction/catalog metadata updates; joined rows also require <em>Capture "joined" stream events</em>; viewer counts still follow the viewer/hype toggles.</li>
904904
</ul>
905905
<table class="event-table">
906906
<thead>
@@ -928,7 +928,7 @@ <h3>Whatnot</h3>
928928
</tr>
929929
<tr>
930930
<td><code>joined</code></td>
931-
<td>Chat rows whose normalized body starts with <code>joined</code>.</td>
931+
<td>Chat rows whose normalized body starts with <code>joined</code>, when <em>Capture "joined" stream events</em> is enabled.</td>
932932
<td>Uses string event labels for join notices (not boolean <code>true</code>).</td>
933933
</tr>
934934
<tr>
@@ -1178,6 +1178,11 @@ <h3>VPZone - WebSocket</h3>
11781178
<td>VPZone <code>follow</code> frame.</td>
11791179
<td>Mapped into the standard follower event shape.</td>
11801180
</tr>
1181+
<tr>
1182+
<td class="status-event"><code>joined</code></td>
1183+
<td>VPZone join/presence-style websocket events, when <em>Capture "joined" stream events</em> is enabled.</td>
1184+
<td>Mapped into a chat-style system event with VPZone actor metadata under <code>meta</code>.</td>
1185+
</tr>
11811186
</tbody>
11821187
</table>
11831188
</article>
@@ -1370,7 +1375,7 @@ <h3>MeetMe - DOM and WebSocket Capture</h3>
13701375
<ul class="requirements">
13711376
<li>Reads MeetMe's rendered live chat DOM on <code>app.meetme.com/live/view/...</code> pages and inside the <code>api.gateway.meetme-live.com/web-live/...</code> iframe.</li>
13721377
<li>When the iframe websocket is available, <code>wss://video-live.meetme.com/</code> frames are parsed before the DOM fallback to capture richer live events.</li>
1373-
<li><code>hideevents</code> suppresses non-donation events; MeetMe gifts and diamond donations still populate donation fields. <code>capturejoinedevent</code> disables join/rejoin notices, and <code>capturelikeevent</code> controls whether likes appear in the main feed or only the reactions target.</li>
1378+
<li><code>hideevents</code> suppresses non-donation events; MeetMe gifts and diamond donations still populate donation fields. <code>capturejoinedevent</code> enables join/rejoin notices, and <code>capturelikeevent</code> controls whether likes appear in the main feed or only the reactions target.</li>
13741379
<li>Viewer counts prefer the visible MeetMe header count, falling back to websocket totals only when the DOM count is unavailable. Counts emit on change and repeat the latest count about every 30 seconds while <code>showviewercount</code>/<code>hypemode</code> is enabled; follower totals are changed-only and rate-limited to roughly 60 seconds.</li>
13751380
</ul>
13761381
<table class="event-table">
@@ -1389,7 +1394,7 @@ <h3>MeetMe - DOM and WebSocket Capture</h3>
13891394
</tr>
13901395
<tr>
13911396
<td class="status-event"><code>joined</code> / <code>rejoined</code> / <code>left</code></td>
1392-
<td><code>SNSChatParticipant</code> websocket create, update, or delete frames arrive, or MeetMe renders a DOM <code>join-cell</code> row.</td>
1397+
<td><code>SNSChatParticipant</code> websocket create, update, or delete frames arrive, or MeetMe renders a DOM <code>join-cell</code> row. Joined/rejoined notices require <em>Capture "joined" stream events</em>.</td>
13931398
<td>Emits chat-style system notices with actor name/avatar when MeetMe exposes them. <code>meta.isNewViewer</code>, <code>meta.viewerLevelId</code>, <code>meta.isBouncer</code>, and <code>meta.isSubscriber</code> preserve participant state.</td>
13941399
</tr>
13951400
<tr>

popup.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6685,13 +6685,13 @@ <h3>Custom Injection</h3>
66856685
<img class="icon" src="./sources/images/tiktok.png" style="display: inline-block;" />🚫🔗
66866686
<span data-translate="no-tiktok-links"> Do not post links into TikTok's chat</span>
66876687
</div>
6688-
<div title="Don't block 'joined' stream events in TikTok">
6688+
<div title="Capture 'joined' stream events from supported sources">
66896689
<label class="switch">
66906690
<input type="checkbox" data-setting="capturejoinedevent" />
66916691
<span class="slider round"></span>
66926692
</label>
6693-
<img class="icon" src="./sources/images/tiktok.png" style="display: inline-block;" />🚪
6694-
<span data-translate="capture-joined-events"> Don't block "joined" stream events (TikTok) </span>
6693+
<span class="icon" style="display: inline-block;">🚪</span>
6694+
<span data-translate="capture-joined-events"> Capture "joined" stream events </span>
66956695
</div>
66966696
<div title="Show TikTok like events in the main chat/events feed. Reactions Overlay still receives them directly.">
66976697
<label class="switch">

sources/cherrytv.js

Lines changed: 101 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
11
(function () {
22
try {
3-
var settings = {};
3+
var isExtensionOn = true;
4+
var settings = {};
5+
6+
function sendData(data) {
7+
if (!isExtensionOn) {
8+
return;
9+
}
10+
try {
11+
chrome.runtime.sendMessage(chrome.runtime.id, { "message": data }, function (response) { });
12+
} catch (e) { }
13+
}
414

5-
function escapeHtml(unsafe) {
6-
return unsafe
7-
.replace(/&/g, "&amp;")
8-
.replace(/</g, "&lt;")
9-
.replace(/>/g, "&gt;")
10-
.replace(/"/g, "&quot;")
11-
.replace(/'/g, "&#039;");
12-
}
15+
function escapeHtml(unsafe) {
16+
try {
17+
unsafe = unsafe || "";
18+
if (settings.textonlymode) {
19+
return unsafe;
20+
}
21+
return unsafe
22+
.replace(/&/g, "&amp;")
23+
.replace(/</g, "&lt;")
24+
.replace(/>/g, "&gt;")
25+
.replace(/"/g, "&quot;")
26+
.replace(/'/g, "&#039;");
27+
} catch (e) {
28+
return "";
29+
}
30+
}
1331

1432
function getAllContentNodes(element) {
1533
var resp = "";
@@ -18,32 +36,43 @@
1836
resp += getAllContentNodes(node);
1937
} else if (node.nodeType === 3) {
2038
resp += escapeHtml(node.textContent);
21-
} else if (node.nodeType === 1) {
22-
if (node.nodeName === "IMG" && node.src) {
23-
resp += `<img src="${node.src}">`;
24-
} else {
25-
resp += node.outerHTML;
26-
}
27-
}
39+
} else if (node.nodeType === 1) {
40+
if (node.nodeName === "IMG" && node.src) {
41+
if (settings.textonlymode) {
42+
resp += escapeHtml(node.alt || "");
43+
} else {
44+
resp += `<img src="${node.src}">`;
45+
}
46+
} else if (!settings.textonlymode) {
47+
resp += node.outerHTML;
48+
}
49+
}
2850
});
2951
return resp;
3052
}
3153

3254
function processMessage(ele) {
33-
if (!ele) {
34-
console.error("Element not found");
35-
return;
36-
}
37-
try {
38-
if (ele.querySelector('img[alt*="User Joined"]')) {
39-
console.log("User joined:", ele.textContent.trim());
40-
let data = {};
41-
data.chatmessage = ele.textContent.trim();
42-
data.event = "joined";
43-
data.type = "cherry";
44-
chrome.runtime.sendMessage(chrome.runtime.id, { "message": data }, function (response) { });
45-
return;
46-
}
55+
if (!isExtensionOn) {
56+
return;
57+
}
58+
if (!ele || ele.nodeType !== 1) {
59+
console.error("Element not found");
60+
return;
61+
}
62+
try {
63+
if (ele.querySelector('img[alt*="User Joined"]')) {
64+
if (!settings.capturejoinedevent) {
65+
return;
66+
}
67+
console.log("User joined:", ele.textContent.trim());
68+
let data = {};
69+
data.chatmessage = ele.textContent.trim();
70+
data.event = "joined";
71+
data.type = "cherrytv";
72+
data.textonly = settings.textonlymode || false;
73+
sendData(data);
74+
return;
75+
}
4776

4877
if (ele.querySelector('img[alt*="Gift image"]')) {
4978
var giftUser = ele.querySelector('.text-white.font-medium').textContent.trim();
@@ -80,35 +109,53 @@
80109

81110
var data = {};
82111
data.chatname = chatName;
83-
data.chatmessage = chatMessage;
84-
data.type = "cherrytv";
85-
data.chatimg = chatImg;
86-
data.textonly = false;
87-
88-
chrome.runtime.sendMessage(chrome.runtime.id, { "message": data }, function (response) { });
89-
} catch (e) {
90-
console.error("Error processing message:", e);
91-
return;
92-
}
93-
}
94-
95-
chrome.runtime.onMessage.addListener(
96-
function (request, sender, sendResponse) {
97-
try {
112+
data.chatmessage = chatMessage;
113+
data.type = "cherrytv";
114+
data.chatimg = chatImg;
115+
data.textonly = settings.textonlymode || false;
116+
117+
sendData(data);
118+
} catch (e) {
119+
console.error("Error processing message:", e);
120+
return;
121+
}
122+
}
123+
124+
chrome.runtime.sendMessage(chrome.runtime.id, { "getSettings": true }, function(response) {
125+
if (typeof chrome !== "undefined" && chrome.runtime && chrome.runtime.lastError) { return; }
126+
response = response || {};
127+
if ("settings" in response) {
128+
settings = response.settings || {};
129+
}
130+
if ("state" in response) {
131+
isExtensionOn = response.state;
132+
}
133+
});
134+
135+
chrome.runtime.onMessage.addListener(
136+
function (request, sender, sendResponse) {
137+
try {
98138
if ("getSource" == request){sendResponse("cherrytv"); return; }
99139
if ("focusChat" == request) {
100140
document.querySelector('input[data-test="field-message-box-public"]').focus();
101141
sendResponse(true);
102142
return;
103-
}
104-
if (typeof request === "object") {
105-
if ("settings" in request) {
106-
settings = request.settings;
107-
sendResponse(true);
108-
return;
109-
}
110-
}
111-
} catch (e) { }
143+
}
144+
if (typeof request === "object") {
145+
if ("state" in request) {
146+
isExtensionOn = request.state;
147+
}
148+
if ("settings" in request) {
149+
settings = request.settings || {};
150+
sendResponse(true);
151+
return;
152+
}
153+
if ("state" in request) {
154+
sendResponse(true);
155+
return;
156+
}
157+
}
158+
} catch (e) { }
112159
sendResponse(false);
113160
}
114161
);

sources/kwai.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989

9090
if (event && msg){
9191
if (msg.includes("just joined")){
92+
if (!settings.capturejoinedevent){return;}
9293
eventType = "joined";
9394
} else if (ele.querySelector(".gift-img")){
9495
hasDonation = msg.split("x");

sources/meetme.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ function toDataURL(url, callback) {
565565
if (settings.hideevents && !isDonationEvent) {
566566
return false;
567567
}
568-
if ((eventName === "joined" || eventName === "rejoined") && settings.capturejoinedevent === false) {
568+
if ((eventName === "joined" || eventName === "rejoined") && !settings.capturejoinedevent) {
569569
return false;
570570
}
571571
return true;

sources/substack.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565

6666
let eventType = "";
6767
if (ele.querySelector("[class*='joinedText']")){
68+
if (!settings.capturejoinedevent){return;}
6869
eventType = "joined";
6970
}
7071

sources/vpzone.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@
597597
var simpleMatch = rowText.match(/^(.+?)\s+(?:just\s+)?(joined|left)$/i);
598598
if (simpleMatch && simpleMatch[1] && simpleMatch[2]) {
599599
var simpleType = simpleMatch[2].toLowerCase();
600-
if (simpleType === "joined" && settings.capturejoinedevent === false) {
600+
if (simpleType === "joined" && !settings.capturejoinedevent) {
601601
return null;
602602
}
603603
var simpleData = buildBaseData();
@@ -624,7 +624,7 @@
624624
}
625625

626626
var eventType = match[2].toLowerCase();
627-
if (eventType === "joined" && settings.capturejoinedevent === false) {
627+
if (eventType === "joined" && !settings.capturejoinedevent) {
628628
return null;
629629
}
630630

sources/websocket/vpzone.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@
10901090
var isOwner = eventFlag(ev, ["is_owner", "isOwner", "owner", "broadcaster"]);
10911091
if (!mapped || mapped === "presence" || mapped === "chat_message") return null;
10921092
if (state.settings.hideevents) return null;
1093-
if (mapped === "joined" && state.settings.capturejoinedevent === false) return null;
1093+
if (mapped === "joined" && !state.settings.capturejoinedevent) return null;
10941094
var rawName = ev.actorDisplayName || ev.displayName || ev.actorUsername || ev.username || "";
10951095
if (!rawName || String(rawName).toLowerCase() === "system") return null;
10961096
data = basePayload();

sources/whatnot.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,9 @@
10751075
var data = createWebSocketChatData(user, messageText);
10761076
var normalizedMessage = normalizeText(messageText);
10771077
data.event = /^joined\b/i.test(normalizedMessage) ? "joined" : false;
1078+
if (data.event === "joined" && !settings.capturejoinedevent) {
1079+
return;
1080+
}
10781081
if (!data.event && messageText.indexOf("?") > -1) {
10791082
data.question = true;
10801083
}
@@ -1314,6 +1317,9 @@
13141317

13151318
var normalizedMessage = (messageBody || "").replace(/\s+/g, " ").trim();
13161319
data.event = /^joined\b/i.test(normalizedMessage) ? "joined" : false;
1320+
if (data.event === "joined" && !settings.capturejoinedevent) {
1321+
return;
1322+
}
13171323

13181324
var meta = {};
13191325
if (messageIndex !== null) {

0 commit comments

Comments
 (0)