diff --git a/.common/core/widget-dock-core/index.html b/.common/core/widget-dock-core/index.html index 37638a2..4aa341f 100644 --- a/.common/core/widget-dock-core/index.html +++ b/.common/core/widget-dock-core/index.html @@ -16,9 +16,9 @@
- -
@@ -29,7 +29,7 @@ -
+
diff --git a/.common/core/widget-dock-core/script.js b/.common/core/widget-dock-core/script.js index dbe6296..e67d58f 100644 --- a/.common/core/widget-dock-core/script.js +++ b/.common/core/widget-dock-core/script.js @@ -7,6 +7,7 @@ const urlParams = new URLSearchParams(queryString); const configJson = urlParams.get("config") || ""; + /////////////////// // PAGE ELEMENTS // /////////////////// @@ -22,18 +23,25 @@ const sbRequiredActionsSuccessLabel = document.getElementById('sb-required-actio const sbRequiredActionsFailureLabel = document.getElementById('sb-required-actions-failure'); const sbRequiredActionsFailureSubtext = document.getElementById('sb-required-actions-failure-subtext'); - const sbRequiredActionsList = document.getElementById('sb-required-actions-list'); const sbImportCodeLabel = document.getElementById('sb-import-code'); const sbImportCopyButton = document.getElementById('sb-import-copy-button'); - const sbActionsButton = document.getElementById('sb-actions-button'); const sbStatusButton = document.getElementById('sb-status-button'); const sbStatusIcon = document.getElementById('sb-status-icon'); const blurLayer = document.getElementById('blur-layer'); +const contentIFrame = document.getElementById('content'); + + +////////////////////// +// GLOBAL VARIABLES // +////////////////////// + +let sbClientListeners; + ///////////////////////// @@ -54,7 +62,7 @@ const sbServerAddress = sbAddressInput.value; const sbServerPort = sbPortInput.value; const sbServerPassword = sbPasswordInput.value; -window.sbClient = new StreamerbotClient({ +sbClient = new StreamerbotClient({ host: sbServerAddress, port: sbServerPort, password: sbServerPassword, @@ -65,6 +73,16 @@ window.sbClient = new StreamerbotClient({ console.debug(data); SetConnectionState(true); + + // Notify iframe + contentIFrame.addEventListener("load", () => { + NotifyTheContentIFrameThatSBHasConnectedSuccessfullySoItCanDoStuff(data); + }); + NotifyTheContentIFrameThatSBHasConnectedSuccessfullySoItCanDoStuff(); + + // Idk why but listeners get cleared when re-connecting, so copy them back in + if (sbClientListeners) + sbClient.listeners = sbClientListeners; }, onDisconnect: () => { @@ -89,7 +107,7 @@ function SetConnectionState(isConnected) { sbErrorLabel.style.display = 'none'; sbStatusIcon.src = 'icons/connected.svg'; - sbStatusButton.title = `Connected to ${window.sbClient.info.name} (${window.sbClient.info.version})`; + sbStatusButton.title = `Connected to ${sbClient.info.name} (${sbClient.info.version})`; // Check required actions CheckRequiredActions(); @@ -157,7 +175,7 @@ async function CheckRequiredActions() { // Iterate over required SB actions and check if they're present config.requiredSbActions.forEach(req => { const exists = response.actions.some(act => act.id === req.id); - + console.debug(`${req.name}: ${exists ? 'Found' : 'Missing'}`) // As soon as one is found that doesn't exist, throw up warning @@ -188,8 +206,7 @@ async function CheckRequiredActions() { sbRequiredActionsList.appendChild(item); }); } - else - { + else { // There are no required actions, so hide the button sbActionsButton.style.display = 'none'; } @@ -199,8 +216,7 @@ async function CheckRequiredActions() { } function SetRequiredActionState(isSuccess) { - if (isSuccess) - { + if (isSuccess) { sbRequiredActionsSuccessLabel.style.display = 'block'; sbRequiredActionsFailureLabel.style.display = 'none'; sbRequiredActionsFailureSubtext.style.display = 'none'; @@ -209,12 +225,11 @@ function SetRequiredActionState(isSuccess) { sbActionsButton.textContent = '✅'; } - else - { + else { sbRequiredActionsSuccessLabel.style.display = 'none'; sbRequiredActionsFailureLabel.style.display = 'block'; sbRequiredActionsFailureSubtext.style.display = 'block'; - + sbActionsButton.title = `You are missing Streamer.bot actions`; sbActionsButton.textContent = '⚠️'; @@ -228,7 +243,8 @@ function SetRequiredActionState(isSuccess) { // PAGE INTERACTIONS // /////////////////////// -function Connect() { +function Connect() { + sbClientListeners = sbClient.listeners; sbClient.options.host = sbAddressInput.value; sbClient.options.port = sbPortInput.value; sbClient.options.password = sbPasswordInput.value; @@ -240,7 +256,7 @@ function CopyImportCode() { navigator.clipboard.writeText(textToCopy) .then(() => { console.debug('Copied to clipboard!'); - + // Click feedback const root = document.documentElement; const successColor = getComputedStyle(root).getPropertyValue('--success-color').trim(); @@ -273,4 +289,11 @@ function OpenRequiredActionsDialog() { function CloseRequiredActionsDialog() { sbRequiredActionsDialog.style.display = "none"; blurLayer.style.display = "none"; +} + +function NotifyTheContentIFrameThatSBHasConnectedSuccessfullySoItCanDoStuff(data) { + contentIFrame.contentWindow.postMessage( + { type: "sbClientConnected", data }, + "*" // replace with iframe origin for security if needed + ); } \ No newline at end of file diff --git a/.common/core/widget-dock-core/style.css b/.common/core/widget-dock-core/style.css index 53dca40..198d01e 100644 --- a/.common/core/widget-dock-core/style.css +++ b/.common/core/widget-dock-core/style.css @@ -17,7 +17,8 @@ body { align-items: center; padding: 0em 1em; gap: 1em; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 1); + box-shadow: 0 0 10px rgba(0,0,0,1); + z-index: 1; } #header-end { @@ -25,11 +26,6 @@ body { align-items: center; display: flex; flex-direction: row; - gap: 1em; -} - -#header-end button { - padding: 0.2em; } @@ -41,21 +37,6 @@ body { #content { flex: 1; overflow: auto; - margin: 1em; -} - - - -/******************/ -/*** BLUR LAYER ***/ -/******************/ - -#blur-layer { - position: fixed; - inset: 0; /* top:0; left:0; bottom:0; right:0 */ - backdrop-filter: blur(10px); /* blur the content behind */ - -webkit-backdrop-filter: blur(10px); /* Safari support */ - z-index: 1; /* behind the modal */ } diff --git a/.common/styles/global.css b/.common/styles/global.css index ef0826b..c532de9 100644 --- a/.common/styles/global.css +++ b/.common/styles/global.css @@ -34,20 +34,25 @@ body { /************/ .title { - font-weight: 900; - font-size: 1.2em; - text-transform: uppercase; + font-weight: 900; + font-size: 1.2em; + text-transform: uppercase; } .field { - display: flex; - flex-direction: column; - gap: 0.5em; + display: flex; + flex-direction: column; + gap: 0.5em; } .setting-description { - font-size: 0.9em; - font-weight: 100; + font-size: 0.9em; + font-weight: 200; +} + +.setting-attribute { + font-size: 0.8em; + font-weight: 200; } @@ -80,6 +85,9 @@ button { padding: 0.5em 1em; width: 100%; transition: all 0.2s ease-in-out; + display: flex; + align-items: center; + justify-content: center; } button:hover { @@ -92,26 +100,66 @@ button:disabled { cursor: inherit; } +.icon-button { + margin-left: auto; + width: 1em; + height: 1em; + + background: transparent; + border: none; + font-size: 1.5em; + font-weight: 100; + padding: 1em; + border-radius: 50%; +} + +.icon-button:hover { + background: var(--button-color); +} + /************************/ /*** INPUT TEXT BOXES ***/ /************************/ +textarea, input { + font-family: inherit; border-radius: 0.5em; width: auto; padding: 0.5em; background-color: #171717; - border: 1px solid #404040; + border: none; + outline: 1px solid #404040; color: white; font-size: 0.9em; + transition: 0.2s; } +textarea:disabled, input:disabled { opacity: 0.5; } +textarea:focus, +input:focus, +select:focus { + outline: 1px solid var(--accent-color); +} + +textarea { + resize: vertical; +} + +.textarea-description { + min-height: 10em; +} + +textarea::-webkit-resizer { + display: none; +} + /***********************/ @@ -226,16 +274,16 @@ iframe { /***************/ .callout { - font-size: 0.9em; - font-weight: 100; - background-color: var(--callout-background); - display: flex; - flex-direction: column; - gap: 1em; - padding: 1em; + font-size: 0.9em; + font-weight: 100; + background-color: var(--callout-background); + display: flex; + flex-direction: column; + gap: 1em; + padding: 1em; - border-radius: 0.5em; - border: 1px solid #40404080; + border-radius: 0.5em; + border: 1px solid #40404080; } @@ -245,21 +293,22 @@ iframe { /**************/ .dialog { - font-size: 1em; - background-color: var(--dialog-background); - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - display: flex; - flex-direction: column; - gap: 1em; - padding: 1em; + font-size: 1em; + background-color: var(--dialog-background); + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + gap: 1em; + padding: 1em; - border-radius: 0.5em; - border: 1px solid #40404080; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); - z-index: 1000; /* above overlay */ + border-radius: 0.5em; + border: 1px solid #40404080; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + z-index: 1000; + /* above overlay */ } .dialog-nav-button { @@ -271,11 +320,49 @@ iframe { width: 1em; height: 1em; position: absolute; - position: fixed; /* fixed to viewport */ - top: 0.5em; /* small offset from top */ - right: 0.5em; /* small offset from right */ + position: fixed; + /* fixed to viewport */ + top: 0.5em; + /* small offset from top */ + right: 0.5em; + /* small offset from right */ } .dialog-nav-button:hover { background: var(--button-color); +} + + + +/******************/ +/*** BLUR LAYER ***/ +/******************/ + +.blur { + position: fixed; + inset: 0; + /* top:0; left:0; bottom:0; right:0 */ + backdrop-filter: blur(10px); + /* blur the content behind */ + -webkit-backdrop-filter: blur(10px); + /* Safari support */ + z-index: 1; + /* behind the modal */ +} + + +/***************/ +/*** SELECTS ***/ +/***************/ + +select { + padding: 0.5em; + font-size: 0.9em; + border-radius: 0.5em; + border: none; + outline: 1px solid #404040; + background-color: var(--callout-background); + color: #fff; + cursor: pointer; + min-width: 8em; } \ No newline at end of file diff --git a/multistream-title-updater/connected.svg b/.old/multistream-title-updater/connected.svg similarity index 100% rename from multistream-title-updater/connected.svg rename to .old/multistream-title-updater/connected.svg diff --git a/multistream-title-updater/disconnected.svg b/.old/multistream-title-updater/disconnected.svg similarity index 100% rename from multistream-title-updater/disconnected.svg rename to .old/multistream-title-updater/disconnected.svg diff --git a/multistream-title-updater/icons.afdesign b/.old/multistream-title-updater/icons.afdesign similarity index 100% rename from multistream-title-updater/icons.afdesign rename to .old/multistream-title-updater/icons.afdesign diff --git a/.old/multistream-title-updater/index.html b/.old/multistream-title-updater/index.html new file mode 100644 index 0000000..4c088ec --- /dev/null +++ b/.old/multistream-title-updater/index.html @@ -0,0 +1,81 @@ + + + + + + + + + + +
Refreshed
+ +
+ + +
+ + +
+ +
+
Update Titles
+ + +
+ +
+ +
+
+

