mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 11:49:25 +00:00
Gate twitch integration
This commit is contained in:
@@ -7,6 +7,28 @@ const scriptLayer = document.getElementById("broadcast-script-layer");
|
||||
setUpElectronWindowFrame();
|
||||
|
||||
const renderer = new BroadcastRenderer({ canvas, scriptLayer, broadcaster, showToast });
|
||||
const defaultScriptSettings = {
|
||||
allowChannelEmotesForAssets: true,
|
||||
allowScriptChatAccess: true,
|
||||
};
|
||||
let currentScriptSettings = { ...defaultScriptSettings };
|
||||
|
||||
const settingsPromise = fetch(`/api/channels/${encodeURIComponent(broadcaster)}/settings`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to load channel settings");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((settings) => {
|
||||
currentScriptSettings = { ...defaultScriptSettings, ...settings };
|
||||
renderer.setScriptSettings(currentScriptSettings);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn("Unable to load channel settings", error);
|
||||
renderer.setScriptSettings(defaultScriptSettings);
|
||||
});
|
||||
|
||||
fetch(`/api/twitch/emotes?channel=${encodeURIComponent(broadcaster)}`)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
@@ -16,20 +38,26 @@ fetch(`/api/twitch/emotes?channel=${encodeURIComponent(broadcaster)}`)
|
||||
})
|
||||
.then((catalog) => renderer.setEmoteCatalog(catalog))
|
||||
.catch((error) => console.warn("Unable to load Twitch emotes", error));
|
||||
const disconnectChat = connectTwitchChat(
|
||||
broadcaster,
|
||||
({ channel, displayName, message, tags, prefix, raw }) => {
|
||||
console.log(`[twitch:${broadcaster}] ${displayName}: ${message}`);
|
||||
renderer.receiveChatMessage({
|
||||
channel,
|
||||
displayName,
|
||||
message,
|
||||
tags,
|
||||
prefix,
|
||||
raw,
|
||||
});
|
||||
},
|
||||
);
|
||||
let disconnectChat = () => {};
|
||||
settingsPromise.finally(() => {
|
||||
if (!currentScriptSettings.allowScriptChatAccess) {
|
||||
return;
|
||||
}
|
||||
disconnectChat = connectTwitchChat(
|
||||
broadcaster,
|
||||
({ channel, displayName, message, tags, prefix, raw }) => {
|
||||
console.log(`[twitch:${broadcaster}] ${displayName}: ${message}`);
|
||||
renderer.receiveChatMessage({
|
||||
channel,
|
||||
displayName,
|
||||
message,
|
||||
tags,
|
||||
prefix,
|
||||
raw,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
setUpElectronWindowResizeListener(canvas);
|
||||
renderer.start();
|
||||
|
||||
@@ -28,7 +28,11 @@ export class BroadcastRenderer {
|
||||
this.chatMessages = [];
|
||||
this.emoteCatalog = [];
|
||||
this.emoteCatalogById = new Map();
|
||||
this.globalEmotes = [];
|
||||
this.channelEmotes = [];
|
||||
this.lastChatPruneAt = 0;
|
||||
this.allowChannelEmotesForAssets = true;
|
||||
this.allowScriptChatAccess = true;
|
||||
|
||||
this.obsBrowser = !!globalThis.obsstudio;
|
||||
this.supportsAnimatedDecode =
|
||||
@@ -423,6 +427,17 @@ export class BroadcastRenderer {
|
||||
this.updateScriptWorkerEmoteCatalog();
|
||||
}
|
||||
|
||||
setScriptSettings(settings) {
|
||||
this.allowChannelEmotesForAssets = settings?.allowChannelEmotesForAssets !== false;
|
||||
this.allowScriptChatAccess = settings?.allowScriptChatAccess !== false;
|
||||
if (!this.allowScriptChatAccess) {
|
||||
this.chatMessages = [];
|
||||
}
|
||||
this.refreshEmoteCatalog();
|
||||
this.updateScriptWorkerChatMessages();
|
||||
this.updateScriptWorkerEmoteCatalog();
|
||||
}
|
||||
|
||||
updateScriptWorkerCanvas() {
|
||||
if (!this.scriptWorker || !this.scriptWorkerReady) {
|
||||
return;
|
||||
@@ -443,7 +458,7 @@ export class BroadcastRenderer {
|
||||
this.scriptWorker.postMessage({
|
||||
type: "chatMessages",
|
||||
payload: {
|
||||
messages: this.chatMessages,
|
||||
messages: this.allowScriptChatAccess ? this.chatMessages : [],
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -461,27 +476,20 @@ export class BroadcastRenderer {
|
||||
}
|
||||
|
||||
setEmoteCatalog(catalog) {
|
||||
const globalEmotes = Array.isArray(catalog?.global) ? catalog.global : [];
|
||||
const channelEmotes = Array.isArray(catalog?.channel) ? catalog.channel : [];
|
||||
this.emoteCatalog = [...globalEmotes, ...channelEmotes];
|
||||
this.globalEmotes = Array.isArray(catalog?.global) ? catalog.global : [];
|
||||
this.channelEmotes = Array.isArray(catalog?.channel) ? catalog.channel : [];
|
||||
this.refreshEmoteCatalog();
|
||||
}
|
||||
|
||||
refreshEmoteCatalog() {
|
||||
const allowedChannelEmotes = this.allowChannelEmotesForAssets ? this.channelEmotes : [];
|
||||
this.emoteCatalog = [...this.globalEmotes, ...allowedChannelEmotes];
|
||||
this.emoteCatalogById = new Map(
|
||||
this.emoteCatalog.map((entry) => [String(entry?.id || ""), entry]).filter(([key]) => key),
|
||||
);
|
||||
if (this.chatMessages.length) {
|
||||
this.chatMessages = this.chatMessages.map((message) => {
|
||||
if (!Array.isArray(message.fragments)) {
|
||||
return message;
|
||||
}
|
||||
const fragments = message.fragments.map((fragment) => {
|
||||
if (fragment.type !== "emote" || fragment.url) {
|
||||
return fragment;
|
||||
}
|
||||
const emoteInfo = this.emoteCatalogById.get(String(fragment.id));
|
||||
if (!emoteInfo) {
|
||||
return fragment;
|
||||
}
|
||||
return { ...fragment, url: emoteInfo.url, name: emoteInfo.name || fragment.name };
|
||||
});
|
||||
const fragments = this.buildMessageFragments(message.message || "", message.tags);
|
||||
return { ...message, fragments };
|
||||
});
|
||||
this.updateScriptWorkerChatMessages();
|
||||
@@ -537,13 +545,17 @@ export class BroadcastRenderer {
|
||||
}
|
||||
const emoteText = message.slice(emote.start, emote.end + 1);
|
||||
const emoteInfo = this.emoteCatalogById.get(String(emote.id));
|
||||
fragments.push({
|
||||
type: "emote",
|
||||
id: emote.id,
|
||||
text: emoteText,
|
||||
name: emoteInfo?.name || emoteText,
|
||||
url: emoteInfo?.url || null,
|
||||
});
|
||||
if (emoteInfo) {
|
||||
fragments.push({
|
||||
type: "emote",
|
||||
id: emote.id,
|
||||
text: emoteText,
|
||||
name: emoteInfo?.name || emoteText,
|
||||
url: emoteInfo?.url || null,
|
||||
});
|
||||
} else {
|
||||
fragments.push({ type: "text", text: emoteText });
|
||||
}
|
||||
cursor = emote.end + 1;
|
||||
});
|
||||
if (cursor < message.length) {
|
||||
@@ -553,6 +565,9 @@ export class BroadcastRenderer {
|
||||
}
|
||||
|
||||
receiveChatMessage(message) {
|
||||
if (!this.allowScriptChatAccess) {
|
||||
return;
|
||||
}
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ const elements = {
|
||||
canvasHeight: document.getElementById("canvas-height"),
|
||||
canvasStatus: document.getElementById("canvas-status"),
|
||||
canvasSaveButton: document.getElementById("save-canvas-btn"),
|
||||
allowChannelEmotes: document.getElementById("allow-channel-emotes"),
|
||||
allowScriptChat: document.getElementById("allow-script-chat"),
|
||||
scriptSettingsStatus: document.getElementById("script-settings-status"),
|
||||
scriptSettingsSaveButton: document.getElementById("save-script-settings-btn"),
|
||||
};
|
||||
|
||||
const apiBase = `/api/channels/${encodeURIComponent(broadcaster)}`;
|
||||
@@ -242,6 +246,54 @@ async function saveCanvasSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderScriptSettings(settings) {
|
||||
if (elements.allowChannelEmotes) {
|
||||
elements.allowChannelEmotes.checked = settings.allowChannelEmotesForAssets !== false;
|
||||
}
|
||||
if (elements.allowScriptChat) {
|
||||
elements.allowScriptChat.checked = settings.allowScriptChatAccess !== false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchScriptSettings() {
|
||||
try {
|
||||
const data = await fetchJson("/settings", {}, "Failed to load script settings");
|
||||
renderScriptSettings(data);
|
||||
} catch (error) {
|
||||
renderScriptSettings({ allowChannelEmotesForAssets: true, allowScriptChatAccess: true });
|
||||
showToast("Using default script settings. Unable to load saved preferences.", "warning");
|
||||
}
|
||||
}
|
||||
|
||||
async function saveScriptSettings() {
|
||||
const allowChannelEmotesForAssets = elements.allowChannelEmotes?.checked ?? true;
|
||||
const allowScriptChatAccess = elements.allowScriptChat?.checked ?? true;
|
||||
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "Saving...";
|
||||
setButtonBusy(elements.scriptSettingsSaveButton, true, "Saving...");
|
||||
try {
|
||||
const settings = await fetchJson(
|
||||
"/settings",
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ allowChannelEmotesForAssets, allowScriptChatAccess }),
|
||||
},
|
||||
"Failed to save script settings",
|
||||
);
|
||||
renderScriptSettings(settings);
|
||||
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "Saved.";
|
||||
showToast("Script settings saved successfully.", "success");
|
||||
setTimeout(() => {
|
||||
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "";
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "Unable to save right now.";
|
||||
showToast("Unable to save script settings. Please retry.", "error");
|
||||
} finally {
|
||||
setButtonBusy(elements.scriptSettingsSaveButton, false, "Saving...");
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.adminInput) {
|
||||
elements.adminInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
@@ -254,3 +306,4 @@ if (elements.adminInput) {
|
||||
fetchAdmins();
|
||||
fetchSuggestedAdmins();
|
||||
fetchCanvasSettings();
|
||||
fetchScriptSettings();
|
||||
|
||||
Reference in New Issue
Block a user