sync from github
This commit is contained in:
863
js/settings.js
863
js/settings.js
@@ -1,452 +1,499 @@
|
||||
let streamerBotClient;
|
||||
let streamerBotConnected = false;
|
||||
let kickWebSocket = null;
|
||||
let tikfinityWebSocket = null;
|
||||
let speakerBotClient = null;
|
||||
|
||||
async function saveSettingsToLocalStorage() {
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const colorfields = document.querySelectorAll("input[type=color]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
|
||||
const hiddenField = document.querySelector("textarea[name=youTubeCustomEmotes]:not(.avoid)");
|
||||
/* -------------------------
|
||||
Salvar configurações no localStorage
|
||||
-------------------------- */
|
||||
function saveSettingsToLocalStorage() {
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const colorfields = document.querySelectorAll("input[type=color]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
const ranges = document.querySelectorAll("input[type=range]:not(.avoid)");
|
||||
const settings = {};
|
||||
|
||||
const ranges = document.querySelectorAll("input[type=range]:not(.avoid)");
|
||||
checkboxes.forEach(cb => settings[cb.name] = cb.checked);
|
||||
ranges.forEach(r => settings[r.name] = r.value);
|
||||
textfields.forEach(tf => settings[tf.name] = tf.value);
|
||||
numberfields.forEach(nf => settings[nf.name] = nf.value);
|
||||
colorfields.forEach(cf => settings[cf.name] = cf.value);
|
||||
selects.forEach(s => settings[s.name] = s.value);
|
||||
|
||||
const settings = {};
|
||||
localStorage.setItem("chatrdWidgetSettings", JSON.stringify(settings));
|
||||
|
||||
checkboxes.forEach((checkbox) => {
|
||||
settings[checkbox.name] = checkbox.checked;
|
||||
});
|
||||
ranges.forEach((range) => {
|
||||
settings[range.name] = range.value;
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
settings[textfield.name] = textfield.value;
|
||||
});
|
||||
numberfields.forEach((numberfield) => {
|
||||
settings[numberfield.name] = numberfield.value;
|
||||
});
|
||||
colorfields.forEach((colorfield) => {
|
||||
settings[colorfield.name] = colorfield.value;
|
||||
});
|
||||
selects.forEach((select) => {
|
||||
settings[select.name] = select.value;
|
||||
});
|
||||
|
||||
localStorage.setItem("chatWidgetSettings", JSON.stringify(settings));
|
||||
|
||||
if (streamerBotConnected == true) {
|
||||
streamerBotClient.doAction(
|
||||
{ name : "YouTube Custom Emotes" },
|
||||
{
|
||||
"chatrdytcustomemotes": JSON.stringify(hiddenField.value.trim()),
|
||||
}
|
||||
).then( (setglobals) => {
|
||||
console.debug('Saving YouTube Emotes from Streamer.Bot', setglobals);
|
||||
});
|
||||
}
|
||||
// Salva emotes no Streamer.bot
|
||||
try {
|
||||
const youtubeMemberEmotes = document.querySelector("textarea[name=youTubeCustomEmotes]:not(.avoid)");
|
||||
youtubeSaveMemberEmotes(JSON.parse(youtubeMemberEmotes.value));
|
||||
}
|
||||
catch (err) {
|
||||
console.error("[ChatRD] Emotes JSON inválido", err);
|
||||
}
|
||||
|
||||
generateUrl();
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------
|
||||
Carregar configurações do localStorage
|
||||
-------------------------- */
|
||||
async function loadSettingsFromLocalStorage() {
|
||||
const saved = localStorage.getItem("chatWidgetSettings");
|
||||
if (!saved) return;
|
||||
const saved = localStorage.getItem("chatrdWidgetSettings");
|
||||
if (!saved) return;
|
||||
|
||||
const settings = JSON.parse(saved);
|
||||
console.log(settings);
|
||||
const settings = JSON.parse(saved);
|
||||
|
||||
Object.keys(settings).forEach((key) => {
|
||||
const input = document.querySelector(`[name="${key}"]`);
|
||||
if (input) {
|
||||
if (input.type === "checkbox") {
|
||||
input.checked = settings[key];
|
||||
}
|
||||
else {
|
||||
input.value = settings[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
Object.keys(settings).forEach(key => {
|
||||
const input = document.querySelector(`[name="${key}"]`);
|
||||
if (input) {
|
||||
if (input.type === "checkbox") {
|
||||
input.checked = settings[key];
|
||||
} else {
|
||||
input.value = settings[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#font-value').textContent = Math.floor(document.querySelector('#font-slider').value * 100) + '%';
|
||||
document.querySelector('#bg-opacity-value').textContent = Math.floor(document.querySelector('#bg-opacity-slider').value * 100) + '%';
|
||||
|
||||
document.querySelector('#font-value').textContent = Math.floor(document.querySelector('#font-slider').value * 100) + '%';
|
||||
youtubeLoadMemberEmotes().then(settings => {
|
||||
if (settings) {
|
||||
const youtubeMemberEmotes = document.querySelector("textarea[name=youTubeCustomEmotes]:not(.avoid)");
|
||||
console.log('[ChatRD][Settings] YouTube Member Emotes Loaded', settings);
|
||||
youtubeMemberEmotes.value = JSON.stringify(settings);
|
||||
populateEmoteList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Configurar eventos para salvar mudanças
|
||||
-------------------------- */
|
||||
function pushChangeEvents() {
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const colorfields = document.querySelectorAll("input[type=color]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
const ranges = document.querySelectorAll("input[type=range]:not(.avoid)");
|
||||
|
||||
var streamerBotServerAddress = document.querySelector('input[type=text][name=streamerBotServerAddress]').value;
|
||||
var streamerBotServerPort = document.querySelector('input[type=text][name=streamerBotServerPort]').value;
|
||||
[...checkboxes, ...textfields, ...numberfields, ...colorfields, ...selects, ...ranges].forEach(el => {
|
||||
el.addEventListener('change', saveSettingsToLocalStorage);
|
||||
el.addEventListener('input', saveSettingsToLocalStorage);
|
||||
});
|
||||
|
||||
streamerBotClient = new StreamerbotClient({
|
||||
host: streamerBotServerAddress,
|
||||
port: streamerBotServerPort,
|
||||
onConnect: (data) => {
|
||||
streamerBotConnected = true;
|
||||
document.querySelector('#font-slider').addEventListener('input', function () {
|
||||
document.querySelector('#font-value').textContent = Math.floor(this.value * 100) + '%';
|
||||
});
|
||||
|
||||
document.querySelector('#memberemotesbstatus').classList.remove('offline');
|
||||
document.querySelector('#memberemotesbstatus').classList.add('online');
|
||||
document.querySelector('#memberemotesbstatus span').textContent = 'Streamer.Bot is Online!';
|
||||
document.querySelector('#bg-opacity-slider').addEventListener('input', function () {
|
||||
document.querySelector('#bg-opacity-value').textContent = Math.floor(this.value * 100) + '%';
|
||||
});
|
||||
}
|
||||
|
||||
streamerBotClient.getGlobals().then( (getglobals) => {
|
||||
const settings = JSON.parse(getglobals.variables.chatrdytcustomemotes.value);
|
||||
console.debug('Getting YouTube Emotes from Streamer.Bot', settings);
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
textarea.value = settings;
|
||||
|
||||
populateEmoteList();
|
||||
});
|
||||
|
||||
},
|
||||
onDisconnect: () => {
|
||||
console.error('Streamer.bot Disconnected!');
|
||||
|
||||
streamerBotConnected = false;
|
||||
|
||||
document.querySelector('#memberemotesbstatus').classList.remove('online');
|
||||
document.querySelector('#memberemotesbstatus').classList.add('offline');
|
||||
document.querySelector('#memberemotesbstatus span').textContent = 'Streamer.Bot is Offline!';
|
||||
}
|
||||
});
|
||||
/* -------------------------
|
||||
Gerar URL de preview
|
||||
-------------------------- */
|
||||
function generateUrl() {
|
||||
const outputField = document.getElementById("outputUrl");
|
||||
outputField.value = '';
|
||||
|
||||
const baseUrlObj = new URL(window.location.href);
|
||||
baseUrlObj.pathname = baseUrlObj.pathname.replace(/index\.html$/, "chat.html");
|
||||
const baseUrl = baseUrlObj.toString();
|
||||
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const colorfields = document.querySelectorAll("input[type=color]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
const ranges = document.querySelectorAll("input[type=range]:not(.avoid)");
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
selects.forEach(s => params.set(s.name, s.value));
|
||||
ranges.forEach(r => params.set(r.name, r.value));
|
||||
checkboxes.forEach(cb => params.set(cb.name, cb.checked));
|
||||
colorfields.forEach(cf => params.set(cf.name, cf.value));
|
||||
textfields.forEach(tf => params.set(tf.name, tf.value));
|
||||
numberfields.forEach(nf => params.set(nf.name, nf.value));
|
||||
|
||||
outputField.value = baseUrl + '?' + params.toString();
|
||||
document.querySelector('#preview iframe').src = 'chat.html?' + params.toString();
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Copiar URL para clipboard
|
||||
-------------------------- */
|
||||
function copyUrl() {
|
||||
const output = document.getElementById("outputUrl");
|
||||
const value = output.value;
|
||||
const button = document.querySelector('.url-bar button');
|
||||
const buttonDefaultText = 'Copy URL';
|
||||
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
button.textContent = 'ChatRD URL Copied!';
|
||||
button.style.backgroundColor = "#00dd63";
|
||||
|
||||
setTimeout(() => {
|
||||
button.textContent = buttonDefaultText;
|
||||
button.removeAttribute('style');
|
||||
}, 3000);
|
||||
}).catch(err => {
|
||||
console.error("Failed to copy: ", err);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Mostrar/esconder plataformas
|
||||
-------------------------- */
|
||||
function setupPlatformToggles() {
|
||||
const platforms = document.querySelectorAll('.platform');
|
||||
|
||||
platforms.forEach(platform => {
|
||||
const platformId = platform.id;
|
||||
const toggleName = `show${capitalize(platformId)}`;
|
||||
const toggle = platform.querySelector(`input[name="${toggleName}"]`);
|
||||
const setupDiv = platform.querySelector('.setup');
|
||||
|
||||
if (toggle && setupDiv) {
|
||||
// Removido: initializeTransitionStyles(setupDiv);
|
||||
|
||||
// Defina o overflow no CSS ou aqui, se preferir
|
||||
setupDiv.style.overflow = 'hidden';
|
||||
setupDiv.style.transition = 'max-height 0.4s ease, opacity 0.4s ease';
|
||||
|
||||
setVisible(setupDiv, toggle.checked);
|
||||
|
||||
toggle.removeEventListener('change', toggle._handler || (() => { }));
|
||||
|
||||
const handler = () => setVisible(setupDiv, toggle.checked);
|
||||
toggle._handler = handler;
|
||||
toggle.addEventListener('change', handler);
|
||||
}
|
||||
});
|
||||
|
||||
function setVisible(element, visible) {
|
||||
if (visible) {
|
||||
// Remove 'display: none' para que a altura possa ser calculada
|
||||
element.style.display = 'block';
|
||||
|
||||
// Força o elemento a iniciar com altura e opacidade zero
|
||||
element.style.maxHeight = '0px';
|
||||
element.style.opacity = '0';
|
||||
element.offsetHeight; // Força a renderização
|
||||
|
||||
// Inicia a transição para a altura real e opacidade completa
|
||||
element.style.maxHeight = element.scrollHeight + 'px';
|
||||
element.style.opacity = '1';
|
||||
|
||||
// Remove os estilos após a transição de abertura
|
||||
element.addEventListener('transitionend', function handler() {
|
||||
element.style.maxHeight = null;
|
||||
element.style.opacity = null;
|
||||
element.removeEventListener('transitionend', handler);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
// Define o maxHeight para a altura atual antes de iniciar a transição de fechamento
|
||||
element.style.maxHeight = element.scrollHeight + 'px';
|
||||
element.offsetHeight; // Força a renderização
|
||||
|
||||
// Inicia a transição para fechar o elemento
|
||||
element.style.maxHeight = '0px';
|
||||
element.style.opacity = '0';
|
||||
|
||||
// Esconde o elemento com 'display: none' após a transição
|
||||
setTimeout(() => {
|
||||
if (element.style.opacity === '0') {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}, 400); // O tempo precisa ser o mesmo da transição (0.4s)
|
||||
}
|
||||
}
|
||||
|
||||
// A função initializeTransitionStyles() foi removida.
|
||||
// O estilo de transição foi movido para a função principal para ser definido uma única vez.
|
||||
|
||||
function capitalize(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Navegação no footer
|
||||
-------------------------- */
|
||||
function setupFooterNavBar() {
|
||||
document.querySelectorAll('.nav-bar a').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const targetId = this.getAttribute('href');
|
||||
if (!targetId || !targetId.startsWith('#')) return;
|
||||
|
||||
const targetElement = document.querySelector(targetId);
|
||||
if (targetElement) {
|
||||
const offset = 20;
|
||||
const y = targetElement.getBoundingClientRect().top + window.scrollY - offset;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Modal para adicionar emotes
|
||||
-------------------------- */
|
||||
function setupAddEmoteModal() {
|
||||
const modal = document.getElementById("addEmoteModal");
|
||||
const nameInput = document.getElementById("newEmoteName");
|
||||
const urlInput = document.getElementById("newEmoteURL");
|
||||
const confirmBtn = document.getElementById("confirmAddEmote");
|
||||
const cancelBtn = document.getElementById("cancelAddEmote");
|
||||
const addButton = document.querySelector("#addEmoteButton");
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
|
||||
if (!modal || !addButton || !textarea) return;
|
||||
|
||||
// ESC global → aciona cancelBtn
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && !modal.classList.contains("hidden")) {
|
||||
cancelBtn.click();
|
||||
}
|
||||
});
|
||||
|
||||
// ENTER nos inputs → aciona confirmBtn
|
||||
[nameInput, urlInput].forEach(input => {
|
||||
input.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault(); // evita submit/form
|
||||
confirmBtn.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addButton.onclick = (event) => {
|
||||
event.preventDefault();
|
||||
if (streamerBotConnected) {
|
||||
nameInput.value = "";
|
||||
urlInput.value = "";
|
||||
modal.classList.remove("hidden");
|
||||
nameInput.focus();
|
||||
} else {
|
||||
alert("Streamer.bot is Offline!");
|
||||
}
|
||||
};
|
||||
|
||||
cancelBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
modal.classList.add("hidden");
|
||||
};
|
||||
|
||||
confirmBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const name = nameInput.value.trim();
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
if (!name || !url) {
|
||||
alert("Both fields are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
let emotes;
|
||||
try {
|
||||
emotes = JSON.parse(textarea.value);
|
||||
} catch (err) {
|
||||
console.error("Invalid JSON", err);
|
||||
alert("Emote data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (emotes[name]) {
|
||||
alert(`Emote "${name}" already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
emotes[name] = url;
|
||||
textarea.value = JSON.stringify(emotes);
|
||||
modal.classList.add("hidden");
|
||||
populateEmoteList();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function pushChangeEvents() {
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const colorfields = document.querySelectorAll("input[type=color]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
/* -------------------------
|
||||
Lista de emotes
|
||||
-------------------------- */
|
||||
function populateEmoteList() {
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
const emoteList = document.querySelector("#youtube .emote-list");
|
||||
if (!textarea || !emoteList) return;
|
||||
|
||||
const ranges = document.querySelectorAll("input[type=range]:not(.avoid)");
|
||||
emoteList.querySelectorAll(".emote-item").forEach(item => {
|
||||
if (item.querySelector("button")?.id !== "addEmoteButton") {
|
||||
item.remove();
|
||||
}
|
||||
});
|
||||
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
textfield.addEventListener('input', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
numberfields.forEach((numberfield) => {
|
||||
numberfield.addEventListener('input', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
colorfields.forEach((colorfield) => {
|
||||
colorfield.addEventListener('change', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
selects.forEach((select) => {
|
||||
select.addEventListener('change', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
textfield.addEventListener('input', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
let emotes;
|
||||
try {
|
||||
emotes = JSON.parse(textarea.value);
|
||||
} catch (e) {
|
||||
console.error("[ChatRD][Settings] Invalid JSON in YouTube Emotes textarea", e);
|
||||
return;
|
||||
}
|
||||
|
||||
ranges.forEach((range) => {
|
||||
range.addEventListener('change', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
const addButtonSpan = emoteList.querySelector("#addEmoteButton")?.parentElement;
|
||||
|
||||
document.querySelector('#font-slider').addEventListener('input', function () {
|
||||
document.querySelector('#font-value').textContent = Math.floor(this.value * 100) + '%';
|
||||
});
|
||||
for (const [emoteName, emoteUrl] of Object.entries(emotes)) {
|
||||
const span = document.createElement("span");
|
||||
span.classList.add("emote-item");
|
||||
span.innerHTML = `
|
||||
<img data-emote="${emoteName}" src="${emoteUrl}" alt="">
|
||||
<em>${emoteName}</em>
|
||||
<button class="delete"><i class="fa-solid fa-trash-can"></i></button>
|
||||
`;
|
||||
|
||||
document.querySelector('#bg-opacity-slider').addEventListener('input', function () {
|
||||
document.querySelector('#bg-opacity-value').textContent = this.value;
|
||||
});
|
||||
span.querySelector(".delete").addEventListener("click", () => {
|
||||
if (confirm(`Are you sure you want to delete '${emoteName}'?`)) {
|
||||
delete emotes[emoteName];
|
||||
textarea.value = JSON.stringify(emotes);
|
||||
populateEmoteList();
|
||||
}
|
||||
});
|
||||
|
||||
emoteList.insertBefore(span, addButtonSpan || null);
|
||||
}
|
||||
|
||||
saveSettingsToLocalStorage();
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Funções YouTube <-> Streamer.bot
|
||||
-------------------------- */
|
||||
function youtubeSaveMemberEmotes(data) {
|
||||
if (!streamerBotClient) return;
|
||||
const json = JSON.stringify(data);
|
||||
streamerBotClient.doAction({ name: "[YouTube] Member Emotes" }, {
|
||||
"chatrdytcustomemotes": json,
|
||||
}).then((res) => {
|
||||
console.debug('[ChatRD][Settings] Saving YouTube Member Emotes... ', res);
|
||||
});
|
||||
}
|
||||
|
||||
function youtubeLoadMemberEmotes() {
|
||||
if (!streamerBotClient) return Promise.resolve(null);
|
||||
return streamerBotClient.getGlobals().then((globals) => {
|
||||
console.debug('[ChatRD][Settings] Loading Global Vars...', globals);
|
||||
const emoteglobal = globals.variables?.chatrdytcustomemotes;
|
||||
if (!emoteglobal) {
|
||||
console.warn('[ChatRD][Settings] Global variable "chatrdytcustomemotes" not found.');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(emoteglobal.value);
|
||||
} catch (e) {
|
||||
console.error('[ChatRD][Settings] Failed to parse YouTube Member Emote JSON', e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Conexão com Streamer.bot
|
||||
-------------------------- */
|
||||
function streamerBotConnect() {
|
||||
const streamerBotStatus = document.getElementById('streamerBotStatus');
|
||||
|
||||
const streamerBotServerAddress = document.querySelector('input[type=text][name=streamerBotServerAddress]').value;
|
||||
const streamerBotServerPort = document.querySelector('input[type=text][name=streamerBotServerPort]').value;
|
||||
|
||||
streamerBotClient = new StreamerbotClient({
|
||||
host: streamerBotServerAddress,
|
||||
port: streamerBotServerPort,
|
||||
onConnect: () => {
|
||||
console.debug(`[ChatRD][Settings] Connected to Streamer.bot successfully!`);
|
||||
streamerBotConnected = true;
|
||||
|
||||
streamerBotStatus.classList.add('connected');
|
||||
streamerBotStatus.querySelector('small').textContent = `Connected`;
|
||||
|
||||
loadSettingsFromLocalStorage();
|
||||
generateUrl();
|
||||
pushChangeEvents();
|
||||
setupFooterNavBar();
|
||||
setupAddEmoteModal();
|
||||
setupPlatformToggles();
|
||||
|
||||
},
|
||||
onDisconnect: () => {
|
||||
streamerBotStatus.classList.remove('connected');
|
||||
streamerBotStatus.querySelector('small').textContent = `Awaiting for connection`;
|
||||
streamerBotConnected = false;
|
||||
console.debug(`[ChatRD][Settings] Streamer.bot Disconnected!`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function generateUrl() {
|
||||
document.getElementById("outputUrl").value = '';
|
||||
async function speakerBotConnection() {
|
||||
const speakerBotStatus = document.getElementById('speakerBotStatus');
|
||||
|
||||
|
||||
var baseUrl = 'https://vortisrd.github.io/chatrd/chat.html';
|
||||
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const colorfields = document.querySelectorAll("input[type=color]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
const speakerBotServerAddress = document.querySelector('input[type=text][name=speakerBotServerAddress]').value;
|
||||
const speakerBotServerPort = document.querySelector('input[type=text][name=speakerBotServerPort]').value;
|
||||
const speakerBotVoiceAlias = document.querySelector('input[type=text][name=speakerBotVoiceAlias]').value;
|
||||
|
||||
const ranges = document.querySelectorAll("input[type=range]:not(.avoid)");
|
||||
const showSpeakerbot = document.querySelector('input[type=checkbox][name=showSpeakerbot]').checked;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
selects.forEach((select) => {
|
||||
params.set(select.name, select.value);
|
||||
});
|
||||
ranges.forEach((range) => {
|
||||
params.set(range.name, range.value);
|
||||
});
|
||||
checkboxes.forEach((checkbox) => {
|
||||
params.set(checkbox.name, checkbox.checked);
|
||||
});
|
||||
colorfields.forEach((colorfield) => {
|
||||
params.set(colorfield.name, colorfield.value);
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
params.set(textfield.name, textfield.value);
|
||||
});
|
||||
numberfields.forEach((numberfield) => {
|
||||
params.set(numberfield.name, numberfield.value);
|
||||
});
|
||||
if (!showSpeakerbot) {
|
||||
// Se não é pra mostrar, desconecta caso esteja ativo
|
||||
if (speakerBotClient && speakerBotClient.ws && speakerBotClient.ws.readyState !== WebSocket.CLOSED) {
|
||||
console.log("[ChatRD][Settings] Disconnecting SpeakerBot...");
|
||||
speakerBotClient.disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("outputUrl").value = baseUrl + '?' + params.toString();
|
||||
document.querySelector('#chat-preview iframe').src = 'chat.html?'+params.toString();
|
||||
}
|
||||
// Se já está conectado ou conectando, não cria outro
|
||||
if (speakerBotClient && speakerBotClient.ws && speakerBotClient.ws.readyState !== WebSocket.CLOSED) {
|
||||
console.log("[ChatRD][Settings] SpeakerBot WebSocket is already on!.");
|
||||
return;
|
||||
}
|
||||
|
||||
async function copyUrl() {
|
||||
// Cria nova instância
|
||||
speakerBotClient = new SpeakerBotClient({
|
||||
host: speakerBotServerAddress,
|
||||
port: speakerBotServerPort,
|
||||
voiceAlias: speakerBotVoiceAlias,
|
||||
|
||||
const output = document.getElementById("outputUrl")
|
||||
const value = output.value;
|
||||
|
||||
const button = document.querySelector('.url-bar button');
|
||||
const buttonDefaulText = 'Copy URL';
|
||||
onConnect: () => {
|
||||
speakerBotStatus.classList.add('connected');
|
||||
speakerBotStatus.querySelector('small').textContent = `Connected`;
|
||||
},
|
||||
|
||||
navigator.clipboard.writeText(value)
|
||||
.then(() => {
|
||||
|
||||
button.textContent = 'ChatRD URL Copied!';
|
||||
button.style.backgroundColor = "#00dd63";
|
||||
|
||||
setTimeout(() => {
|
||||
button.textContent = buttonDefaulText;
|
||||
button.removeAttribute('style');
|
||||
}, 3000);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Failed to copy: ", err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function setupAddEmoteModal() {
|
||||
const modal = document.getElementById("addEmoteModal");
|
||||
const nameInput = document.getElementById("newEmoteName");
|
||||
const urlInput = document.getElementById("newEmoteURL");
|
||||
const confirmBtn = document.getElementById("confirmAddEmote");
|
||||
const cancelBtn = document.getElementById("cancelAddEmote");
|
||||
const addButton = document.querySelector("#youtube .emote-item:last-child .add");
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
|
||||
if (!modal || !addButton || !textarea) return;
|
||||
|
||||
// Show modal
|
||||
addButton.onclick = () => {
|
||||
if (streamerBotConnected == true) {
|
||||
nameInput.value = "";
|
||||
urlInput.value = "";
|
||||
modal.classList.remove("hidden");
|
||||
nameInput.focus();
|
||||
}
|
||||
else {
|
||||
alert("Streamer.bot is Offline!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel
|
||||
cancelBtn.onclick = () => {
|
||||
modal.classList.add("hidden");
|
||||
};
|
||||
|
||||
// Confirm
|
||||
confirmBtn.onclick = () => {
|
||||
const name = nameInput.value.trim();
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
if (!name || !url) {
|
||||
alert("Both fields are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
let emotes;
|
||||
try {
|
||||
emotes = JSON.parse(textarea.value);
|
||||
} catch (err) {
|
||||
console.error("Invalid JSON", err);
|
||||
alert("Emote data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (emotes[name]) {
|
||||
alert(`Emote "${name}" already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add and update
|
||||
emotes[name] = url;
|
||||
textarea.value = JSON.stringify(emotes, null, 4);
|
||||
modal.classList.add("hidden");
|
||||
populateEmoteList();
|
||||
};
|
||||
onDisconnect: () => {
|
||||
speakerBotStatus.classList.remove('connected');
|
||||
speakerBotStatus.querySelector('small').textContent = `Awaiting for connection`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function populateEmoteList() {
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
const emoteList = document.querySelector("#youtube .emote-list");
|
||||
|
||||
if (!textarea || !emoteList) return;
|
||||
|
||||
const addButtonSpan = emoteList.querySelector(".emote-item:last-child");
|
||||
|
||||
// Remove all emote items except the add button
|
||||
emoteList.querySelectorAll(".emote-item").forEach(item => {
|
||||
if (item !== addButtonSpan) {
|
||||
item.remove();
|
||||
}
|
||||
});
|
||||
|
||||
let emotes;
|
||||
try {
|
||||
emotes = JSON.parse(textarea.value);
|
||||
} catch (e) {
|
||||
console.error("Invalid JSON in YouTube Emotes textarea", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate each emote item
|
||||
for (const [emoteName, emoteUrl] of Object.entries(emotes)) {
|
||||
const span = document.createElement("span");
|
||||
span.classList.add("emote-item");
|
||||
span.innerHTML = `
|
||||
<img data-emote="${emoteName}" src="${emoteUrl}" alt="">
|
||||
<em>${emoteName}</em>
|
||||
<button class="delete"><i class="fa-solid fa-trash-can"></i></button>
|
||||
`;
|
||||
|
||||
// Add delete handler directly to the button
|
||||
const deleteBtn = span.querySelector(".delete");
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
if (confirm(`Are you sure you want to delete '${emoteName}'?`)) {
|
||||
delete emotes[emoteName];
|
||||
textarea.value = JSON.stringify(emotes, null, 4);
|
||||
populateEmoteList(); // Re-render everything
|
||||
}
|
||||
});
|
||||
|
||||
emoteList.insertBefore(span, addButtonSpan);
|
||||
}
|
||||
|
||||
setupAddEmoteModal();
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const accordionButtons = document.querySelectorAll("button.accordion");
|
||||
|
||||
accordionButtons.forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
const targetId = button.getAttribute("data-target");
|
||||
const target = document.getElementById(targetId);
|
||||
const icon = button.querySelector("i");
|
||||
|
||||
if (!target || !target.classList.contains("accordion-container")) return;
|
||||
|
||||
const isOpen = target.classList.contains("open");
|
||||
|
||||
// Fecha todos os outros accordions
|
||||
document.querySelectorAll(".accordion-container.open").forEach(container => {
|
||||
if (container !== target) {
|
||||
container.classList.remove("open");
|
||||
container.style.maxHeight = null;
|
||||
|
||||
const otherButton = document.querySelector(`button.accordion[data-target="${container.id}"]`);
|
||||
if (otherButton) {
|
||||
const otherIcon = otherButton.querySelector("i");
|
||||
if (otherIcon) otherIcon.className = "fa-solid fa-chevron-down";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Alterna o atual
|
||||
if (!isOpen) {
|
||||
target.classList.add("open");
|
||||
target.style.maxHeight = target.scrollHeight + "px";
|
||||
if (icon) icon.className = "fa-solid fa-chevron-up";
|
||||
|
||||
// Espera a animação terminar para scrollar
|
||||
target.addEventListener("transitionend", function handler(e) {
|
||||
if (e.propertyName === "max-height") {
|
||||
target.removeEventListener("transitionend", handler);
|
||||
|
||||
const offset = target.getBoundingClientRect().top + window.scrollY - 60;
|
||||
window.scrollTo({
|
||||
top: offset,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
target.classList.remove("open");
|
||||
target.style.maxHeight = null;
|
||||
if (icon) icon.className = "fa-solid fa-chevron-down";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
loadSettingsFromLocalStorage();
|
||||
generateUrl();
|
||||
pushChangeEvents();
|
||||
populateEmoteList();
|
||||
|
||||
|
||||
document.querySelectorAll('.nav-bar a').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove todas as classes dos links dentro da nav-bar
|
||||
document.querySelectorAll('.nav-bar a').forEach(link => {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
|
||||
this.classList.add('active');
|
||||
|
||||
const targetId = this.getAttribute('href');
|
||||
const targetElement = document.querySelector(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
|
||||
const offset = 60; // ajusta 20px acima
|
||||
const y = targetElement.getBoundingClientRect().top + window.scrollY - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: y,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* -------------------------
|
||||
Inicialização
|
||||
-------------------------- */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
streamerBotConnect();
|
||||
speakerBotConnection();
|
||||
|
||||
const speakerBotSwitcher = document.querySelector('input[type=checkbox][name=showSpeakerbot]');
|
||||
speakerBotSwitcher.addEventListener('change', () => {
|
||||
speakerBotConnection();
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user