+
+ + +
+
+ +
+ +
+
+ + + +
+ + + +
+ + + + \ No newline at end of file diff --git a/multistream-title-updater/info.svg b/.old/multistream-title-updater/info.svg similarity index 100% rename from multistream-title-updater/info.svg rename to .old/multistream-title-updater/info.svg diff --git a/multistream-title-updater/requiredActions.txt b/.old/multistream-title-updater/requiredActions.txt similarity index 98% rename from multistream-title-updater/requiredActions.txt rename to .old/multistream-title-updater/requiredActions.txt index 503a772..c43224d 100644 --- a/multistream-title-updater/requiredActions.txt +++ b/.old/multistream-title-updater/requiredActions.txt @@ -1,4 +1,4 @@ -[NUT] Multistream Title Updater | Fetch Broadcasts -[NUT] Multistream Title Updater | Update All Broadcasts -[NUT] Multistream Title Updater | Update Twitch Title +[NUT] Multistream Title Updater | Fetch Broadcasts +[NUT] Multistream Title Updater | Update All Broadcasts +[NUT] Multistream Title Updater | Update Twitch Title [NUT] Multistream Title Updater | Update YouTube Title \ No newline at end of file diff --git a/.old/multistream-title-updater/script.js b/.old/multistream-title-updater/script.js new file mode 100644 index 0000000..72ec198 --- /dev/null +++ b/.old/multistream-title-updater/script.js @@ -0,0 +1,444 @@ +//////////// +// FIELDS // +//////////// + +let sbDebugMode = true; + +const queryString = window.location.search; +const urlParams = new URLSearchParams(queryString); + +const sbServerPort = urlParams.get("port") || 8080; +const sbServerAddress = urlParams.get("server") || "127.0.0.1"; + +///////////////// +// GLOBAL VARS // +///////////////// + +let ws; + +/////////////////////////////////// +// SRTEAMER.BOT WEBSOCKET SERVER // +/////////////////////////////////// + +// This is the main function that connects to the Streamer.bot websocke server +function connectws() { + if ("WebSocket" in window) { + ws = new WebSocket("ws://" + sbServerAddress + ":" + sbServerPort + "/"); + + // Reconnect + ws.onclose = function () { + SetConnectionStatus(false); + setTimeout(connectws, 5000); + }; + + // Connect + ws.onopen = async function () { + SetConnectionStatus(true); + + console.log("Subscribe to events"); + ws.send( + JSON.stringify({ + request: "Subscribe", + id: "subscribe-events-id", + // This is the list of Streamer.bot websocket events to subscribe to + // See full list of events here: + // https://docs.streamer.bot/api/servers/websocket/requests + events: { + twitch: [ + "StreamUpdate" + ], + youTube: [ + "BroadcastStarted", + "BroadcastEnded", + "BroadcastUpdated" + ], + general: [ + "Custom" + ] + } + }) + ); + + sbGetActions(ws); + + ws.onmessage = function (event) { + // Grab message and parse JSON + const msg = event.data; + const wsdata = JSON.parse(msg); + + // Check if the user installed all the required Streamer.bot actions + if (wsdata.id == "GetActions") { + + // Check if all the required SB action exist + ReadLinesFromFile('requiredActions.txt') + .then(requiredActions => { + if (sbCheckRequiredActions(wsdata.actions, requiredActions)) { + SetElementVisibility("missingActionsInstructions", false); + sbFetchBroadcasts(ws); + } + else + SetElementVisibility("missingActionsInstructions", true); + }) + } + + if (typeof wsdata.event == "undefined") { + return; + } + + // Print data to log for debugging purposes + if (sbDebugMode) { + console.log(wsdata.data); + console.log(wsdata.event.source); + console.log(wsdata.event.type); + } + + // Check for events to trigger + // See documentation for all events here: + // https://wiki.streamer.bot/en/Servers-Clients/WebSocket-Server/Events + switch (wsdata.event.source) { + // Twitch Events + case 'Twitch': + switch (wsdata.event.type) { + case ('StreamUpdate'): + sbFetchBroadcasts(ws); + break; + } + // Twitch Events + case 'YouTube': + switch (wsdata.event.type) { + case ('BroadcastStarted'): + case ('BroadcastEnded'): + case ('BroadcastUpdated'): + sbFetchBroadcasts(ws); + break; + } + // General Events + case 'General': + switch (wsdata.event.type) { + case ('Custom'): + switch (wsdata.data.action) { + case ('[NUT] Multistream Title Updater | Fetch Broadcasts'): + UpdateBroadcastList(wsdata.data); + break; + } + break; + } + break; + + } + }; + } + } +} + + + +///////////////////////// +// STREAMER.BOT WIDGET // +///////////////////////// + +function sbGetActions(ws) { + + let request = JSON.stringify({ + request: "GetActions", + id: "GetActions" + }); + + ws.send(request); +} + +// Check if all the entries in targetActionNames exist in actionList +function sbCheckRequiredActions(actionList, targetActionNames) { + let foundActions = 0; + for (targetActionName of targetActionNames) { + if (actionList.some(action => action.name === targetActionName)) + foundActions++; + } + + return foundActions == targetActionNames.length +} + +function sbFetchBroadcasts(ws) { + + let request = JSON.stringify({ + request: "DoAction", + id: generateUUID(), + action: { + name: "[NUT] Multistream Title Updater | Fetch Broadcasts" + } + }); + + ws.send(request); +} + +function sbUpdateTitles(ws, title) { + let request = JSON.stringify({ + request: "DoAction", + id: generateUUID(), + action: { + name: "[NUT] Multistream Title Updater | Update All Broadcasts" + }, + args: { + title: title + } + + }); + + ws.send(request); +} + +function sbUpdateTwitchTitle(ws, title) { + let request = JSON.stringify({ + request: "DoAction", + id: generateUUID(), + action: { + name: "[NUT] Multistream Title Updater | Update Twitch Title" + }, + args: { + title: title + } + + }); + + ws.send(request); +} + +function sbUpdateYouTubeTitle(ws, title, broadcastId) { + let request = JSON.stringify({ + request: "DoAction", + id: generateUUID(), + action: { + name: "[NUT] Multistream Title Updater | Update YouTube Title" + }, + args: { + broadcastId: broadcastId, + title: title + } + + }); + + ws.send(request); +} + + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// + +function generateUUID() { // Public Domain/MIT + var d = new Date().getTime();//Timestamp + var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;//Time in microseconds since page-load or 0 if unsupported + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16;//random number between 0 and 16 + if (d > 0) {//Use timestamp until depleted + r = (d + r) % 16 | 0; + d = Math.floor(d / 16); + } else {//Use microseconds since page-load if supported + r = (d2 + r) % 16 | 0; + d2 = Math.floor(d2 / 16); + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); +} + +function IsNullOrWhitespace(str) { + return /^\s*$/.test(str); +} + +function SetElementVisibility(elementID, visibility) { + let element = document.getElementById(elementID); + if (visibility) + element.style.display = 'inline'; + else + element.style.display = 'none'; +} + +function ReadLinesFromFile(filePath) { + return new Promise((resolve, reject) => { + const request = new XMLHttpRequest(); + request.open('GET', filePath, true); + request.onload = function () { + if (request.status === 200) { + resolve(request.responseText.split(/\r?\n/)); + } else { + reject(new Error(`File loading failed with status: ${request.status}`)); + } + }; + request.onerror = function () { + reject(new Error('Network error occurred during file loading.')); + }; + request.send(); + }); +} + + + +/////////////////////////////////// +// STREAMER.BOT WEBSOCKET STATUS // +/////////////////////////////////// + +// This function sets the visibility of the Streamer.bot status label on the overlay +function SetConnectionStatus(connected) { + let connectionStatusIcon = document.getElementById("connectionStatusIcon"); + let ThisIsWhereAllTheCoolStuffHappens = document.getElementById("ThisIsWhereAllTheCoolStuffHappens"); + + if (connected) { + connectionStatusIcon.src = "connected.svg"; + ThisIsWhereAllTheCoolStuffHappens.classList.remove('disabled'); + SetElementVisibility("infoIcon", false); + SetElementVisibility("streamerbotConnectInstructions", false); + } + else { + connectionStatusIcon.src = "disconnected.svg"; + ThisIsWhereAllTheCoolStuffHappens.classList.add('disabled'); + SetElementVisibility("infoIcon", true); + SetElementVisibility("streamerbotConnectInstructions", true); + + // (1) Clear list of broadcasts + const fieldsContainer = document.getElementById('fieldsContainer'); + fieldsContainer.innerHTML = ""; + } +} + +// This function sets the visibility of the Streamer.bot status label on the overlay +function RefreshAnimation() { + let refreshContainer = document.getElementById("refreshContainer"); + refreshContainer.style.opacity = 1; + var tl = new TimelineMax(); + tl + .to(refreshContainer, 2, { opacity: 0, ease: Linear.easeNone }) +} + + + +// Button handling for UPDATE ALL button only +const infoIcon = document.querySelector("#infoIcon"); +infoIcon.addEventListener("click", function () { + window.open("https://www.notion.so/nutty-s-multistream-title-updater-e2fb7b24c7514f9cbd943729f54be1d9"); +}); + +// Button handling for UPDATE ALL button only +const submitButton = document.querySelector("#submitButton"); +submitButton.addEventListener("click", function () { + const titleInput = document.querySelector("#titleInput").value; + sbUpdateTitles(ws, titleInput); +}); + +// Button handling for REFRESH button only +const refreshButton = document.querySelector("#refreshButton"); +refreshButton.addEventListener("click", function () { + sbFetchBroadcasts(ws); +}); + + +function UpdateBroadcastList(data) { + // Iterate through broadcast list + // Find div whose ID matches the broadcast + // If it exists, update the title + // else, it's a new broadcast, so add it to the list + const fieldsContainer = document.getElementById('fieldsContainer'); + for (const broadcast of data.broadcastList) { + const broadcastDiv = fieldsContainer.querySelector(`#${broadcast.id}`); + + if (broadcastDiv == null) + AddBroadcast(broadcast); + else + UpdateBroadcast(broadcastDiv, broadcast); + } + + // Check if any broadcasts have gone offline + // If so, delete them from the list + var parentDiv = document.getElementById('fieldsContainer'); + const childDivs = parentDiv.querySelectorAll("div"); + childDivs.forEach(childDiv => { + var result = data.broadcastList.find(obj => { + return obj.id === childDiv.id + }) + if (result == null) + childDiv.innerHTML = ""; + }); + + //// THIS CODE ALSO WORKS AND IS WAY SIMPLER, I JUST MADE THINGS + //// 10 TIMES HARDER FOR MYSELF BECAUSE I LOVE PAIN POGGERS + // // (1) Clear list of broadcasts + // const fieldsContainer = document.getElementById('fieldsContainer'); + // fieldsContainer.innerHTML = ""; + + // // (2) For each broadcast in list, create new entry + // for (const broadcast of data.broadcastList) + // { + // AddBroadcast(broadcast); + // } + + // Count the number of YouTube broadcasts + // If this is 0, put a message to tell the user that they need to go live on YouTube first + // Else, hide that message + const youtubeBroadcastCount = data.broadcastList.reduce((count, broadcast) => count + (broadcast.platform === "youtube" ? 1 : 0), 0); + if (youtubeBroadcastCount <= 0) + SetElementVisibility("noYouTubeStreamsInstructions", true); + else + SetElementVisibility("noYouTubeStreamsInstructions", false); + + RefreshAnimation(); +} + +function AddBroadcast(broadcast) { + // Get a reference to the template + const template = document.getElementById('platformTemplate'); + + // Create a new instance of the template + const instance = template.content.cloneNode(true); + + // Assign ID to instance + const broadcastBox = instance.querySelector('.broadcastBox'); + broadcastBox.id = broadcast.id; + + // Modify the content of the template instance + const titleElement = instance.querySelector('.platformTitle'); + titleElement.value = broadcast.title; + + // Modify the content of the template instance + const buttonElement = instance.querySelector('.platformSubmitButton'); + buttonElement.classList.add(broadcast.platform); + + // Modify the icon of the template instance + const iconElement = instance.querySelector('#icon'); + + // Add button handling (need separate handling for Twitch/YouTube) + switch (broadcast.platform) { + case 'twitch': + iconElement.src = 'twitch.png'; + + buttonElement.addEventListener("click", function () { + sbUpdateTwitchTitle(ws, titleElement.value); + }); + break; + case 'youtube': + // Modify the icon of the template instance + iconElement.src = 'youtube.png'; + + buttonElement.addEventListener("click", function () { + const youtubeID = broadcast.id.replace('youtube-', ''); + sbUpdateYouTubeTitle(ws, titleElement.value, youtubeID); + }); + break; + } + + // Add click event + const iconButton = instance.querySelector('#platformIconButton'); + iconButton.addEventListener("click", function () { + window.open(broadcast.url); + }); + + // Insert the modified template instance into the DOM + const fieldsContainer = document.getElementById('fieldsContainer'); + fieldsContainer.appendChild(instance); +} + +function UpdateBroadcast(div, broadcast) { + // Modify the content of the template instance + const titleElement = div.querySelector('.platformTitle'); + titleElement.value = broadcast.title; +} + +connectws(); \ No newline at end of file diff --git a/multistream-title-updater/style.css b/.old/multistream-title-updater/style.css similarity index 100% rename from multistream-title-updater/style.css rename to .old/multistream-title-updater/style.css diff --git a/multistream-title-updater/twitch.png b/.old/multistream-title-updater/twitch.png similarity index 100% rename from multistream-title-updater/twitch.png rename to .old/multistream-title-updater/twitch.png diff --git a/multistream-title-updater/youtube.png b/.old/multistream-title-updater/youtube.png similarity index 100% rename from multistream-title-updater/youtube.png rename to .old/multistream-title-updater/youtube.png diff --git a/.templates/obs-dock/config.json b/.templates/obs-dock/config.json new file mode 100644 index 0000000..32ca26f --- /dev/null +++ b/.templates/obs-dock/config.json @@ -0,0 +1,6 @@ +{ + "title": "Widget", + "requiredSbActions": [ + ], + "sbImportCode": "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" +} \ No newline at end of file diff --git a/.templates/obs-dock/contents/index.html b/.templates/obs-dock/contents/index.html new file mode 100644 index 0000000..6b37cd3 --- /dev/null +++ b/.templates/obs-dock/contents/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.templates/obs-dock/contents/script.js b/.templates/obs-dock/contents/script.js new file mode 100644 index 0000000..9eb8555 --- /dev/null +++ b/.templates/obs-dock/contents/script.js @@ -0,0 +1,45 @@ +////////////////////// +// GLOBAL VARIABLES // +////////////////////// + +const sbAction = ''; + + + +///////////////////////// +// STREAMER.BOT EVENTS // +///////////////////////// + +window.parent.sbClient.on('General.Custom', (response) => { + console.debug(response.data); + CustomEvent(response.data); +}) + + + +///////////////// +// PRINTER BOT // +///////////////// + + + + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// + + + + + +/////////////////////// +// PAGE INTERACTIONS // +/////////////////////// + + + + + +/////////////////// +// PAGE SETTINGS // +/////////////////// \ No newline at end of file diff --git a/.templates/obs-dock/contents/style.css b/.templates/obs-dock/contents/style.css new file mode 100644 index 0000000..9fa4a22 --- /dev/null +++ b/.templates/obs-dock/contents/style.css @@ -0,0 +1,3 @@ +body { + margin: 1em; +} \ No newline at end of file diff --git a/.templates/obs-dock/index.html b/.templates/obs-dock/index.html new file mode 100644 index 0000000..332b16c --- /dev/null +++ b/.templates/obs-dock/index.html @@ -0,0 +1,20 @@ + + + + + + nutty + + + + + + + + + \ No newline at end of file diff --git a/.templates/obs-dock/script.js b/.templates/obs-dock/script.js new file mode 100644 index 0000000..2668f87 --- /dev/null +++ b/.templates/obs-dock/script.js @@ -0,0 +1,16 @@ +// Construct URL +const currentURL = window.location.href; +let baseURL = currentURL; + +if (baseURL.endsWith("index.html")) + baseURL = baseURL.replace("index.html", ""); + +const configJson = "?config=" + baseURL + "config.json"; + +// Implement widget dock core +window.dockWrapper = document.getElementById('dock-wrapper'); +dockWrapper.src = `../../.common/core/widget-dock-core${configJson}`; + +dockWrapper.addEventListener('load', () => { + dockWrapper.contentWindow.content.src = baseURL + '/contents'; +}); \ No newline at end of file diff --git a/multistream-title-updater/config.json b/multistream-title-updater/config.json new file mode 100644 index 0000000..1f4cf89 --- /dev/null +++ b/multistream-title-updater/config.json @@ -0,0 +1,18 @@ +{ + "title": "Stream Info", + "requiredSbActions": [ + { + "id": "9486774d-d706-41d8-85b4-7daff5cd1b0d", + "name": "Multistream Title Updater | Fetch Broadcasts" + }, + { + "id": "1c58eff0-e98a-4fab-86f0-6f5cee1d3ab3", + "name": "Multistream Title Updater | Update Stream Info" + }, + { + "id": "14da1d44-6e29-4582-92c3-2c59388be57e", + "name": "Multistream Title Updater | Open Link" + } + ], + "sbImportCode": "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" +} \ No newline at end of file diff --git a/multistream-title-updater/contents/icons/platforms/kick.png b/multistream-title-updater/contents/icons/platforms/kick.png new file mode 100644 index 0000000..9cc9afa Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/kick.png differ diff --git a/multistream-title-updater/contents/icons/platforms/kofi.png b/multistream-title-updater/contents/icons/platforms/kofi.png new file mode 100644 index 0000000..8812cac Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/kofi.png differ diff --git a/multistream-title-updater/contents/icons/platforms/patreon.png b/multistream-title-updater/contents/icons/platforms/patreon.png new file mode 100644 index 0000000..22c9748 Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/patreon.png differ diff --git a/multistream-title-updater/contents/icons/platforms/tiktok.png b/multistream-title-updater/contents/icons/platforms/tiktok.png new file mode 100644 index 0000000..c9e0da5 Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/tiktok.png differ diff --git a/multistream-title-updater/contents/icons/platforms/tipeeeStream.png b/multistream-title-updater/contents/icons/platforms/tipeeeStream.png new file mode 100644 index 0000000..cd981f3 Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/tipeeeStream.png differ diff --git a/multistream-title-updater/contents/icons/platforms/twitch.png b/multistream-title-updater/contents/icons/platforms/twitch.png new file mode 100644 index 0000000..dcbd6c8 Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/twitch.png differ diff --git a/multistream-title-updater/contents/icons/platforms/youtube.png b/multistream-title-updater/contents/icons/platforms/youtube.png new file mode 100644 index 0000000..5abdb7b Binary files /dev/null and b/multistream-title-updater/contents/icons/platforms/youtube.png differ diff --git a/multistream-title-updater/contents/index.html b/multistream-title-updater/contents/index.html new file mode 100644 index 0000000..1c73238 --- /dev/null +++ b/multistream-title-updater/contents/index.html @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/multistream-title-updater/contents/script.js b/multistream-title-updater/contents/script.js new file mode 100644 index 0000000..fc18d15 --- /dev/null +++ b/multistream-title-updater/contents/script.js @@ -0,0 +1,308 @@ +////////////////////// +// GLOBAL VARIABLES // +////////////////////// + +const sbActionFetchBroadcasts = '9486774d-d706-41d8-85b4-7daff5cd1b0d'; +const sbActionUpdateStreamInfo = '1c58eff0-e98a-4fab-86f0-6f5cee1d3ab3'; +const sbActionOpenUrl = '14da1d44-6e29-4582-92c3-2c59388be57e'; + +let currentBroadcastId = ''; + + +/////////////////// +// PAGE ELEMENTS // +/////////////////// + +const blurLayer = document.getElementById('blur-layer'); +const updateAllDialog = document.getElementById('update-all-dialog'); +const updateTwitchDialog = document.getElementById('update-twitch-dialog'); +const updateKickDialog = document.getElementById('update-kick-dialog'); +const updateYouTubeDialog = document.getElementById('update-youtube-dialog'); +const broadcastList = document.getElementById('broadcast-list'); +const youtubeWarning = document.getElementById('youtube-warning'); + + + +///////////////////////// +// STREAMER.BOT EVENTS // +///////////////////////// + +window.addEventListener("message", (event) => { + FetchBroadcasts(); +}); + +window.parent.sbClient.on('General.Custom', (response) => { + GeneralCustom(response.data); +}) + + + +/////////////////////////////// +// MULTISTREAM TITLE UPDATER // +/////////////////////////////// + +async function GeneralCustom(data) { + switch(data.actionId) { + case sbActionFetchBroadcasts: + { + // Iterate through list of broadcasts and add it to the list + for (const broadcast of data.broadcastList) + await AddBroadcast(broadcast); + + // Check if YouTube account is connected + // If yes, count how many YouTube broadcasts there were + // If 0, show warning + const broadcasterInfo = await window.parent.sbClient.getBroadcaster(); + if (broadcasterInfo.platforms.youtube) + { + const ytBroadcastCount = data.broadcastList.filter(b => b.platform === "youtube").length; + if (ytBroadcastCount <= 0) + youtubeWarning.style.display = 'inline'; + else + youtubeWarning.style.display = 'none'; + } + else + youtubeWarning.style.display = 'none'; + + // Check if any broadcasts have gone offline + // If so, delete them from the list + const childDivs = broadcastList.querySelectorAll(":scope > div"); + childDivs.forEach(childDiv => { + var result = data.broadcastList.find(obj => { + return obj.id === childDiv.id + }) + if (result == null) + childDiv.remove(); + }); + } + break; + } +} + +async function FetchBroadcasts() { + // Fetch from Streamer.bot + await window.parent.sbClient.doAction({ id: sbActionFetchBroadcasts}); +} + +async function AddBroadcast(data) { + // Get a reference to the template + const template = document.getElementById('broadcast-template'); + + const existingDiv = broadcastList.querySelector(`#${data.id}`); + + // Create a new instance of the template + const instance = existingDiv ? existingDiv : template.content.cloneNode(true); + + // Get divs + const broadcastEl = instance.querySelector('.broadcast'); + const platformIconEl = instance.querySelector('#platform-icon'); + const titleEl = instance.querySelector('#broadcast-title'); + const categoryEl = instance.querySelector('#broadcast-category'); + const kickWarningEl = instance.querySelector('#kick-warning'); + const streamButtonEl = instance.querySelector('#broadcast-stream-button'); + const dashboardButtonEl = instance.querySelector('#broadcast-dashboard-button'); + const editButtonEl = instance.querySelector('#broadcast-edit-button'); + + // Set properties + if (!existingDiv) + broadcastEl.id = data.id; + + // Set the platform icon + platformIconEl.src = `icons/platforms/${data.platform}.png`; + + // Set the stream title + if (data.title) + titleEl.textContent = data.title; + + // Set the stream category + if (data.category) + categoryEl.textContent = data.category; + + // Streamer.bot does not provide title/category for Kick, so pull from API + if (data.platform == 'kick') + { + let response = await fetch('https://kick.com/api/v1/channels/' + data.userLogin); + let response_data = await response.json(); + + // The current title is only provided if the stream if currently live + if (response_data.livestream) + { + titleEl.textContent = response_data.livestream.session_title; + categoryEl.textContent = response_data.livestream.categories[0].name; + kickWarningEl.style.display = 'none'; + } + else if (response_data.previous_livestreams) + { + titleEl.textContent = response_data.previous_livestreams[0].session_title; + categoryEl.textContent = response_data.previous_livestreams[0].categories[0].name; + kickWarningEl.style.display = 'inline'; + } + else + { + kickWarningEl.style.display = 'inline'; + } + } + + streamButtonEl.onclick = function() { + //window.open(data.streamUrl, '_blank'); + OpenURL(data.streamUrl); + }; + + dashboardButtonEl.onclick = function() { + //window.open(data.dashboardUrl, '_blank'); + OpenURL(data.dashboardUrl); + }; + + editButtonEl.onclick = function() { + switch (data.platform) { + case 'twitch': + document.getElementById('update-twitch-title-input').value = titleEl.textContent; + document.getElementById('update-twitch-category-input').value = categoryEl.textContent; + updateTwitchDialog.style.display = "flex"; + break; + case 'kick': + document.getElementById('update-kick-title-input').value = titleEl.textContent; + document.getElementById('update-kick-category-input').value = categoryEl.textContent; + updateKickDialog.style.display = "flex"; + break; + case 'youtube': + currentBroadcastId = broadcastEl.id; + document.getElementById('update-youtube-title-input').value = data.title; + document.getElementById('update-youtube-description-input').value = data.description; + document.getElementById('update-youtube-category-input').value = data.category; + document.getElementById('update-youtube-privacy-select').value = data.privacy; + updateYouTubeDialog.style.display = "flex"; + break; + } + blurLayer.style.display = "block"; + }; + + if (!existingDiv) + broadcastList.appendChild(instance); +} + + + + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// + +async function OpenURL(url) { + await window.parent.sbClient.doAction( + action = { + id: sbActionOpenUrl + }, + args = { + url: url + } + ); +} + + + + + +/////////////////////// +// PAGE INTERACTIONS // +/////////////////////// + +function OpenUpdateAllDialog() { + updateAllDialog.style.display = "flex"; + blurLayer.style.display = "block"; +} + +function CloseUpdateAllDialog() { + updateAllDialog.style.display = "none"; + blurLayer.style.display = "none"; +} + +function CloseUpdateTwitchDialog() { + updateTwitchDialog.style.display = "none"; + blurLayer.style.display = "none"; +} + +function CloseUpdateKickDialog() { + updateKickDialog.style.display = "none"; + blurLayer.style.display = "none"; +} + +function CloseUpdateYouTubeDialog() { + updateYouTubeDialog.style.display = "none"; + blurLayer.style.display = "none"; +} + +async function UpdateAllSubmit() { + await window.parent.sbClient.doAction( + action = { + id: sbActionUpdateStreamInfo + }, + args = { + platform: 'all', + title: document.getElementById('update-all-title-input').value, + category: document.getElementById('update-all-category-input').value + } + ); + + CloseUpdateAllDialog(); +} + +async function UpdateTwitchSubmit() { + await window.parent.sbClient.doAction( + action = { + id: sbActionUpdateStreamInfo + }, + args = { + platform: 'twitch', + title: document.getElementById('update-twitch-title-input').value, + category: document.getElementById('update-twitch-category-input').value, + tags: document.getElementById('update-twitch-tags-input').value + } + ); + + CloseUpdateTwitchDialog(); +} + +async function UpdateKickSubmit() { + await window.parent.sbClient.doAction( + action = { + id: sbActionUpdateStreamInfo + }, + args = { + platform: 'kick', + title: document.getElementById('update-kick-title-input').value, + category: document.getElementById('update-kick-category-input').value + } + ); + + CloseUpdateKickDialog(); +} + +async function UpdateYouTubeSubmit() { + await window.parent.sbClient.doAction( + action = { + id: sbActionUpdateStreamInfo + }, + args = { + platform: 'youtube', + title: document.getElementById('update-youtube-title-input').value, + description: document.getElementById('update-youtube-description-input').value, + category: document.getElementById('update-youtube-category-input').value, + tags: document.getElementById('update-youtube-tags-input').value, + privacy: document.getElementById('update-youtube-privacy-select').value, + broadcastId: currentBroadcastId + } + ); + + CloseUpdateYouTubeDialog(); +} + + + + + +//////////////////////////// +// REFRESH BROADCAST LIST // +//////////////////////////// + +setInterval(FetchBroadcasts, 5000); \ No newline at end of file diff --git a/multistream-title-updater/contents/style.css b/multistream-title-updater/contents/style.css new file mode 100644 index 0000000..95affc9 --- /dev/null +++ b/multistream-title-updater/contents/style.css @@ -0,0 +1,78 @@ +body { + display: flex; + flex-direction: column; + gap: 1em; + margin: 1em; +} + +button { + gap: 0.5em; +} + +#broadcast-list { + display: flex; + flex-direction: column; + gap: 0.5em; +} + +.broadcast { + background-color: var(--dialog-background); + border: 1px solid #40404080; + border-radius: 0.5em; + padding: 0.2em 0.5em; + + display: flex; + flex-direction: row; + align-items: center; + gap: 1em; +} + +.broadcast-info { + display: flex; + flex-direction: column; +} + +#broadcast-buttons { + display: flex; + flex-direction: row; + align-items: center; + /* gap: 1em; */ + margin-left: auto; +} + +.platform-icon { + height: 2em; +} + +.button-icon { + height: 1em; +} + +.flip-that-shit-homie { + /* Flip that bad boy */ + transform: scaleX(-1); +} + +.blur { + display: none; +} + +.dialog { + display: none; + width: 90%; + max-height: 80%; + overflow-y: auto; +} + +.setting-attribute { + color: yellow; +} + +#kick-warning { + display: none; +} + +#youtube-warning { + display: none; + text-align: center; +} \ No newline at end of file diff --git a/multistream-title-updater/index.html b/multistream-title-updater/index.html index 4c088ec..332b16c 100644 --- a/multistream-title-updater/index.html +++ b/multistream-title-updater/index.html @@ -1,81 +1,20 @@ - - - - - + + + + nutty + -
Refreshed
- -
- - -
- - -
- -
-
Update Titles
- - -
- -
- -
-
-

-
- - -
-
- -
- -
-
- - - -
- - - -
+ + - - \ No newline at end of file + \ No newline at end of file diff --git a/multistream-title-updater/script.js b/multistream-title-updater/script.js index 72ec198..2668f87 100644 --- a/multistream-title-updater/script.js +++ b/multistream-title-updater/script.js @@ -1,444 +1,16 @@ -//////////// -// FIELDS // -//////////// +// Construct URL +const currentURL = window.location.href; +let baseURL = currentURL; -let sbDebugMode = true; +if (baseURL.endsWith("index.html")) + baseURL = baseURL.replace("index.html", ""); -const queryString = window.location.search; -const urlParams = new URLSearchParams(queryString); +const configJson = "?config=" + baseURL + "config.json"; -const sbServerPort = urlParams.get("port") || 8080; -const sbServerAddress = urlParams.get("server") || "127.0.0.1"; +// Implement widget dock core +window.dockWrapper = document.getElementById('dock-wrapper'); +dockWrapper.src = `../../.common/core/widget-dock-core${configJson}`; -///////////////// -// GLOBAL VARS // -///////////////// - -let ws; - -/////////////////////////////////// -// SRTEAMER.BOT WEBSOCKET SERVER // -/////////////////////////////////// - -// This is the main function that connects to the Streamer.bot websocke server -function connectws() { - if ("WebSocket" in window) { - ws = new WebSocket("ws://" + sbServerAddress + ":" + sbServerPort + "/"); - - // Reconnect - ws.onclose = function () { - SetConnectionStatus(false); - setTimeout(connectws, 5000); - }; - - // Connect - ws.onopen = async function () { - SetConnectionStatus(true); - - console.log("Subscribe to events"); - ws.send( - JSON.stringify({ - request: "Subscribe", - id: "subscribe-events-id", - // This is the list of Streamer.bot websocket events to subscribe to - // See full list of events here: - // https://docs.streamer.bot/api/servers/websocket/requests - events: { - twitch: [ - "StreamUpdate" - ], - youTube: [ - "BroadcastStarted", - "BroadcastEnded", - "BroadcastUpdated" - ], - general: [ - "Custom" - ] - } - }) - ); - - sbGetActions(ws); - - ws.onmessage = function (event) { - // Grab message and parse JSON - const msg = event.data; - const wsdata = JSON.parse(msg); - - // Check if the user installed all the required Streamer.bot actions - if (wsdata.id == "GetActions") { - - // Check if all the required SB action exist - ReadLinesFromFile('requiredActions.txt') - .then(requiredActions => { - if (sbCheckRequiredActions(wsdata.actions, requiredActions)) { - SetElementVisibility("missingActionsInstructions", false); - sbFetchBroadcasts(ws); - } - else - SetElementVisibility("missingActionsInstructions", true); - }) - } - - if (typeof wsdata.event == "undefined") { - return; - } - - // Print data to log for debugging purposes - if (sbDebugMode) { - console.log(wsdata.data); - console.log(wsdata.event.source); - console.log(wsdata.event.type); - } - - // Check for events to trigger - // See documentation for all events here: - // https://wiki.streamer.bot/en/Servers-Clients/WebSocket-Server/Events - switch (wsdata.event.source) { - // Twitch Events - case 'Twitch': - switch (wsdata.event.type) { - case ('StreamUpdate'): - sbFetchBroadcasts(ws); - break; - } - // Twitch Events - case 'YouTube': - switch (wsdata.event.type) { - case ('BroadcastStarted'): - case ('BroadcastEnded'): - case ('BroadcastUpdated'): - sbFetchBroadcasts(ws); - break; - } - // General Events - case 'General': - switch (wsdata.event.type) { - case ('Custom'): - switch (wsdata.data.action) { - case ('[NUT] Multistream Title Updater | Fetch Broadcasts'): - UpdateBroadcastList(wsdata.data); - break; - } - break; - } - break; - - } - }; - } - } -} - - - -///////////////////////// -// STREAMER.BOT WIDGET // -///////////////////////// - -function sbGetActions(ws) { - - let request = JSON.stringify({ - request: "GetActions", - id: "GetActions" - }); - - ws.send(request); -} - -// Check if all the entries in targetActionNames exist in actionList -function sbCheckRequiredActions(actionList, targetActionNames) { - let foundActions = 0; - for (targetActionName of targetActionNames) { - if (actionList.some(action => action.name === targetActionName)) - foundActions++; - } - - return foundActions == targetActionNames.length -} - -function sbFetchBroadcasts(ws) { - - let request = JSON.stringify({ - request: "DoAction", - id: generateUUID(), - action: { - name: "[NUT] Multistream Title Updater | Fetch Broadcasts" - } - }); - - ws.send(request); -} - -function sbUpdateTitles(ws, title) { - let request = JSON.stringify({ - request: "DoAction", - id: generateUUID(), - action: { - name: "[NUT] Multistream Title Updater | Update All Broadcasts" - }, - args: { - title: title - } - - }); - - ws.send(request); -} - -function sbUpdateTwitchTitle(ws, title) { - let request = JSON.stringify({ - request: "DoAction", - id: generateUUID(), - action: { - name: "[NUT] Multistream Title Updater | Update Twitch Title" - }, - args: { - title: title - } - - }); - - ws.send(request); -} - -function sbUpdateYouTubeTitle(ws, title, broadcastId) { - let request = JSON.stringify({ - request: "DoAction", - id: generateUUID(), - action: { - name: "[NUT] Multistream Title Updater | Update YouTube Title" - }, - args: { - broadcastId: broadcastId, - title: title - } - - }); - - ws.send(request); -} - - -////////////////////// -// HELPER FUNCTIONS // -////////////////////// - -function generateUUID() { // Public Domain/MIT - var d = new Date().getTime();//Timestamp - var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;//Time in microseconds since page-load or 0 if unsupported - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16;//random number between 0 and 16 - if (d > 0) {//Use timestamp until depleted - r = (d + r) % 16 | 0; - d = Math.floor(d / 16); - } else {//Use microseconds since page-load if supported - r = (d2 + r) % 16 | 0; - d2 = Math.floor(d2 / 16); - } - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }); -} - -function IsNullOrWhitespace(str) { - return /^\s*$/.test(str); -} - -function SetElementVisibility(elementID, visibility) { - let element = document.getElementById(elementID); - if (visibility) - element.style.display = 'inline'; - else - element.style.display = 'none'; -} - -function ReadLinesFromFile(filePath) { - return new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); - request.open('GET', filePath, true); - request.onload = function () { - if (request.status === 200) { - resolve(request.responseText.split(/\r?\n/)); - } else { - reject(new Error(`File loading failed with status: ${request.status}`)); - } - }; - request.onerror = function () { - reject(new Error('Network error occurred during file loading.')); - }; - request.send(); - }); -} - - - -/////////////////////////////////// -// STREAMER.BOT WEBSOCKET STATUS // -/////////////////////////////////// - -// This function sets the visibility of the Streamer.bot status label on the overlay -function SetConnectionStatus(connected) { - let connectionStatusIcon = document.getElementById("connectionStatusIcon"); - let ThisIsWhereAllTheCoolStuffHappens = document.getElementById("ThisIsWhereAllTheCoolStuffHappens"); - - if (connected) { - connectionStatusIcon.src = "connected.svg"; - ThisIsWhereAllTheCoolStuffHappens.classList.remove('disabled'); - SetElementVisibility("infoIcon", false); - SetElementVisibility("streamerbotConnectInstructions", false); - } - else { - connectionStatusIcon.src = "disconnected.svg"; - ThisIsWhereAllTheCoolStuffHappens.classList.add('disabled'); - SetElementVisibility("infoIcon", true); - SetElementVisibility("streamerbotConnectInstructions", true); - - // (1) Clear list of broadcasts - const fieldsContainer = document.getElementById('fieldsContainer'); - fieldsContainer.innerHTML = ""; - } -} - -// This function sets the visibility of the Streamer.bot status label on the overlay -function RefreshAnimation() { - let refreshContainer = document.getElementById("refreshContainer"); - refreshContainer.style.opacity = 1; - var tl = new TimelineMax(); - tl - .to(refreshContainer, 2, { opacity: 0, ease: Linear.easeNone }) -} - - - -// Button handling for UPDATE ALL button only -const infoIcon = document.querySelector("#infoIcon"); -infoIcon.addEventListener("click", function () { - window.open("https://www.notion.so/nutty-s-multistream-title-updater-e2fb7b24c7514f9cbd943729f54be1d9"); -}); - -// Button handling for UPDATE ALL button only -const submitButton = document.querySelector("#submitButton"); -submitButton.addEventListener("click", function () { - const titleInput = document.querySelector("#titleInput").value; - sbUpdateTitles(ws, titleInput); -}); - -// Button handling for REFRESH button only -const refreshButton = document.querySelector("#refreshButton"); -refreshButton.addEventListener("click", function () { - sbFetchBroadcasts(ws); -}); - - -function UpdateBroadcastList(data) { - // Iterate through broadcast list - // Find div whose ID matches the broadcast - // If it exists, update the title - // else, it's a new broadcast, so add it to the list - const fieldsContainer = document.getElementById('fieldsContainer'); - for (const broadcast of data.broadcastList) { - const broadcastDiv = fieldsContainer.querySelector(`#${broadcast.id}`); - - if (broadcastDiv == null) - AddBroadcast(broadcast); - else - UpdateBroadcast(broadcastDiv, broadcast); - } - - // Check if any broadcasts have gone offline - // If so, delete them from the list - var parentDiv = document.getElementById('fieldsContainer'); - const childDivs = parentDiv.querySelectorAll("div"); - childDivs.forEach(childDiv => { - var result = data.broadcastList.find(obj => { - return obj.id === childDiv.id - }) - if (result == null) - childDiv.innerHTML = ""; - }); - - //// THIS CODE ALSO WORKS AND IS WAY SIMPLER, I JUST MADE THINGS - //// 10 TIMES HARDER FOR MYSELF BECAUSE I LOVE PAIN POGGERS - // // (1) Clear list of broadcasts - // const fieldsContainer = document.getElementById('fieldsContainer'); - // fieldsContainer.innerHTML = ""; - - // // (2) For each broadcast in list, create new entry - // for (const broadcast of data.broadcastList) - // { - // AddBroadcast(broadcast); - // } - - // Count the number of YouTube broadcasts - // If this is 0, put a message to tell the user that they need to go live on YouTube first - // Else, hide that message - const youtubeBroadcastCount = data.broadcastList.reduce((count, broadcast) => count + (broadcast.platform === "youtube" ? 1 : 0), 0); - if (youtubeBroadcastCount <= 0) - SetElementVisibility("noYouTubeStreamsInstructions", true); - else - SetElementVisibility("noYouTubeStreamsInstructions", false); - - RefreshAnimation(); -} - -function AddBroadcast(broadcast) { - // Get a reference to the template - const template = document.getElementById('platformTemplate'); - - // Create a new instance of the template - const instance = template.content.cloneNode(true); - - // Assign ID to instance - const broadcastBox = instance.querySelector('.broadcastBox'); - broadcastBox.id = broadcast.id; - - // Modify the content of the template instance - const titleElement = instance.querySelector('.platformTitle'); - titleElement.value = broadcast.title; - - // Modify the content of the template instance - const buttonElement = instance.querySelector('.platformSubmitButton'); - buttonElement.classList.add(broadcast.platform); - - // Modify the icon of the template instance - const iconElement = instance.querySelector('#icon'); - - // Add button handling (need separate handling for Twitch/YouTube) - switch (broadcast.platform) { - case 'twitch': - iconElement.src = 'twitch.png'; - - buttonElement.addEventListener("click", function () { - sbUpdateTwitchTitle(ws, titleElement.value); - }); - break; - case 'youtube': - // Modify the icon of the template instance - iconElement.src = 'youtube.png'; - - buttonElement.addEventListener("click", function () { - const youtubeID = broadcast.id.replace('youtube-', ''); - sbUpdateYouTubeTitle(ws, titleElement.value, youtubeID); - }); - break; - } - - // Add click event - const iconButton = instance.querySelector('#platformIconButton'); - iconButton.addEventListener("click", function () { - window.open(broadcast.url); - }); - - // Insert the modified template instance into the DOM - const fieldsContainer = document.getElementById('fieldsContainer'); - fieldsContainer.appendChild(instance); -} - -function UpdateBroadcast(div, broadcast) { - // Modify the content of the template instance - const titleElement = div.querySelector('.platformTitle'); - titleElement.value = broadcast.title; -} - -connectws(); \ No newline at end of file +dockWrapper.addEventListener('load', () => { + dockWrapper.contentWindow.content.src = baseURL + '/contents'; +}); \ No newline at end of file diff --git a/printer-bot/contents/index.html b/printer-bot/contents/index.html index f9ff25b..741461d 100644 --- a/printer-bot/contents/index.html +++ b/printer-bot/contents/index.html @@ -67,7 +67,7 @@
diff --git a/printer-bot/contents/script.js b/printer-bot/contents/script.js index 2b684cd..fc3672c 100644 --- a/printer-bot/contents/script.js +++ b/printer-bot/contents/script.js @@ -304,7 +304,7 @@ async function CustomEvent(data) { titleEl.innerText = `Gifted Sub`; const messageEl = document.createElement('div'); - messageEl.innerHTML = `${data["recipient.userName"]}
received a sub from
${data.user}!`; + messageEl.innerHTML = `${data.user}
gifted a sub to
${data["recipient.userName"]}!`; contentEl.appendChild(messageEl); } diff --git a/printer-bot/contents/style.css b/printer-bot/contents/style.css index ebb8a51..937b872 100644 --- a/printer-bot/contents/style.css +++ b/printer-bot/contents/style.css @@ -1,3 +1,7 @@ +body { + margin: 1em; +} + #settings { display: flex; flex-direction: column; @@ -58,7 +62,8 @@ height: 2em; } -#receipt-icon:empty { +#receipt-icon:not([src]), +#receipt-icon[src=""] { display: none; }