Re-release of Multistream Title Updater
Minor Printer.bot improvements
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../../.common/styles/global.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Update All Button -->
|
||||
<button onclick="OpenUpdateAllDialog()">
|
||||
<img src="https://api.iconify.design/material-symbols:edit-square-outline.svg?color=%23ffffff"> Update All
|
||||
</button>
|
||||
|
||||
<!-- List of all broadcasts -->
|
||||
<div id="broadcast-list">
|
||||
</div>
|
||||
|
||||
<label id="youtube-warning" class="setting-attribute"><i>YouTube stream info can only be updated while live</i></label>
|
||||
|
||||
<!-- Blur Layer -->
|
||||
<div id="blur-layer" class="blur"></div>
|
||||
|
||||
<!-- Update All Popup -->
|
||||
<div id="update-all-dialog" class="dialog">
|
||||
<button class="dialog-nav-button" onclick="CloseUpdateAllDialog()">×</button>
|
||||
|
||||
<label class="title" style="text-align: center;">Stream Info</label>
|
||||
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input type="text" id="update-all-title-input" placeholder="My First Stream #RoadToAffiliate">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Category</label>
|
||||
<input type="text" id="update-all-category-input" placeholder="Just Chatting">
|
||||
</div>
|
||||
|
||||
<button onclick="UpdateAllSubmit()">Update All</button>
|
||||
</div>
|
||||
|
||||
<!-- Update Twitch Popup -->
|
||||
<div id="update-twitch-dialog" class="dialog">
|
||||
<button class="dialog-nav-button" onclick="CloseUpdateTwitchDialog()">×</button>
|
||||
|
||||
<div style="display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 1em;">
|
||||
<img src="icons/platforms/twitch.png" class="platform-icon">
|
||||
<label class="title" style="text-align: center;">Stream Info</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input type="text" id="update-twitch-title-input" placeholder="My First Stream #RoadToAffiliate">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Category</label>
|
||||
<input type="text" id="update-twitch-category-input" placeholder="Just Chatting">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Tags</label>
|
||||
<input type="text" id="update-twitch-tags-input" placeholder="Retro, Speedrun, CreamTeam">
|
||||
</div>
|
||||
|
||||
<button onclick="UpdateTwitchSubmit()">Update</button>
|
||||
</div>
|
||||
|
||||
<!-- Update Kick Popup -->
|
||||
<div id="update-kick-dialog" class="dialog">
|
||||
<button class="dialog-nav-button" onclick="CloseUpdateKickDialog()">×</button>
|
||||
|
||||
<div style="display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 1em;">
|
||||
<img src="icons/platforms/kick.png" class="platform-icon">
|
||||
<label class="title" style="text-align: center;">Stream Info</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input type="text" id="update-kick-title-input" placeholder="My First Stream #RoadToAffiliate">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Category</label>
|
||||
<input type="text" id="update-kick-category-input" placeholder="Just Chatting">
|
||||
</div>
|
||||
|
||||
<button onclick="UpdateKickSubmit()">Update</button>
|
||||
</div>
|
||||
|
||||
<!-- Update YouTube Popup -->
|
||||
<div id="update-youtube-dialog" class="dialog">
|
||||
<button class="dialog-nav-button" onclick="CloseUpdateYouTubeDialog()">×</button>
|
||||
|
||||
<div style="display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 1em;">
|
||||
<img src="icons/platforms/youtube.png" class="platform-icon">
|
||||
<label class="title" style="text-align: center;">Stream Info</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input type="text" id="update-youtube-title-input" placeholder="My First Stream #RoadToAffiliate">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<textarea type="text" id="update-youtube-description-input" placeholder="I'm describing so hard right now..." class="textarea-description"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Category</label>
|
||||
<input type="text" id="update-youtube-category-input" placeholder="Gaming">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Tags</label>
|
||||
<input type="text" id="update-youtube-tags-input" placeholder="Retro, Speedrun, CreamTeam">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Visibility</label>
|
||||
<select id="update-youtube-privacy-select">
|
||||
<option value="private">Private</option>
|
||||
<option value="unlisted">Unlisted</option>
|
||||
<option value="public">Public</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button onclick="UpdateYouTubeSubmit()">Update</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<!-- Broadcast Template -->
|
||||
<template id="broadcast-template">
|
||||
<div class="broadcast">
|
||||
<img class="platform-icon" id="platform-icon" src="icons/platforms/twitch.png"/>
|
||||
|
||||
<div class="broadcast-info">
|
||||
<label id="broadcast-title"></label>
|
||||
<label id="broadcast-category" class="setting-description"></label>
|
||||
<label id="kick-warning" class="setting-attribute"><br><i>Current stream info only available when live — Showing last known stream title</i></label>
|
||||
</div>
|
||||
|
||||
<div id="broadcast-buttons">
|
||||
<button id="broadcast-stream-button" class="icon-button" title="Open Stream"><img class="button-icon" src="https://api.iconify.design/streamline:button-play-solid.svg?color=%23ffffff"></button>
|
||||
<button id="broadcast-dashboard-button" class="icon-button" title="Dashboard"><img class="button-icon" src="https://api.iconify.design/gridicons:popout.svg?color=%23ffffff"></button>
|
||||
<button id="broadcast-edit-button" class="icon-button flip-that-shit-homie" title="Edit">✎</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="script.js"></script>
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||