Skip to content

Commit 9401a9e

Browse files
committed
Fix beta TTS dock issues
1 parent f1cf350 commit 9401a9e

34 files changed

Lines changed: 3739 additions & 2594 deletions

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,15 @@ debug.log
3030
docs/md/
3131
.tmp_*
3232
.codex-tmp/
33+
34+
# Local TTS service repo clones
3335
.codex-tmp/local-tts-services/
36+
local-tts-services/
37+
chatterbox-tts-api/
38+
Chatterbox-TTS-Server/
39+
F5-TTS/
40+
F5-TTS_server/
41+
GPT-SoVITS/
42+
MisoTTS/
43+
openedai-speech/
44+
Qwen3-TTS/

ai.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ async function fetchOpenCodeZenModels(apiKey = "", force = false) {
495495

496496
async function getOpenCodeZenCandidateModels(llmSettings, refreshModelList) {
497497
const models = refreshModelList
498-
? await fetchOpenCodeZenModels(getOpenCodeZenApiKey(llmSettings), false)
498+
? await fetchOpenCodeZenModels(getOpenCodeZenApiKey(llmSettings), true)
499499
: getOpenCodeZenKnownModels();
500500
const chatModels = sortOpenCodeZenModels(models).filter(isOpenCodeZenChatCompletionsModel);
501501
const candidates = chatModels.length ? chatModels : OPENCODE_ZEN_FREE_MODEL_ORDER.slice();

api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@ The poll system can now be controlled through the API with the following actions
10311031
- **Load Poll Preset**: `{"action": "loadpoll", "value": {"pollId": "poll-123456"}}` - Loads a previously saved poll preset by its ID
10321032
- **Get Poll Presets**: `{"action": "getpollpresets"}` - Returns a list of all saved poll presets with their IDs and names
10331033
- **Set Poll Settings**: `{"action": "setpollsettings", "value": {...}}` - Updates the current poll settings
1034-
- Available settings: `pollType`, `pollQuestion`, `multipleChoiceOptions`, `pollStyle`, `pollTimer`, `pollTimerState`, `pollTally`, `pollSpam`
1034+
- Available settings: `pollType`, `pollQuestion`, `multipleChoiceOptions`, `pollMatchMode`, `pollStyle`, `pollTimer`, `pollTimerState`, `pollTally`, `pollSpam`
10351035
- **Create New Poll**: `{"action": "createpoll", "value": {"settings": {...}}}` - Creates a new poll with specified settings
10361036

10371037
### Example Usage

background.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,18 @@ function commandAliasMatches(commandString, messageText, mode) {
5858
if (!aliases.length) {
5959
return false;
6060
}
61-
const text = String(messageText);
62-
const lowerText = text.toLowerCase();
61+
const normalizedText = String(messageText).trim().toLowerCase();
6362

6463
if (mode === "exact") {
65-
return aliases.some(alias => lowerText === alias.lower);
64+
return aliases.some(alias => normalizedText === alias.lower);
6665
}
6766
if (mode === "startsWith") {
68-
return aliases.some(alias => lowerText.startsWith(alias.lower));
67+
return aliases.some(alias => normalizedText.startsWith(alias.lower));
6968
}
7069
if (mode === "word") {
71-
return aliases.some(alias => alias.wordRegex.test(text));
70+
return aliases.some(alias => alias.wordRegex.test(normalizedText));
7271
}
73-
return aliases.some(alias => lowerText.includes(alias.lower));
72+
return aliases.some(alias => normalizedText.includes(alias.lower));
7473
}
7574

7675
function getCustomGifCommandEntryId(command, url, id) {
@@ -83,16 +82,26 @@ function getCustomGifCommandEntryId(command, url, id) {
8382
return "gif_" + Math.abs(hash);
8483
}
8584

86-
function getMatchedCommandAlias(commandString, messageText) {
87-
const text = String(messageText || "");
88-
const lowerText = text.toLowerCase();
85+
function getMatchedCommandAlias(commandString, messageText, mode) {
86+
const normalizedText = String(messageText || "").trim().toLowerCase();
8987
const aliases = getCommandAliases(commandString);
88+
const matchMode = mode || "contains";
89+
9090
for (let i = 0; i < aliases.length; i++) {
91-
if (lowerText === aliases[i].lower) {
91+
if (matchMode === "exact" && normalizedText === aliases[i].lower) {
92+
return aliases[i].command;
93+
}
94+
if (matchMode === "startsWith" && normalizedText.startsWith(aliases[i].lower)) {
95+
return aliases[i].command;
96+
}
97+
if (matchMode === "word" && aliases[i].wordRegex.test(normalizedText)) {
98+
return aliases[i].command;
99+
}
100+
if (matchMode === "contains" && normalizedText.includes(aliases[i].lower)) {
92101
return aliases[i].command;
93102
}
94103
}
95-
return text;
104+
return normalizedText;
96105
}
97106

98107
// Spotify integration
@@ -6642,14 +6651,15 @@ async function sendToDestinations(message) {
66426651
try {
66436652
if (settings.enableCustomGifCommands && settings["customGifCommands"]) {
66446653
// settings.enableCustomGifCommands.object = JSON.stringify([{command,url},{command,url},{command,url})
6645-
const firstWord = message && message.chatmessage ? message.chatmessage.split(" ")[0] : "";
6654+
const messageText = typeof message.chatmessage === "string" ? message.chatmessage : "";
6655+
const cleanMessageText = messageText.trim().replace(/^[^a-zA-Z0-9#]+/g, "");
66466656
settings["customGifCommands"]["object"].forEach(values => {
6647-
if (firstWord && values.url && values.command && commandAliasMatches(values.command, firstWord, "exact")) {
6657+
if (cleanMessageText && values.url && values.command && commandAliasMatches(values.command, cleanMessageText, "startsWith")) {
66486658
// || "https://picsum.photos/1280/720?random="+values.command
66496659
const aliases = getCommandAliases(values.command).map(alias => alias.command);
66506660
const gifMeta = Object.assign({}, message.meta || {}, {
66516661
customGifCommandId: getCustomGifCommandEntryId(values.command, values.url, values.id),
6652-
customGifCommand: getMatchedCommandAlias(values.command, firstWord),
6662+
customGifCommand: getMatchedCommandAlias(values.command, cleanMessageText, "startsWith"),
66536663
customGifCommands: aliases
66546664
});
66556665
const gifPayload = { ...message, ...{ contentimg: values.url, meta: gifMeta } }; // overwrite any existing contentimg. leave the rest of the meta data tho
@@ -11407,7 +11417,7 @@ function loadPollPreset(pollId) {
1140711417
function updatePollSettings(newSettings) {
1140811418
try {
1140911419
// Update poll-related settings
11410-
const pollKeys = ["pollType", "pollQuestion", "multipleChoiceOptions", "pollStyle", "pollTimer", "pollTimerState", "pollTally", "pollSpam", "pollDonationWeighted"];
11420+
const pollKeys = ["pollType", "pollQuestion", "multipleChoiceOptions", "pollMatchMode", "pollStyle", "pollTimer", "pollTimerState", "pollTally", "pollSpam", "pollDonationWeighted"];
1141111421

1141211422
pollKeys.forEach(key => {
1141311423
if (newSettings.hasOwnProperty(key)) {
@@ -11452,6 +11462,7 @@ function createNewPoll(pollSettings) {
1145211462
pollType: "freeform",
1145311463
pollQuestion: "",
1145411464
multipleChoiceOptions: "",
11465+
pollMatchMode: "exact",
1145511466
pollStyle: "default",
1145611467
pollTimer: "60",
1145711468
pollTimerState: false,

dock.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7372,10 +7372,10 @@ <h2 id="chatDestinationsFor">Checking what chat destinations are available...</h
73727372
var normalizedMsg = (msg && msg.mid && msg.mid !== msg.id) ? Object.assign({}, msg, { id: msg.mid, dbid: msg.id }) : msg;
73737373
if (prependRecentHistory) {
73747374
prependMode = true;
7375-
processData({contents: normalizedMsg}, { skipHistoryDeferral: true });
7375+
processData({contents: normalizedMsg}, { reloaded: true, suppressLiveSideEffects: true, skipHistoryDeferral: true });
73767376
prependMode = false;
73777377
} else {
7378-
processData({contents: normalizedMsg}, { skipHistoryDeferral: true });
7378+
processData({contents: normalizedMsg}, { reloaded: true, suppressLiveSideEffects: true, skipHistoryDeferral: true });
73797379
}
73807380
}
73817381
});

docs/event-reference.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ <h2>Quick Feature Availability</h2>
139139
<td>&mdash;</td>
140140
<td>&mdash;</td>
141141
<td>&mdash;</td>
142-
<td>Viewer count, live event card snapshots, auction footer metadata (when exposed), and upcoming event metadata</td>
142+
<td>Viewer count, follower count, live event card snapshots, auction footer metadata (when exposed), reaction hearts, and upcoming event metadata</td>
143143
</tr>
144144
<tr>
145145
<td><strong>Streamlabs Alert Box</strong></td>
@@ -901,6 +901,11 @@ <h3>eBay Live</h3>
901901
<td>When the active event viewer count changes (header count or live event pill fallback).</td>
902902
<td><code>meta</code> is an integer viewer count.</td>
903903
</tr>
904+
<tr>
905+
<td class="status-event"><code>follower_update</code></td>
906+
<td>When the seller follower count changes from the seller stats endpoint.</td>
907+
<td><code>meta</code> is an integer follower count. The source polls the seller endpoint every 5 minutes because the endpoint cache only refreshes on that cadence.</td>
908+
</tr>
904909
<tr>
905910
<td class="status-event"><code>auction_update</code></td>
906911
<td>When active auction metadata changes.</td>
@@ -911,6 +916,11 @@ <h3>eBay Live</h3>
911916
<td>When catalog/live-event snapshot sections change.</td>
912917
<td>Metadata-only snapshot under <code>meta</code>, including available sections such as <code>meta.navigation</code>, <code>meta.playerCards</code>, <code>meta.liveEvents</code>, <code>meta.livePreview</code>, <code>meta.currentEvent</code>, and <code>meta.upcomingEvents</code>.</td>
913918
</tr>
919+
<tr>
920+
<td class="status-event"><code>reaction</code></td>
921+
<td>When eBay Live renders a heart/reaction animation.</td>
922+
<td>Sent directly to the dedicated reactions target. <code>meta.reactionType</code> is <code>heart</code>; eBay does not expose a per-user name for these DOM animations.</td>
923+
</tr>
914924
</tbody>
915925
</table>
916926
<p class="field-highlights">eBay metadata events intentionally omit <code>chatname</code>/<code>chatmessage</code>; downstream overlays should render from <code>data.event</code> + <code>data.meta</code> only.</p>

gif.html

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
const urlParams = new URLSearchParams(window.location.search);
6464
const mediaContainer = document.getElementById('mediaContainer');
6565
const mediaContent = document.getElementById('mediaContent');
66+
const bridgeSession = (urlParams.get('session') || '').split(',')[0].trim();
6667
const customGifFilterId = (urlParams.get('gifid') || '').trim();
6768
const customGifFilterCommand = (urlParams.get('gifcommand') || '').trim().toLowerCase();
6869
const customGifFilterCommands = customGifFilterCommand.split(',').map(function(command) {
@@ -548,25 +549,27 @@
548549
const bridgeLabel = ((customGifFilterId || customGifFilterCommand) ? filename : (urlParams.get('label') || filename)).trim() || filename;
549550
const password = urlParams.get('password') || 'false';
550551
const lanonly = urlParams.has('lanonly') ? '&lanonly' : '';
551-
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&notmobile&notmobile&password=" + encodeURIComponent(password) + lanonly + "&solo&view=" + urlParams.get('session') + "&novideo&noaudio&label=" + encodeURIComponent(bridgeLabel) + "&cleanoutput&room=" + urlParams.get('session');
552-
iframe.style.width = "0px";
553-
iframe.style.height = "0px";
554-
iframe.style.position = "fixed";
555-
iframe.style.left = "-100px";
556-
iframe.style.top = "-100px";
557-
iframe.id = "frame1";
558-
document.body.appendChild(iframe);
559-
const eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
560-
const eventer = window[eventMethod];
561-
const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
562-
eventer(messageEvent, function (e) {
563-
if (e.source != iframe.contentWindow) return;
564-
if ("dataReceived" in e.data) {
565-
if ("overlayNinja" in e.data.dataReceived) {
566-
processData(e.data.dataReceived.overlayNinja);
567-
}
568-
}
569-
});
552+
if (bridgeSession) {
553+
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&notmobile&notmobile&password=" + encodeURIComponent(password) + lanonly + "&solo&view=" + encodeURIComponent(bridgeSession) + "&novideo&noaudio&label=" + encodeURIComponent(bridgeLabel) + "&cleanoutput&room=" + encodeURIComponent(bridgeSession);
554+
iframe.style.width = "0px";
555+
iframe.style.height = "0px";
556+
iframe.style.position = "fixed";
557+
iframe.style.left = "-100px";
558+
iframe.style.top = "-100px";
559+
iframe.id = "frame1";
560+
document.body.appendChild(iframe);
561+
const eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
562+
const eventer = window[eventMethod];
563+
const messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
564+
eventer(messageEvent, function (e) {
565+
if (e.source != iframe.contentWindow) return;
566+
if ("dataReceived" in e.data) {
567+
if ("overlayNinja" in e.data.dataReceived) {
568+
processData(e.data.dataReceived.overlayNinja);
569+
}
570+
}
571+
});
572+
}
570573

571574
// Handle audio context for browsers that require user interaction
572575
attachAudioUnlockListeners();

issues.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Issues Found
2+
3+
## High
4+
5+
- `poll.html:739-741`: `compactChoiceText()` removes all non-alphanumeric characters, so option text like `goof!` and `goof` normalize to the same value and can both count the same vote path.
6+
7+
- `poll.html:845-846`: `parseInt(vote, 10)` accepts values with trailing characters (for example `1abc`), so non-numeric tokens can be interpreted as valid numeric votes in multiple/yes-no polls.
8+
9+
- `baretempate.html:76-77`, `bot.html:1853-1857`, `battle.html:444-445`, `confetti.html:230-231`, `content.html:443-444`, `credits.html:993-994`, `emotes.html:503-504`, `games.html:563-564`, `featured.html:2355-2357`, `gif.html:566-568`, `games/wordstorm.html:667-668`, `themes/featured-styles/featured-gaming.html:723-727`, `themes/featured-styles/featured-particles.html:669-670`, `themes/featured-styles/featured-neon.html:711-712`, `themes/overlay-bubbles.html:768-769`, `themes/overlay-cards.html:790-791`, `themes/compact-glass.html:626-627`, `dock.html:8215-8218`: multiple overlay/game/theme message handlers use `"overlayNinja" in e.data.dataReceived` or equivalent checks without null/shape guards, so unexpected `message` events can throw and stop processing overlay payloads.
10+
11+
- `content.html:519`, `emotes.html:574`: both use `parseInt(Math.random*100000000)` instead of invoking `Math.random()`, producing `NaN` IDs that can break dedupe/linking flows that expect integer identifiers.
12+
13+
## Medium
14+
15+
- `poll.html:720-727`: hashtag matching in `getVoteCandidates()` only accepts ASCII token characters (`[A-Za-z0-9_][A-Za-z0-9_-]*`), so valid unicode/extended hashtags are ignored in hashtag-anywhere mode.
16+
17+
- `aiprompt.html:1029-1030`, `aiprompt.html:1159`, `aiprompt.html:1321-1322`, `aiprompt.html:1395`: URL query params (`limit`, `showtime`, `duration`, `target`, `start`, `minutes`) are parsed with `parseInt(...)`/`Math.max(...)` and no `NaN` fallback, so malformed values (for example `?limit=bad`) become `NaN` and disable the feed cap (`while (feed.children.length > limit)` never truncates).
18+
19+
- `background.js:484-487` and `background.js:15757`, `background.js:15834`: `String.prototype.replaceAll()` is used for GIF parsing and stream ID generation; this is not available in Chrome-80-era environments despite this project's compatibility note.
20+
21+
- `gif.html:327-329`, `gif.html:339`: `detectMediaType()` does an unbounded `HEAD` request before any timeout and proceeds even when `fetch` stalls or returns unexpected content, so media queues can wait unnecessarily and degrade overlay responsiveness.
22+
23+
- `tts.js:617-632`, `tts.js:764-766`, `tts.js:798`, `tts.js:814-815`: numeric URL params are parsed with `parseFloat/parseInt(... ) || default`, which makes valid zero values impossible and can leave fields as `NaN` when malformed input is passed.
24+
25+
- `dock.html:3781-3804`, `dock.html:3789`, `dock.html:4858-4862`, `dock.html:4930`: room IDs are taken from URL params and only normalized in a narrow file:// branch (`prompt` path), so socket joins can use malformed/trimless values from normal URL-sourced sessions without the same validation.

0 commit comments

Comments
 (0)