Add files via upload

This commit is contained in:
nuttylmao
2025-03-23 22:04:07 +11:00
committed by GitHub
parent f133722899
commit 6c53d973f7
97 changed files with 15625 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2000 1556" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(30.772,0,0,30.772,323.017,878.74)">
<path d="M32.927,8.977L22,22L11.073,8.977C14.134,6.408 18.003,5 22,5C25.997,5 29.866,6.408 32.927,8.977ZM-5.271,-10.5L-10.497,-16.729C-1.392,-24.369 10.114,-28.557 22,-28.557C33.886,-28.557 45.392,-24.369 54.497,-16.729L49.271,-10.5C41.63,-16.912 31.974,-20.426 22,-20.426C12.026,-20.426 2.37,-16.912 -5.271,-10.5ZM4.373,0.993L0.145,-4.046C6.269,-9.184 14.007,-12 22,-12C29.993,-12 37.731,-9.184 43.855,-4.046L39.627,0.993C34.688,-3.151 28.447,-5.422 22,-5.422C15.553,-5.422 9.312,-3.151 4.373,0.993Z" style="fill:rgb(0,230,118);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1865 1556" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(86.4291,0,0,86.4291,-901.44,-432.146)">
<path d="M32,9L32,15L30,15L30,9L32,9ZM32,17L32,19L30,19L30,17L32,17ZM28.048,9.158C26.155,8.331 24.097,7.895 22,7.895C18.449,7.895 15.011,9.146 12.291,11.429L10.43,9.211C13.672,6.491 17.768,5 22,5C24.923,5 27.782,5.712 30.339,7.048L30,7.048C28.922,7.048 28.048,7.922 28.048,9L28.048,9.158ZM28.082,15.362C26.355,13.988 24.212,13.237 22,13.237C19.705,13.237 17.483,14.045 15.724,15.521L14.219,13.727C16.399,11.897 19.154,10.895 22,10.895C24.135,10.895 26.22,11.459 28.048,12.514L28.048,15C28.048,15.124 28.06,15.245 28.082,15.362ZM25.891,18.363L22,23L18.109,18.363C19.2,17.449 20.577,16.947 22,16.947C23.423,16.947 24.8,17.449 25.891,18.363Z" style="fill:rgb(221,44,0);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="#" />
<link rel="stylesheet" href="./style.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xz/fonts@1/serve/metropolis.min.css" />
</head>
<body>
<div id="refreshContainer">Refreshed</div>
<div id="instructionsContainer">
<div id="streamerbotConnectInstructions" style="display: none;">
<div style="padding: 10px 20px;">
Please connect to Streamer.bot.
<br>
Click <a class="hyperlinks" href="https://nuttylmao.notion.site/Multistream-Title-Updater-e2fb7b24c7514f9cbd943729f54be1d9" target="_blank">here</a> for instructions.
</div>
</div>
<div id="missingActionsInstructions" style="display: none; ">
<div style="padding: 10px 20px;">
Streamer.bot is connected, but you are missing actions.
Please import <label style="color: #ffb700;">multistream-title-updater.nut</label> and refresh the page.
Click <a class="hyperlinks" href="https://nuttylmao.notion.site/Multistream-Title-Updater-e2fb7b24c7514f9cbd943729f54be1d9" target="_blank">here</a> for instructions.
</div>
</div>
</div>
<div id="mainContainer">
<div class="straightUpIloveCum">
<div id="title" style="font-size: 20px;">Update Titles</div>
<button id="infoIcon" style="padding: 0px 10px;">
<img src="info.svg" height="16px"/>
</button>
<img src="disconnected.svg" id="connectionStatusIcon"/>
</div>
<br/>
<div id="ThisIsWhereAllTheCoolStuffHappens">
<div>
<input type="text" id="titleInput" class="textBox"><br><br>
<div class="straightUpIloveCum">
<input type="submit" id="submitButton" class="button submitButton" value="UPDATE ALL">
<input type="submit" id="refreshButton" class="button" value="REFRESH">
</div>
</div>
<br/>
<div id="fieldsContainer">
</div>
<template id="platformTemplate">
<div class="broadcastBox">
<button id="platformIconButton" style="padding: 0px 10px;">
<img id="icon" height="20px" style="padding: 0px 10px 0px 0px;"/>
</button>
<input type="text" class="textBox platformTitle">
<input type="submit" class="button submitButton platformSubmitButton" value="UPDATE">
</div>
</template>
</div>
<div id="noYouTubeStreamsInstructions" style="display: none; text-align: center; margin: 10px; font-weight: bold; opacity: 0.5;">
<div>
⚠️ YouTube titles can only be updated after the stream has started ⚠️
<br><br>
Please start your stream to update YouTube titles
</div>
</div>
</div>
</body>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js'></script>
<script src="./script.js"></script>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2000 1556" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M1000,0C1429.31,0 1777.86,348.548 1777.86,777.862C1777.86,1207.18 1429.31,1555.72 1000,1555.72C570.686,1555.72 222.138,1207.18 222.138,777.862C222.138,348.548 570.686,0 1000,0ZM934,714.686L934,1145L865,1145L865,1216.69L1137.63,1216.69L1137.63,1145L1068.63,1145L1068.63,643L865,643L865,714.686L934,714.686ZM1000,305C943.705,305 898,350.705 898,407C898,463.295 943.705,509 1000,509C1056.3,509 1102,463.295 1102,407C1102,350.705 1056.3,305 1000,305Z" style="fill:white;"/>
</svg>

