Skip to content

Commit dc05b1c

Browse files
committed
docs: add transport migration roadmap
1 parent 27b28eb commit dc05b1c

12 files changed

Lines changed: 516 additions & 201 deletions

File tree

.github/workflows/firefox-sign.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ jobs:
6060
run: npm install --global "web-ext@${WEB_EXT_VERSION}"
6161

6262
- name: Validate Firefox build
63-
run: web-ext lint --source-dir "$WEB_EXT_SOURCE_DIR" --self-hosted
63+
run: |
64+
set -euo pipefail
65+
lint_args=()
66+
if [[ "$WEB_EXT_CHANNEL" == "unlisted" ]]; then
67+
lint_args+=(--self-hosted)
68+
fi
69+
web-ext lint --source-dir "$WEB_EXT_SOURCE_DIR" "${lint_args[@]}"
6470
6571
- name: Submit add-on to AMO
6672
id: submit

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Much more than just an overlay - Social Stream Ninja is a complete chat ecosyste
205205
- circle.so (and community domains like community.talkinghealthtech.com or members.firstinfam.com)
206206
- sooplive.com (pop out the chat to use; supports https://play.sooplive.com/*?vtype=chat)
207207
- flextv.co.kr (open the live channel page with chat visible)
208+
- sealteamsloth.com (open the pop-out chat: https://sealteamsloth.com/popout-chat/*)
208209
- on24.com ( Q&A - questions supported)
209210
- meetme
210211
- moonbeam.stream (no pop out)

TODO.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Server Transport Migration Roadmap
2+
3+
## Current state
4+
5+
- [x] Cross-transport dedupe is deployed on beta.
6+
- [x] The socket-only gate is present but requires links with `v=3.52.0` or newer.
7+
- [ ] The guarded pages are deployed to production.
8+
- [ ] Production go-live time is recorded below.
9+
- [ ] Version `3.52.0` is released.
10+
11+
Production go-live (Day 0): `NOT STARTED`
12+
13+
The 30-day countdown starts only when the guarded pages go live on production. Do not bump the extension to `3.52.0` before the Day 30 readiness review passes.
14+
15+
## Phase 1: Production rollout - Day 0
16+
17+
- [ ] Deploy the guarded page changes to production.
18+
- [ ] Record the exact production date and time above.
19+
- [ ] Confirm fresh production copies of representative pages contain the transport gate and dedupe guard.
20+
- [ ] Run `node scripts/transport-dedupe-regression.test.cjs` against the production commit.
21+
- [ ] Keep the extension version below `3.52.0`.
22+
- [ ] Keep additive delivery experimental; do not enable it by default.
23+
24+
## Phase 2: Cache and production soak - Days 1-30
25+
26+
- [ ] Leave the `3.52.0` gate inactive for the full 30 days.
27+
- [ ] Monitor reports of duplicate or missing messages.
28+
- [ ] Test additive delivery with existing `v=3.50.5` links; they should remain dual-listen and render once.
29+
- [ ] Test links without `server2`; they should remain bridge-only.
30+
- [ ] Test representative chat overlays, emotes, events, hype, games, and the dock.
31+
- [ ] Do not treat an old saved OBS URL as updated merely because its cached page has refreshed.
32+
33+
## Phase 3: Readiness review - Day 30
34+
35+
All items must pass before releasing `3.52.0`:
36+
37+
- [ ] Thirty full days have passed since the recorded production go-live time.
38+
- [ ] Production is serving the guarded page code.
39+
- [ ] The dedupe regression test passes.
40+
- [ ] Fresh and old saved links both render messages once during additive delivery.
41+
- [ ] No unresolved duplicate-message or dropped-message regression remains.
42+
- [ ] The socket-outage tradeoff is accepted: a socket-only overlay cannot receive the P2P fallback while its bridge is disconnected.
43+
44+
## Phase 4: Release `3.52.0`
45+
46+
- [ ] Bump the extension manifest to `3.52.0`.
47+
- [ ] Release the updated extension.
48+
- [ ] Confirm newly generated links contain `v=3.52.0` or newer.
49+
- [ ] Confirm eligible read-only overlays using `server2` stop opening the legacy bridge.
50+
- [ ] Confirm old saved links remain dual-listen and are protected by dedupe.
51+
- [ ] Monitor the release before changing the additive-delivery default.
52+
53+
## Phase 5: Make additive delivery the default
54+
55+
Do this no earlier than September 1, 2026, and only after the `3.52.0` release has completed its monitoring period.
56+
57+
- [ ] Change additive delivery from opt-in to the default; the existing date comment does not perform this change automatically.
58+
- [ ] Update or remove the experimental setting text in the popup.
59+
- [ ] Retest fresh `3.52.0+` links, old saved links, bridge-only links, and socket outages.
60+
- [ ] Keep the dedupe guards in place.
61+
62+
## Separate later project: retire the legacy bridge
63+
64+
This is not required for the `3.52.0` or additive-delivery rollout.
65+
66+
- [ ] Keep the dock dual-connected until its commands and private/targeted replies work fully through the server.
67+
- [ ] Audit every surface marked `legacyBridgeRequired: true`.
68+
- [ ] Migrate special tools such as hype, credits, events, poll, map, reactions, and tipjar individually.
69+
- [ ] Change transport capability flags one page at a time after focused testing.
70+
- [ ] Decide whether and how old saved URLs will ever be moved away from dual-listen mode.

actions.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1760,7 +1760,7 @@ <h2>Session ID Required</h2>
17601760
if (payload.allow === false || payload.allow === 'false' || payload.allow === 'off' || payload.allow === 0 || payload.allow === '0') {
17611761
allowForce = false;
17621762
}
1763-
TTS.speak(payload.text, allowForce);
1763+
TTS.speak(payload.text, allowForce, { voice: payload.voice });
17641764
console.log("[TTS] Speaking:", payload.text, "(force:", allowForce + ")");
17651765
} else if (!payload.text) {
17661766
console.warn("[TTS] No text provided for TTS action");

actions/EventFlowEditor.js

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,7 +1955,10 @@ class EventFlowEditor {
19551955
case 'fromSource': return `Source: ${node.config.source === '*' ? 'Any' : (node.config.source || 'Any')}`;
19561956
case 'fromChannelName': return `Channel: ${node.config.channelName || 'Any'}`;
19571957
case 'fromUser': return `User: ${node.config.username || 'Any'}`;
1958-
case 'userRole': return `Role: ${node.config.role || 'Any'}`;
1958+
case 'userRole': {
1959+
const roleLabels = { tiktokTeamMember: 'TikTok Team Member' };
1960+
return `Role: ${roleLabels[node.config.role] || node.config.role || 'Any'}`;
1961+
}
19591962
case 'hasDonation': return 'Has donation';
19601963
case 'channelPointRedemption': {
19611964
const rewardName = node.config.rewardName || '';
@@ -3176,11 +3179,21 @@ class EventFlowEditor {
31763179
case 'fromUser':
31773180
html += `<div class="property-group"><label class="property-label">Username</label><input type="text" class="property-input" id="prop-username" value="${node.config.username || ''}"></div>`;
31783181
break;
3179-
case 'userRole':
3180-
html += `<div class="property-group"><label class="property-label">User Role</label><select class="property-input" id="prop-role">
3181-
${['mod', 'vip', 'admin', 'subscriber', 'member', 'follower'].map(r => `<option value="${r}" ${node.config.role === r ? 'selected' : ''}>${r.charAt(0).toUpperCase() + r.slice(1)}</option>`).join('')}
3182-
</select></div>`;
3183-
break;
3182+
case 'userRole':
3183+
const userRoles = [
3184+
{ value: 'mod', label: 'Moderator' },
3185+
{ value: 'vip', label: 'VIP' },
3186+
{ value: 'admin', label: 'Admin' },
3187+
{ value: 'subscriber', label: 'Subscriber' },
3188+
{ value: 'member', label: 'Member' },
3189+
{ value: 'follower', label: 'Follower' },
3190+
{ value: 'tiktokTeamMember', label: 'TikTok Team Member' }
3191+
];
3192+
html += `<div class="property-group"><label class="property-label">User Role</label><select class="property-input" id="prop-role">
3193+
${userRoles.map(role => `<option value="${role.value}" ${node.config.role === role.value ? 'selected' : ''}>${role.label}</option>`).join('')}
3194+
</select></div>
3195+
<div class="property-help">TikTok Team Member matches Fan Club/team levels and badges supplied with a TikTok message.</div>`;
3196+
break;
31843197
case 'hasDonation': // Trigger type
31853198
html += `<p class="property-help">Fires if the message includes donation information.</p>`;
31863199
break;
@@ -5209,14 +5222,20 @@ class EventFlowEditor {
52095222
break;
52105223

52115224
// TTS Actions
5212-
case 'ttsSpeak':
5213-
html += `
5214-
<div class="property-group">
5215-
<label class="property-label">Text to Speak</label>
5216-
<textarea class="property-input" id="prop-text" rows="3" placeholder="Enter text to speak...">${node.config.text || ''}</textarea>
5217-
<div class="property-help">The text that will be spoken aloud</div>
5218-
</div>
5219-
<div class="property-group">
5225+
case 'ttsSpeak':
5226+
html += `
5227+
<div class="property-group">
5228+
<label class="property-label">Text to Speak</label>
5229+
<textarea class="property-input" id="prop-text" rows="3" placeholder="Enter text to speak...">${node.config.text || ''}</textarea>
5230+
<div class="property-help">The text that will be spoken aloud</div>
5231+
</div>
5232+
<div class="property-group">
5233+
<label class="property-label">Voice Override (optional)</label>
5234+
<input type="text" class="property-input" id="prop-voice"
5235+
value="${this.escapeHtml(node.config.voice || '')}" placeholder="Use Flow Actions default voice">
5236+
<div class="property-help">Enter a voice name or ID supported by the active Flow Actions TTS provider. Leave blank to use its configured default.</div>
5237+
</div>
5238+
<div class="property-group">
52205239
<label class="property-label">
52215240
<input type="checkbox" class="property-input" id="prop-useMessageText"
52225241
${node.config.useMessageText ? 'checked' : ''}>

actions/EventFlowSystem.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,37 @@ class EventFlowSystem {
10841084
hasChatLikeFields(message) {
10851085
return !!(message && (message.chatname || message.chatmessage || message.hasDonation || message.contentimg));
10861086
}
1087+
1088+
isTikTokTeamMember(message) {
1089+
if (!message || String(message.type || '').toLowerCase() !== 'tiktok') return false;
1090+
1091+
const meta = message.meta && typeof message.meta === 'object' && !Array.isArray(message.meta)
1092+
? message.meta
1093+
: {};
1094+
const levelCandidates = [
1095+
meta.memberLevel,
1096+
meta.teamMemberLevel,
1097+
meta.fanLevel,
1098+
message.memberLevel,
1099+
message.teamMemberLevel,
1100+
message.fanLevel
1101+
];
1102+
if (levelCandidates.some(level => Number(level) > 0)) return true;
1103+
1104+
const badges = Array.isArray(message.chatbadges)
1105+
? message.chatbadges
1106+
: (message.chatbadges ? [message.chatbadges] : []);
1107+
return badges.some(badge => {
1108+
if (typeof badge === 'string') {
1109+
return /(?:fans?_badge|grade_badge)/i.test(badge);
1110+
}
1111+
try {
1112+
return /(?:fans?_badge|grade_badge)/i.test(JSON.stringify(badge));
1113+
} catch (e) {
1114+
return false;
1115+
}
1116+
});
1117+
}
10871118

10881119
async processMessage(message) {
10891120

@@ -1579,6 +1610,9 @@ class EventFlowSystem {
15791610
return match;
15801611

15811612
case 'userRole':
1613+
if (config && config.role === 'tiktokTeamMember') {
1614+
return this.isTikTokTeamMember(message);
1615+
}
15821616
match = message && config && message[config.role] === true;
15831617
////console.log(`[EvaluateTrigger - userRole] Config Role: "${config.role}", Message Role Value: ${message[config.role]}, Match: ${match}`);
15841618
return match;
@@ -3538,6 +3572,7 @@ class EventFlowSystem {
35383572
overlayNinja: {
35393573
actionType: 'tts',
35403574
text: ttsText,
3575+
voice: typeof config.voice === 'string' ? config.voice.trim() : '',
35413576
force: config.force || false
35423577
}
35433578
}, 'actions');

actions/event-flow-guide.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,8 @@ <h3>Common Q &amp; A</h3>
490490
<li><strong>Do I have to use AND for every pair of filters?</strong> No. Many nodes include multiple checks (for example, the basic Message Filter supports keyword + role). Use AND only for advanced combinations or when merging signals from different nodes.</li>
491491
<li><strong>How do true/false values reach the NOT node?</strong> Any node with a green output emits <code>true</code> by default. When a condition fails, it emits <code>false</code>. Connect that wire into NOT to invert the result.</li>
492492
<li><strong>Can a node emit a payload even if it returns false?</strong> Yes. The payload still travels through the false output; it is up to you to decide where that branch should go.</li>
493+
<li><strong>How do I match TikTok Team members?</strong> Choose <em>TikTok Team Member</em> in the User Role node. It recognizes TikTok Fan Club/team levels and badges on the incoming message and does not depend on Main Chat Overlay settings.</li>
494+
<li><strong>Can each Speak Text node use a different voice?</strong> Yes. Enter a provider-supported voice name or ID in <em>Voice Override</em>, or leave it blank to use the Flow Actions TTS default.</li>
493495
</ul>
494496
</section>
495497

docs/agents/09-api-and-integrations/event-flow-editor.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ User and source triggers:
130130
- `userRole`
131131
- `channelPointRedemption`
132132

133+
`userRole` supports `tiktokTeamMember`, which matches positive TikTok team/fan levels or `fans_badge` / `grade_badge` data on an incoming TikTok message. This check is independent of dock-overlay URL settings.
134+
133135
Timing and random triggers:
134136

135137
- `randomChance`
@@ -214,6 +216,8 @@ TTS actions:
214216
- `ttsClear`
215217
- `ttsVolume`
216218

219+
`ttsSpeak.config.voice` is an optional per-utterance voice name or provider voice ID. Empty or missing values use the configured Flow Actions TTS voice. The override is preserved while premium-provider speech waits in the queue and does not mutate the configured default.
220+
217221
MIDI actions:
218222

219223
- `midiSendNote`

docs/event-reference.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ <h3>Platforms and Sources</h3>
112112
<li><a href="#streamplace">Streamplace</a></li>
113113
<li><a href="#worldswave">WorldsWave</a></li>
114114
<li><a href="#flextv">FLEX TV</a></li>
115+
<li><a href="#sealteamsloth">Seal Team Sloth</a></li>
115116
<li><a href="#meetme-dom">MeetMe</a></li>
116117
<li><a href="#velora">Velora</a></li>
117118
<li><a href="#parti">Parti</a></li>
@@ -1540,6 +1541,36 @@ <h3>FLEX TV</h3>
15401541
</table>
15411542
</article>
15421543

1544+
<article class="platform-card" id="sealteamsloth">
1545+
<h3>Seal Team Sloth</h3>
1546+
<p class="source-note">Implementation: <code>sources/sealteamsloth.js</code></p>
1547+
<ul class="requirements">
1548+
<li>Reads the rendered pop-out chat on <code>https://sealteamsloth.com/popout-chat/*</code> pages.</li>
1549+
<li>Viewer counts require <strong>Show viewer count</strong> or hype mode.</li>
1550+
</ul>
1551+
<table class="event-table">
1552+
<thead>
1553+
<tr>
1554+
<th>Event</th>
1555+
<th>When it Fires</th>
1556+
<th>Payload Notes</th>
1557+
</tr>
1558+
</thead>
1559+
<tbody>
1560+
<tr>
1561+
<td><code>message</code></td>
1562+
<td>A new rendered Seal Team Sloth chat row appears.</td>
1563+
<td>Standard chat payload with <code>type: "sealteamsloth"</code>, sender name, avatar, and message content.</td>
1564+
</tr>
1565+
<tr>
1566+
<td class="status-event"><code>viewer_update</code></td>
1567+
<td>The visible live viewer total changes while viewer-count capture or hype mode is enabled.</td>
1568+
<td><code>meta</code> is the integer viewer count; compact values such as <code>1.2K</code> are normalized.</td>
1569+
</tr>
1570+
</tbody>
1571+
</table>
1572+
</article>
1573+
15431574
<article class="platform-card" id="meetme-dom">
15441575
<h3>MeetMe - DOM and WebSocket Capture</h3>
15451576
<p class="source-note">Implementation: <code>sources/meetme.js</code></p>

tipjar.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,8 +2494,8 @@ <h3 style="color: white; margin-bottom: 15px; text-align: center;">🏆 Top Dono
24942494
if (!useServerOnlyTransport()) {
24952495
const iframe = document.createElement("iframe");
24962496
const filename = "tipjar";
2497-
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&notmobile&notmobile&password=" +
2498-
(password || "false") + "&solo&view=" + roomID + "&novideo&noaudio&label=" +
2497+
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&notmobile&notmobile&password=" +
2498+
encodeURIComponent(password || "false") + "&solo&view=" + roomID + "&novideo&noaudio&label=" +
24992499
filename + "&cleanoutput&room=" + roomID;
25002500
iframe.style.width = "0px";
25012501
iframe.style.height = "0px";

0 commit comments

Comments
 (0)