mirror of
https://github.com/nuttylmao/nutty.gg.git
synced 2026-09-19 04:00:59 -04:00
546 lines
20 KiB
JavaScript
546 lines
20 KiB
JavaScript
//////////////////////
|
|
// 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 = '';
|
|
let runningActionId = '';
|
|
|
|
|
|
///////////////////
|
|
// 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) => {
|
|
console.debug(response.data);
|
|
GeneralCustom(response.data);
|
|
})
|
|
|
|
|
|
|
|
///////////////////////////////
|
|
// MULTISTREAM TITLE UPDATER //
|
|
///////////////////////////////
|
|
|
|
async function GeneralCustom(data) {
|
|
// Only run if response matches the ID of the corresponding FetchBroadcasts() call
|
|
if (runningActionId != data.runningActionId)
|
|
return;
|
|
|
|
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)
|
|
{
|
|
// Only show the warning if there are 0 monitored broadcasts
|
|
const ytBroadcastCount = data.broadcastList.filter(b => b.platform === "youtube").length;
|
|
if (ytBroadcastCount <= 0)
|
|
youtubeWarning.style.display = 'flex';
|
|
else
|
|
youtubeWarning.style.display = 'none';
|
|
|
|
// Set the URL to the livestreaming dashboard
|
|
const broadcastButton = youtubeWarning.querySelector('#broadcast-dashboard-button');
|
|
broadcastButton.onclick = function() {
|
|
//window.open(data.streamUrl, '_blank');
|
|
OpenURL(`https://studio.youtube.com/channel/${broadcasterInfo.platforms.youtube.broadcastUserId}/livestreaming`);
|
|
};
|
|
}
|
|
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
|
|
const response = await window.parent.sbClient.doAction({ id: sbActionFetchBroadcasts});
|
|
runningActionId = response.args.runningActionId;
|
|
}
|
|
|
|
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
|
|
let instance;
|
|
|
|
if (existingDiv)
|
|
instance = existingDiv;
|
|
else {
|
|
instance = template.content.firstElementChild.cloneNode(true);
|
|
instance.id = data.id;
|
|
broadcastList.appendChild(instance);
|
|
}
|
|
|
|
// Get divs
|
|
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');
|
|
|
|
// 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)
|
|
{
|
|
data.title = response_data.livestream.session_title;
|
|
data.category = response_data.livestream.categories[0].name;
|
|
kickWarningEl.style.display = 'none';
|
|
}
|
|
else if (response_data.previous_livestreams.length > 0)
|
|
{
|
|
data.title = response_data.previous_livestreams[0].session_title;
|
|
data.category = response_data.previous_livestreams[0].categories[0].name;
|
|
kickWarningEl.style.display = 'inline';
|
|
}
|
|
else
|
|
{
|
|
data.title = '';
|
|
data.category = '';
|
|
kickWarningEl.style.display = 'inline';
|
|
}
|
|
}
|
|
|
|
// Flash green to show that it updated
|
|
if (titleEl.textContent != data.title || categoryEl.textContent != data.category)
|
|
{
|
|
instance.style.backgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--confirm-flash');
|
|
setTimeout(() => {
|
|
instance.style.backgroundColor = '';
|
|
}, 1000);
|
|
}
|
|
|
|
// Set the platform icon
|
|
platformIconEl.src = `icons/platforms/${data.platform}.png`;
|
|
|
|
// Special logo for YouTube shorts
|
|
if (data.platform == 'youtube') {
|
|
const targets = ["vertical", "shorts"];
|
|
|
|
const isShort = data.tags.some(item =>
|
|
targets.some(target => item.toLowerCase() === target.toLowerCase())
|
|
);
|
|
if (isShort)
|
|
platformIconEl.src = `icons/platforms/youtube-shorts.png`;
|
|
}
|
|
|
|
// Set the stream title
|
|
if (data.title)
|
|
titleEl.textContent = data.title;
|
|
|
|
// Set the stream category
|
|
if (data.category)
|
|
categoryEl.textContent = data.category;
|
|
|
|
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('twitch-title-input').value = data.title;
|
|
document.getElementById('twitch-category-input').value = data.category;
|
|
document.getElementById('twitch-tags-input').value = data.tags.join(", ");
|
|
ValidateTwitchDialog();
|
|
updateTwitchDialog.style.display = "flex";
|
|
break;
|
|
case 'kick':
|
|
document.getElementById('kick-title-input').value = titleEl.textContent;
|
|
document.getElementById('kick-category-input').value = categoryEl.textContent;
|
|
ValidateKickDialog();
|
|
updateKickDialog.style.display = "flex";
|
|
break;
|
|
case 'youtube':
|
|
currentBroadcastId = data.id;
|
|
document.getElementById('youtube-title-input').value = data.title;
|
|
document.getElementById('youtube-description-input').value = data.description;
|
|
document.getElementById('youtube-category-input').value = data.category;
|
|
document.getElementById('youtube-tags-input').value = data.tags.join(", ");
|
|
document.getElementById('youtube-privacy-select').value = data.privacy;
|
|
ValidateYouTubeDialog();
|
|
updateYouTubeDialog.style.display = "flex";
|
|
break;
|
|
}
|
|
blurLayer.style.display = "block";
|
|
};
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////
|
|
// 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('all-title-input').value,
|
|
category: document.getElementById('all-category-input').value
|
|
}
|
|
);
|
|
|
|
CloseUpdateAllDialog();
|
|
}
|
|
|
|
async function UpdateTwitchSubmit() {
|
|
await window.parent.sbClient.doAction(
|
|
action = {
|
|
id: sbActionUpdateStreamInfo
|
|
},
|
|
args = {
|
|
platform: 'twitch',
|
|
title: document.getElementById('twitch-title-input').value,
|
|
category: document.getElementById('twitch-category-input').value,
|
|
tags: document.getElementById('twitch-tags-input').value
|
|
}
|
|
);
|
|
|
|
CloseUpdateTwitchDialog();
|
|
}
|
|
|
|
async function UpdateKickSubmit() {
|
|
await window.parent.sbClient.doAction(
|
|
action = {
|
|
id: sbActionUpdateStreamInfo
|
|
},
|
|
args = {
|
|
platform: 'kick',
|
|
title: document.getElementById('kick-title-input').value,
|
|
category: document.getElementById('kick-category-input').value
|
|
}
|
|
);
|
|
|
|
CloseUpdateKickDialog();
|
|
}
|
|
|
|
async function UpdateYouTubeSubmit() {
|
|
await window.parent.sbClient.doAction(
|
|
action = {
|
|
id: sbActionUpdateStreamInfo
|
|
},
|
|
args = {
|
|
platform: 'youtube',
|
|
title: document.getElementById('youtube-title-input').value,
|
|
description: document.getElementById('youtube-description-input').value,
|
|
category: document.getElementById('youtube-category-input').value,
|
|
tags: document.getElementById('youtube-tags-input').value,
|
|
privacy: document.getElementById('youtube-privacy-select').value,
|
|
broadcastId: currentBroadcastId
|
|
}
|
|
);
|
|
|
|
CloseUpdateYouTubeDialog();
|
|
}
|
|
|
|
|
|
|
|
/////////////////////
|
|
// DATA VALIDATION //
|
|
/////////////////////
|
|
|
|
const GLOBAL_TITLE_MAX = 100;
|
|
const TWITCH_TITLE_MAX = 140;
|
|
const TWITCH_TAGS_MAX = 10;
|
|
const KICK_TITLE_MAX = 100;
|
|
const YOUTUBE_TITLE_MAX = 100;
|
|
const YOUTUBE_DESCRIPTION_MAX = 5000;
|
|
const YOUTUBE_TAGS_MAX = 500;
|
|
|
|
const allTitleInput = document.getElementById("all-title-input");
|
|
const twitchTitleInput = document.getElementById("twitch-title-input");
|
|
const twitchTagsInput = document.getElementById("twitch-tags-input");
|
|
const kickTitleInput = document.getElementById("kick-title-input");
|
|
const youtubeTitleInput = document.getElementById("youtube-title-input");
|
|
const youtubeDescriptionInput = document.getElementById("youtube-description-input");
|
|
const youtubeTagsInput = document.getElementById("youtube-tags-input");
|
|
|
|
// Function to update char count and handle validation
|
|
function ValidateUpdateAllDialog() {
|
|
// Get references to elements
|
|
const allTitleCharLimit = document.getElementById('all-title-char-limit');
|
|
const allCharCount = document.getElementById('all-title-char-count');
|
|
const allTitleCharMax = document.getElementById('all-title-char-max');
|
|
const allUpdateButton = document.getElementById('all-submit-button');
|
|
|
|
// Validate title field
|
|
const currentLength = allTitleInput.value.length;
|
|
allCharCount.textContent = currentLength;
|
|
allTitleCharMax.textContent = GLOBAL_TITLE_MAX;
|
|
|
|
if (currentLength > GLOBAL_TITLE_MAX)
|
|
allTitleCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
allTitleCharLimit.style.color = ""; // reset to default
|
|
|
|
// Set button interactability (yes, that is a word — look it up)
|
|
if (currentLength > GLOBAL_TITLE_MAX)
|
|
allUpdateButton.disabled = true;
|
|
else
|
|
allUpdateButton.disabled = false;
|
|
}
|
|
|
|
// Function to update char count and handle validation
|
|
function ValidateTwitchDialog() {
|
|
// Get references to elements
|
|
const twitchTitleCharLimit = document.getElementById('twitch-title-char-limit');
|
|
const twitchCharCount = document.getElementById('twitch-title-char-count');
|
|
const twitchTitleCharMax = document.getElementById('twitch-title-char-max');
|
|
const twitchTagsCharLimit = document.getElementById('twitch-tags-char-limit');
|
|
const twitchTagsCharCount = document.getElementById('twitch-tags-char-count');
|
|
const twitchTagsCharMax = document.getElementById('twitch-tags-char-max');
|
|
const twitchTagsTooLongWarning = document.getElementById('twitch-tags-too-long-warning');
|
|
const twitchUpdateButton = document.getElementById('twitch-submit-button');
|
|
|
|
// Validate title field
|
|
const currentLength = twitchTitleInput.value.length;
|
|
twitchCharCount.textContent = currentLength;
|
|
twitchTitleCharMax.textContent = TWITCH_TITLE_MAX;
|
|
|
|
if (currentLength > TWITCH_TITLE_MAX)
|
|
twitchTitleCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
twitchTitleCharLimit.style.color = ""; // reset to default
|
|
|
|
// Validate tags field
|
|
// Split by comma, trim each tag, sum lengths
|
|
const tagsArray = twitchTagsInput.value
|
|
.split(',')
|
|
.map(tag => tag.trim())
|
|
.filter(tag => tag.length > 0);
|
|
|
|
const tagsLength = tagsArray.length;
|
|
twitchTagsCharCount.textContent = tagsLength;
|
|
twitchTagsCharMax.textContent = TWITCH_TAGS_MAX;
|
|
|
|
if (tagsLength > TWITCH_TAGS_MAX)
|
|
twitchTagsCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
twitchTagsCharLimit.style.color = ""; // reset to default
|
|
|
|
// Also check that each tag is under 25 characters
|
|
const hasLongTag = tagsArray.some(tag => tag.length > 25);
|
|
if (hasLongTag) {
|
|
twitchTagsTooLongWarning.style.display = 'inline';
|
|
twitchTagsCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
}
|
|
else {
|
|
twitchTagsTooLongWarning.style.display = 'none';
|
|
twitchTagsCharLimit.style.color = ""; // reset to default
|
|
}
|
|
|
|
// Set button interactability (yes, that is a word — look it up)
|
|
if (currentLength > TWITCH_TITLE_MAX || tagsLength > TWITCH_TAGS_MAX || hasLongTag)
|
|
twitchUpdateButton.disabled = true;
|
|
else
|
|
twitchUpdateButton.disabled = false;
|
|
}
|
|
|
|
// Function to update char count and handle validation
|
|
function ValidateKickDialog() {
|
|
// Get references to elements
|
|
const kickTitleCharLimit = document.getElementById('kick-title-char-limit');
|
|
const kickCharCount = document.getElementById('kick-title-char-count');
|
|
const kickTitleCharMax = document.getElementById('kick-title-char-max');
|
|
const kickUpdateButton = document.getElementById('kick-submit-button');
|
|
|
|
// Validate title field
|
|
const currentLength = kickTitleInput.value.length;
|
|
kickCharCount.textContent = currentLength;
|
|
kickTitleCharMax.textContent = KICK_TITLE_MAX;
|
|
|
|
if (currentLength > KICK_TITLE_MAX)
|
|
kickTitleCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
kickTitleCharLimit.style.color = ""; // reset to default
|
|
|
|
// Set button interactability (yes, that is a word — look it up)
|
|
if (currentLength > KICK_TITLE_MAX)
|
|
kickUpdateButton.disabled = true;
|
|
else
|
|
kickUpdateButton.disabled = false;
|
|
}
|
|
|
|
function ValidateYouTubeDialog() {
|
|
// Get references to elements
|
|
const youtubeTitleCharLimit = document.getElementById('youtube-title-char-limit');
|
|
const youtubeTitleCharCount = document.getElementById('youtube-title-char-count');
|
|
const youtubeTitleCharMax = document.getElementById('youtube-title-char-max');
|
|
const youtubeDescriptionCharLimit = document.getElementById('youtube-description-char-limit');
|
|
const youtubeDescriptionCharCount = document.getElementById('youtube-description-char-count');
|
|
const youtubeDescriptionCharMax = document.getElementById('youtube-description-char-max');
|
|
const youtubeTagsCharLimit = document.getElementById('youtube-tags-char-limit');
|
|
const youtubeTagsCharCount = document.getElementById('youtube-tags-char-count');
|
|
const youtubeTagsCharMax = document.getElementById('youtube-tags-char-max');
|
|
const youtubeUpdateButton = document.getElementById('youtube-submit-button');
|
|
|
|
// Validate title field
|
|
const titleLength = youtubeTitleInput.value.length;
|
|
youtubeTitleCharCount.textContent = titleLength;
|
|
youtubeTitleCharMax.textContent = YOUTUBE_TITLE_MAX;
|
|
|
|
if (titleLength > YOUTUBE_TITLE_MAX)
|
|
youtubeTitleCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
youtubeTitleCharLimit.style.color = ""; // reset to default
|
|
|
|
// Validate description field
|
|
const descriptionLength = youtubeDescriptionInput.value.length;
|
|
youtubeDescriptionCharCount.textContent = descriptionLength;
|
|
youtubeDescriptionCharMax.textContent = YOUTUBE_DESCRIPTION_MAX;
|
|
|
|
if (descriptionLength > YOUTUBE_DESCRIPTION_MAX)
|
|
youtubeDescriptionCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
youtubeDescriptionCharLimit.style.color = ""; // reset to default
|
|
|
|
// Validate tags field
|
|
// Split by comma, trim each tag, sum lengths
|
|
const tagsArray = youtubeTagsInput.value
|
|
.split(',')
|
|
.map(tag => tag.trim())
|
|
.filter(tag => tag.length > 0);
|
|
|
|
const tagsLength = tagsArray.reduce((sum, tag) => sum + tag.length, 0) + (tagsArray.length > 0 ? tagsArray.length - 1 : 0);
|
|
youtubeTagsCharCount.textContent = tagsLength;
|
|
youtubeTagsCharMax.textContent = YOUTUBE_TAGS_MAX;
|
|
|
|
if (tagsLength > YOUTUBE_TAGS_MAX)
|
|
youtubeTagsCharLimit.style.color = getComputedStyle(document.documentElement).getPropertyValue('--error-text-color');
|
|
else
|
|
youtubeTagsCharLimit.style.color = ""; // reset to default
|
|
|
|
// Set button interactability (yes, that is a word — look it up)
|
|
if (titleLength > YOUTUBE_TITLE_MAX || descriptionLength > YOUTUBE_DESCRIPTION_MAX || tagsLength > YOUTUBE_TAGS_MAX)
|
|
youtubeUpdateButton.disabled = true;
|
|
else
|
|
youtubeUpdateButton.disabled = false;
|
|
}
|
|
|
|
// Attach event listener
|
|
allTitleInput.addEventListener("input", ValidateUpdateAllDialog);
|
|
twitchTitleInput.addEventListener("input", ValidateTwitchDialog);
|
|
twitchTagsInput.addEventListener("input", ValidateTwitchDialog);
|
|
kickTitleInput.addEventListener("input", ValidateKickDialog);
|
|
youtubeTitleInput.addEventListener("input", ValidateYouTubeDialog);
|
|
youtubeDescriptionInput.addEventListener("input", ValidateYouTubeDialog);
|
|
youtubeTagsInput.addEventListener("input", ValidateYouTubeDialog);
|
|
|
|
// Initial check (in case the input has prefilled text)
|
|
ValidateUpdateAllDialog();
|
|
ValidateTwitchDialog();
|
|
ValidateKickDialog();
|
|
ValidateYouTubeDialog();
|
|
|
|
|
|
|
|
|
|
////////////////////////////
|
|
// REFRESH BROADCAST LIST //
|
|
////////////////////////////
|
|
|
|
setInterval(FetchBroadcasts, 5000); |