After

Width:  |  Height:  |  Size: 937 B

@@ -0,0 +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 | Update YouTube Title
+444
View File
@@ -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();
+201
View File
@@ -0,0 +1,201 @@
/* @import url('https://fonts.googleapis.com/css?family={{googleFont}}:100,200,300,400,500,600,700,800,900'); */
* {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
color: rgb(235, 235, 235);
}
body {
background-color: #1f1f1f;
}
#refreshContainer {
font-family: 'Metropolis';
font-style: italic;
font-weight: bold;
font-size: 30px;
width: 400px;
max-width: fit-content;
text-align: center;
background-color: #2FB774;
opacity: 0;
color: white;
padding: 10px;
border-radius: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#instructionsContainer {
background-color: rgb(59, 59, 59);
font-size: 12px;
font-weight: bold;
border-radius: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 2;
}
.hyperlinks {
color: rgb(79, 92, 212);
}
#mainContainer
{
text-transform: uppercase;
max-width: 500px;
margin-left: auto;
margin-right: auto;
padding: 20px;
font-size: 12px;
}
.disabled
{
pointer-events: none;
opacity: 0.3;
}
#title
{
flex-grow: 1;
font-weight: 600;
}
#connectionStatusIcon
{
height: 16px;
}
#infoIcon
{
background-color: transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
opacity: 0.5;
height: 16px;
}
#infoIcon:hover
{
opacity: 1;
cursor: pointer;
}
.textBox
{
padding: 5px 15px;
border-radius:20px;
background:rgba(255,255,255,.1);
border-width: 0;
width: 100%;
box-sizing: border-box;
}
.straightUpIloveCum
{
display: flex;
margin: 0px 0px;
align-items: center;
}
.button
{
border: none;
padding: 5px 15px;
border-radius:25px;
}
.button:hover
{
box-shadow: 0 0px 10px 0 rgb(255, 255, 255, 0.3);
cursor: pointer;
}
.submitButton
{
background:rgba(17, 97, 238, 0.2);
}
.submitButton:hover{
background-color: rgba(17, 97, 238, 1);
}
#refreshButton
{
background:rgba(255,255,255,.1);
margin: 0px 0px 0px 10px;
}
#refreshButton:hover{
background:rgba(255,255,255,.5);
}
#fieldsContainer
{
width: 100%;
}
.broadcastBox
{
display: flex;
align-items: center;
margin: 10px 0px;
}
.platformTitle
{
flex-grow: 1;
}
.platformSubmitButton
{
width: 100px;
margin: 0px 0px 0px 10px;
}
#platformIconButton
{
background-color: transparent;
background-repeat: no-repeat;
border: none;
overflow: hidden;
outline: none;
opacity: 0.5;
height: 24px;
}
#platformIconButton:hover
{
opacity: 1;
cursor: pointer;
}
.twitch
{
background: rgb(110, 85, 171, 0.5);
}
.twitch:hover
{
background: rgb(110, 85, 171, 1);
}
.youtube
{
background: rgb(255, 3, 3, 0.5);
}
.youtube:hover
{
background: rgb(255, 3, 3, 1);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB