Skip to content

Commit ad50962

Browse files
authored
Merge pull request #830 from steveseguin/beta
Beta
2 parents d80d6e7 + bf6091b commit ad50962

73 files changed

Lines changed: 6583 additions & 1127 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ tests/cohost-customopenai*.playwright.js
2525
# Generated benchmark/stress outputs
2626
tests/artifacts/
2727
tmp/
28+
debug.log
29+
docs/md/
30+
.tmp_*

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ Much more than just an overlay - Social Stream Ninja is a complete chat ecosyste
115115
- mixcloud.com (pop out chat)
116116
- ms teams (teams.live.com and teams.microsoft.com)
117117
- vimeo.com (either the vimeo.com/events/xxx pages or https://vimeo.com/live-chat/xxxxxxxxx/interaction/)
118-
- instagram live (instagram.com/*/live/), css note: `[data.type = "instagramlive"]`
119-
- Instagram post non-live comments (REQUIRES the TOGGLE in menu to enable it), css note: `[data.type = "instagram"]`
118+
- instagram live (instagram.com/*/live/), payload type / css note: `[data.type = "instagramlive"]`
119+
- Instagram post non-live comments (REQUIRES the TOGGLE in menu to enable it), payload type / css note: `[data.type = "instagram"]`
120120
- instafeed.me (no pop out; alternative instagram live support)
121121
- tiktok live (tiktok.com/*/live -- the chat must be left open/visible if using the extension version)
122122
- webex live chat (not the pop out)
@@ -229,6 +229,7 @@ Much more than just an overlay - Social Stream Ninja is a complete chat ecosyste
229229
- streamelements (overlay page)
230230
- versus.cam (test chat page at https://versus.cam/?testchat)
231231
- patreon
232+
- arena.social (open https://arena.social/live/*)
232233
- velora.tv (no popup; just channel watch page)
233234
- blaze.stream (no popup; just channel watch page)
234235

@@ -255,7 +256,7 @@ Past supported sites that have ceased to exist or been retired.
255256
- 🪦 dlive.tv (RIP April 2026)
256257
- 🪦 Mobcrush (RIP August 2022)
257258
- 🪦 AfreecaTV / sooplive.co.kr (merged into sooplive.com)
258-
- 🪦 ArenaTV / Arena Social (livestreaming systems shut down)
259+
- 🪦 ArenaTV (livestreaming system shut down)
259260

260261
(it's the effort that counts, guys; may your code live on in our ai llm bots forever)
261262

actions/EventFlowEditor.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ class EventFlowEditor {
492492
{ value: 'facebook', label: 'Facebook' },
493493
{ value: 'tiktok', label: 'TikTok' },
494494
{ value: 'instagram', label: 'Instagram' },
495+
{ value: 'instagramlive', label: 'Instagram Live' },
495496
{ value: 'rumble', label: 'Rumble' },
496497
{ value: 'kofi', label: 'Ko-fi' },
497498
{ value: 'bmac', label: 'Buy Me a Coffee' },
@@ -2901,7 +2902,7 @@ class EventFlowEditor {
29012902
this.populateMIDIInputDevices('prop-deviceId', node.config.deviceId);
29022903
break;
29032904
case 'fromSource':
2904-
const isCustomSource = node.config.source !== undefined && node.config.source !== null && !['*', 'amazon', 'bandlab', 'beamstream', 'bigo', 'bilibili', 'bilibilicom',
2905+
const isCustomSource = node.config.source !== undefined && node.config.source !== null && !['*', 'amazon', 'arenasocial', 'bandlab', 'beamstream', 'bigo', 'bilibili', 'bilibilicom',
29052906
'bitchute', 'boltplus', 'buzzit', 'castr', 'cbox', 'chatroll', 'chaturbate', 'cherrytv', 'chime', 'chzzk', 'cime',
29062907
'circle', 'cloudhub', 'cozy', 'crowdcast', 'discord', 'dlive', 'estrim', 'facebook', 'fansly', 'favorited',
29072908
'fc2', 'floatplane', 'gala', 'generic', 'instafeed', 'instagram', 'instagramlive', 'jaco', 'joystick', 'kick',
@@ -2918,7 +2919,7 @@ class EventFlowEditor {
29182919

29192920
html += `<div class="property-group"><label class="property-label">Source Platform</label><select class="property-input" id="prop-source">
29202921
<option value="*" ${node.config.source === '*' ? 'selected' : ''}>Any Source</option>
2921-
${['amazon', 'bandlab', 'beamstream', 'bigo', 'bilibili', 'bilibilicom',
2922+
${['amazon', 'arenasocial', 'bandlab', 'beamstream', 'bigo', 'bilibili', 'bilibilicom',
29222923
'bitchute', 'boltplus', 'buzzit', 'castr', 'cbox', 'chatroll', 'chaturbate', 'cherrytv', 'chime', 'chzzk', 'cime',
29232924
'circle', 'cloudhub', 'cozy', 'crowdcast', 'discord', 'dlive', 'estrim', 'facebook', 'fansly', 'favorited',
29242925
'fc2', 'floatplane', 'gala', 'generic', 'instafeed', 'instagram', 'instagramlive', 'jaco', 'joystick', 'kick',

aioverlay.html

Lines changed: 155 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
var overlayRequestTimer = null;
4141
var overlayRequestStartedAt = 0;
4242
var overlayResponseReceived = false;
43+
var recentMetaPayloads = {};
44+
var sockets = [];
45+
var hasServerMode = params.has("server") || params.has("server2") || params.has("server3");
4346

4447
function showMissingOverlayError() {
4548
console.error("[AI Overlay] No saved AI overlay found for overlay=" + (requestedOverlay || "active") + ". Open aiprompt.html, save the overlay, then reload this page.");
@@ -54,6 +57,7 @@
5457

5558
function forwardToOverlay(payload) {
5659
if (!payload) return;
60+
if (!shouldForwardPayload(payload)) return;
5761
if (!overlayReady || !overlayFrame || !overlayFrame.contentWindow) {
5862
pendingPayloads.push(payload);
5963
if (pendingPayloads.length > 100) pendingPayloads.shift();
@@ -62,6 +66,23 @@
6266
overlayFrame.contentWindow.postMessage({ dataReceived: { overlayNinja: payload } }, "*");
6367
}
6468

69+
function shouldForwardPayload(payload) {
70+
if (!payload || typeof payload !== "object" || !Object.prototype.hasOwnProperty.call(payload, "meta")) return true;
71+
var key = "";
72+
try {
73+
key = JSON.stringify(payload);
74+
} catch (e) {
75+
return true;
76+
}
77+
var now = Date.now();
78+
if (recentMetaPayloads[key] && now - recentMetaPayloads[key] < 500) return false;
79+
recentMetaPayloads[key] = now;
80+
for (var existing in recentMetaPayloads) {
81+
if (now - recentMetaPayloads[existing] > 2000) delete recentMetaPayloads[existing];
82+
}
83+
return true;
84+
}
85+
6586
function loadHtml(html) {
6687
if (loaded || !html) return;
6788
loaded = true;
@@ -102,22 +123,125 @@
102123
}
103124
}
104125

105-
function requestExtensionOverlays() {
106-
if (!roomID) return false;
107-
bridgeFrame = document.createElement("iframe");
108-
bridgeFrame.id = "bridgeFrame";
109-
bridgeFrame.dataset.bridgeLabel = params.get("label") || "dock";
110-
bridgeFrame.onload = function () {
126+
function createBridgeFrame(label) {
127+
var frame = document.createElement("iframe");
128+
frame.id = "bridgeFrame";
129+
frame.dataset.bridgeLabel = label;
130+
frame.onload = function () {
111131
startOverlayRequestRetries();
112132
};
113-
bridgeFrame.src = "https://vdo.socialstream.ninja/?ln&password=" + encodeURIComponent(password) +
114-
"&salt=vdo.ninja&notmobile&solo&label=" + encodeURIComponent(bridgeFrame.dataset.bridgeLabel) + "&view=" + encodeURIComponent(roomID) +
133+
frame.src = "https://vdo.socialstream.ninja/?ln&password=" + encodeURIComponent(password) +
134+
"&salt=vdo.ninja&notmobile&solo&label=" + encodeURIComponent(label) + "&view=" + encodeURIComponent(roomID) +
115135
"&novideo&noaudio&cleanoutput&room=" + encodeURIComponent(roomID);
116-
bridgeFrame.style.cssText = "width:0;height:0;position:fixed;left:-100px;top:-100px;";
117-
document.body.appendChild(bridgeFrame);
136+
frame.style.cssText = "width:0;height:0;position:fixed;left:-100px;top:-100px;";
137+
document.body.appendChild(frame);
138+
return frame;
139+
}
140+
141+
function requestExtensionOverlays() {
142+
if (!roomID) return false;
143+
bridgeFrame = createBridgeFrame("aioverlay");
118144
return true;
119145
}
120146

147+
function processIncomingData(raw) {
148+
if (!raw || typeof raw !== "object") return;
149+
if (raw.dataReceived && raw.dataReceived.overlayNinja) {
150+
forwardToOverlay(raw.dataReceived.overlayNinja);
151+
return;
152+
}
153+
if (raw.overlayNinja) {
154+
forwardToOverlay(raw.overlayNinja);
155+
return;
156+
}
157+
forwardToOverlay(raw);
158+
}
159+
160+
function connectSocket(serverURL, socketRoomID, outChannel, inChannel) {
161+
if (!serverURL || !socketRoomID) return;
162+
var connection = {
163+
socket: null,
164+
attempts: 1,
165+
timer: null,
166+
closed: false
167+
};
168+
sockets.push(connection);
169+
170+
function scheduleReconnect() {
171+
if (connection.closed) return;
172+
var delay = Math.min(12000, 200 * connection.attempts);
173+
if (connection.timer) clearTimeout(connection.timer);
174+
connection.timer = setTimeout(function () {
175+
connection.attempts += 1;
176+
connectNow();
177+
}, delay);
178+
}
179+
180+
function connectNow() {
181+
if (connection.closed) return;
182+
try {
183+
connection.socket = new WebSocket(serverURL);
184+
} catch (e) {
185+
scheduleReconnect();
186+
return;
187+
}
188+
189+
connection.socket.onopen = function () {
190+
connection.attempts = 1;
191+
try {
192+
connection.socket.send(JSON.stringify({
193+
join: socketRoomID,
194+
out: outChannel,
195+
in: inChannel
196+
}));
197+
} catch (e) {}
198+
};
199+
200+
connection.socket.onmessage = function (event) {
201+
if (!event || !event.data) return;
202+
try {
203+
var parsed = JSON.parse(event.data);
204+
processIncomingData(parsed);
205+
if (parsed && parsed.get) {
206+
connection.socket.send(JSON.stringify({
207+
callback: {
208+
get: parsed.get,
209+
result: true
210+
}
211+
}));
212+
}
213+
} catch (e) {}
214+
};
215+
216+
connection.socket.onerror = function () {
217+
try {
218+
connection.socket.close();
219+
} catch (e) {}
220+
};
221+
222+
connection.socket.onclose = function () {
223+
scheduleReconnect();
224+
};
225+
}
226+
227+
connectNow();
228+
}
229+
230+
function connectServerModes() {
231+
var socketRoomID = (roomID || "").split(",")[0];
232+
if (!socketRoomID) return;
233+
if (params.has("server")) {
234+
var apiServerURL = params.get("server") || (params.has("localserver") ? "ws://127.0.0.1:3000" : "wss://io.socialstream.ninja/api");
235+
connectSocket(apiServerURL, socketRoomID, 2, 1);
236+
}
237+
if (params.has("server2") || params.has("server3")) {
238+
var extensionServerURL = params.has("server2")
239+
? (params.get("server2") || "wss://io.socialstream.ninja/extension")
240+
: (params.get("server3") || "wss://io.socialstream.ninja/extension");
241+
connectSocket(extensionServerURL, socketRoomID, 3, 4);
242+
}
243+
}
244+
121245
function postOverlayRequest() {
122246
if (!bridgeFrame || !bridgeFrame.contentWindow || loaded || overlayResponseReceived) return;
123247
bridgeFrame.contentWindow.postMessage({
@@ -168,8 +292,12 @@
168292
}
169293
}
170294

295+
function isBridgeSource(source) {
296+
return !!(bridgeFrame && source === bridgeFrame.contentWindow);
297+
}
298+
171299
window.addEventListener("message", function (event) {
172-
if (bridgeFrame && event.source !== bridgeFrame.contentWindow) return;
300+
if (bridgeFrame && !isBridgeSource(event.source)) return;
173301
var message = event.data || {};
174302
if (message.action && !loaded && !overlayResponseReceived) postOverlayRequest();
175303
var payload = resolveBridgeChunk(message.dataReceived && message.dataReceived.overlayNinja);
@@ -182,10 +310,26 @@
182310
}
183311
return;
184312
}
313+
if (hasServerMode && isBridgeSource(event.source)) return;
185314
forwardToOverlay(payload);
186315
});
187316

317+
window.addEventListener("beforeunload", function () {
318+
for (var i = 0; i < sockets.length; i++) {
319+
var connection = sockets[i];
320+
if (connection.timer) clearTimeout(connection.timer);
321+
connection.closed = true;
322+
if (connection.socket) {
323+
try {
324+
connection.socket.onclose = null;
325+
connection.socket.close();
326+
} catch (e) {}
327+
}
328+
}
329+
});
330+
188331
var loadedLocal = loadFromLocalState();
332+
connectServerModes();
189333
if (requestExtensionOverlays()) {
190334
setTimeout(function () {
191335
if (!loaded) console.warn("[AI Overlay] Waiting for saved AI overlays from the extension...");

0 commit comments

Comments
 (0)