Add files via upload

Added a Chat Input Field and Moderation Tools.
This commit is contained in:
Rodrigo Emanuel
2025-05-23 19:28:51 -03:00
committed by GitHub
parent 847769bf79
commit 19006f06ea
9 changed files with 460 additions and 17 deletions

View File

@@ -50,10 +50,49 @@
<div id="chat"> <div id="chat">
</div> </div>
<div id="chat-input">
<form>
<input type="text" placeholder="Send a Message">
</form>
<button id="chat-input-config"><i class="fa-solid fa-gear"></i></button>
<button id="chat-input-send"><i class="fa-solid fa-paper-plane"></i></button>
<div id="chat-input-settings" class="settings animate__faster" style="display: none;">
<span class="chat-enabler" id="twitch">
<img src="images/logo-twitch.svg">
<label class="switch">
<input type="checkbox" data-platform="twitch" name="twitchChatSend" checked>
<span class="slider"></span>
</label>
</span>
<span class="chat-enabler" id="youtube">
<img src="images/logo-youtube.svg">
<label class="switch">
<input type="checkbox" data-platform="youtube" name="youtubeChatSend" checked>
<span class="slider"></span>
</label>
</span>
<span class="chat-enabler" id="kick" title="Only Chat, No commands.">
<img src="images/logo-kick.svg">
<label class="switch">
<input type="checkbox" data-platform="kick" name="kickChatSend" checked>
<span class="slider"></span>
</label>
<i class="fa-solid fa-triangle-exclamation"></i>
</span>
<span class="chat-enabler" id="tiktok">
<img src="images/logo-tiktok.svg">
<i class="fa-solid fa-ban"></i>
</span>
</div>
</div>
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.5/dist/purify.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.5/dist/purify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/simple-notify@1.0.4/dist/simple-notify.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/simple-notify@1.0.4/dist/simple-notify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@streamerbot/client@1.9.5/dist/streamerbot-client.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@streamerbot/client@1.9.5/dist/streamerbot-client.min.js"></script>
@@ -80,4 +119,4 @@
<script src="js/streamelements/module.js"></script> <script src="js/streamelements/module.js"></script>
</body> </body>
</html> </html>

View File

@@ -59,12 +59,19 @@ body {
#chat .message { #chat .message {
position: relative;
color: #FFF; color: #FFF;
font-size: 18px; font-size: 18px;
line-height: 150%; line-height: 150%;
text-shadow: 2px 2px 2px rgba(0,0,0,0.75); text-shadow: 2px 2px 2px rgba(0,0,0,0.75);
transition: all ease-in-out 300ms; transition: all ease-in-out 300ms;
margin: 5px 0; margin: 5px 0;
padding-bottom: 4px;
border-radius: 5px;
}
#chat .message:not(.event):hover {
background: #1a1a1a;
} }
#chat .message img { #chat .message img {
@@ -458,14 +465,12 @@ body {
.wrapper.horizontal {
align-items: flex-end;
}
.wrapper.horizontal #chat {
#chat.horizontal {
position: absolute;
right: 0;
bottom: 0;
flex-direction: row-reverse; flex-direction: row-reverse;
align-items: flex-end; align-items: flex-end;
gap: 10px; gap: 10px;
@@ -474,6 +479,210 @@ body {
#chat .message.twitch:hover .chatmoderation.twitch {
display: block;
}
#chat .message .chatmoderation {
display: none;
position: absolute;
top: 0;
right: 0;
padding: 2px 3px;
background: #262626;
border-radius: 5px;
transition: all ease-in-out 300ms;
}
#chat .message .chatmoderation button {
background: none;
border: none;
color: #FFF;
cursor: pointer;
font-size: 18px;
padding: 5px;
transition: all ease-in-out 300ms;
margin: 0 3px;
}
#chat .message .chatmoderation {
color: #ffcc00;
}
#chat .message.twitch:hover .chatmoderation.twitch {
display: block;
}
#chat .message.youtube:hover .chatmoderation.youtube {
display: block;
}
#chat .message.streamer .chatmoderation button:nth-child(2),
#chat .message.streamer .chatmoderation button:nth-child(3),
#chat .message.owner .chatmoderation {
display: none;
}
#chat-input {
padding: 15px 10px;
position: relative;
display: none;
}
#chat-input.enabled {
display: block;
width: 100%;
}
#chat-input input[type=text] {
font-family: "Inter", sans-serif;
border: none;
background: #222;
color: #FFF;
padding: 10px 90px 10px 15px;
border-radius: 10px;
outline: none;
font-size: 16px;
width: 100%;
transition: all ease-in-out 300ms;
}
#chat-input button {
background: none;
border: none;
color: #FFF;
cursor: pointer;
font-size: 18px;
padding: 5px;
position: absolute;
top: 50%;
transform: translateY(-50%);
transition: all ease-in-out 300ms;
}
#chat-input button.active,
#chat-input button:hover {
color: #ffcc00;
}
#chat-input #chat-input-config {
right: 20px;
}
#chat-input #chat-input-send {
right: 60px;
}
#chat-input .settings {
display: inline-block;
background: #0c0c0c;
padding: 10px;
border-radius: 10px;
position: absolute;
top: -55px;
right: 0px;
z-index: 11;
}
#chat-input .settings::after {
content: "";
position: absolute;
bottom: -10px; /* posiciona fora do balão */
right: 25px; /* onde a pontinha aparece horizontalmente */
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #0c0c0c; /* mesma cor do balão */
}
#chat-input .settings img {
width: 28px;
}
#chat-input .chat-enabler {
display: inline-flex;
padding: 5px;
gap: 5px;
color: #FFF;
font-size: 14px;
align-items: center;
color: #333;
}
#chat-input .settings .switch {
position: relative;
display: inline-block;
width: 50px;
height: 27px;
}
/* Hide default HTML checkbox */
#chat-input .settings .switch input[type=checkbox] {
opacity: 1;
width: 0;
height: 0;
}
/* The slider */
#chat-input .settings .slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #CCC;
transition: .4s;
border-radius: 30px;
}
#chat-input .settings .slider:before {
position: absolute;
content: "";
height: 1.4em;
width: 1.3em;
border-radius: 16px;
left: 5px;
top: 3px;
bottom: 0;
background-color: white;
transition: .4s;
}
#chat-input .settings input[type=checkbox]:checked + .slider:before {
transform: translateX(1.5em);
}
#chat-input #twitch input[type=checkbox]:checked + .slider {
background-color: #a970ff;
}
#chat-input #youtube input[type=checkbox]:checked + .slider {
background-color: #FF0000;
}
#chat-input #kick input[type=checkbox]:checked + .slider {
background-color: #48d415;
}
#chat-input .settings input[type=checkbox]:checked + .slider {
background-color: #03c4de;
}
#chat-input .setting input[type=checkbox]:disabled + .slider {
background-color: #000 !important;
}
#statistics { #statistics {
position: fixed; position: fixed;
z-index: 11; z-index: 11;

View File

@@ -17,6 +17,11 @@ body {
a { color: #ffcc00; } a { color: #ffcc00; }
hr {
border: 1px solid #222;
margin: 20px 0;
}
#chat-divided { #chat-divided {
display: flex; display: flex;

View File

@@ -63,7 +63,31 @@
</label> </label>
</div> </div>
<div class="setting"><label>Scroll Bar<br><small>Adds a scrollbar. Perfect if you using ChatRD as a chat reader instead of an overlay.</small></label></label><label class="switch"><input type="checkbox" name="chatScrollBar"><span class="slider"></span></label></div> <div class="setting"><label>Scroll Bar<br><small>Adds a scrollbar.</small></label></label><label class="switch"><input type="checkbox" name="chatScrollBar"><span class="slider"></span></label></div>
<div class="setting"></div>
<hr>
<div class="setting"><small style="display: inline-block; padding: 10px 20px 10px 10px; background-color: #232323; color: #FFF; border-radius: 10px;"><i class="fa-solid fa-triangle-exclamation"></i> The Chat Field is a <strong>BETA</strong> feature!</small></label></div>
<div class="setting"></div>
<div class="setting"><label>Chat Field<br><small>Adds a chat field for you to type in your chat.</small></label></label><label class="switch"><input type="checkbox" name="chatField"><span class="slider"></span></label></div>
<div class="setting"><small style="display: inline-block; padding: 10px 20px 10px 10px; background-color: #232323; color: #FFF; border-radius: 10px;"><i class="fa-solid fa-circle-exclamation"></i> The Chat Field also accepts <strong>/commands</strong>. <strong><a target="_blank" href="https://github.com/vortisrd/chatrd/tree/main?tab=readme-ov-file#-commands-supported-by-the-chat-field">Commands Supported</a>.</strong></small></label></div>
<hr>
<div class="setting"><small style="display: inline-block; padding: 10px 20px 10px 10px; background-color: #232323; color: #FFF; border-radius: 10px;"><i class="fa-solid fa-triangle-exclamation"></i> Moderation is a <strong>BETA</strong> feature!</small></label></div>
<div class="setting"></div>
<div class="setting"><label>Moderation Actions<br><small>Adds buttons when you hover the messages.</small></label></label><label class="switch"><input type="checkbox" name="chatModeration"><span class="slider"></span></label></div>
<div class="setting"><small style="display: inline-block; padding: 10px 20px 10px 10px; background-color: #232323; color: #FFF; border-radius: 10px;"><i class="fa-solid fa-circle-exclamation"></i> Moderation Actions follows the Commands suported. <strong><a target="_blank" href="https://github.com/vortisrd/chatrd/tree/main?tab=readme-ov-file#-commands-supported-by-the-chat-field">Read More</a>.</strong></small></label></div>
<hr>
<div class="setting"> <div class="setting">
<label>Background Color<br><small>Changes ChatRD's background color</small></label> <label>Background Color<br><small>Changes ChatRD's background color</small></label>
@@ -168,7 +192,7 @@
Kick Kick
</h2> </h2>
<div class="setting"><small style="display: inline-block; padding: 10px 20px 10px 10px; background-color: #232323; color: #FFF; border-radius: 10px;"><i class="fa-solid fa-triangle-exclamation"></i> This is a <strong>BETA</strong> feature! <strong><a href="https://github.com/vortisrd/chatrd/tree/main#%EF%B8%8F-kick-is-a-beta-feature" target="_blank">Read more</a></strong>.</small></label></div> <div class="setting"><small style="display: inline-block; padding: 10px 20px 10px 10px; background-color: #232323; color: #FFF; border-radius: 10px;"><i class="fa-solid fa-triangle-exclamation"></i> Kick is a <strong>BETA</strong> feature! <strong><a href="https://github.com/vortisrd/chatrd/tree/main#%EF%B8%8F-kick-is-a-beta-feature" target="_blank">Read more</a></strong>.</small></label></div>
<div class="setting"></div> <div class="setting"></div>

View File

@@ -1009,4 +1009,4 @@ function randomString(length) {
function randomColor() { function randomColor() {
const randomColor = "hsl(" + Math.random() * 360 + ", 100%, 75%)"; const randomColor = "hsl(" + Math.random() * 360 + ", 100%, 75%)";
return randomColor; return randomColor;
} }

171
js/app.js
View File

@@ -16,6 +16,8 @@ const chatFontSize = getURLParam("chatFontSize", 1);
const chatBackground = getURLParam("chatBackground", "#121212"); const chatBackground = getURLParam("chatBackground", "#121212");
const chatBackgroundOpacity = getURLParam("chatBackgroundOpacity", 1); const chatBackgroundOpacity = getURLParam("chatBackgroundOpacity", 1);
const chatScrollBar = getURLParam("chatScrollBar", false); const chatScrollBar = getURLParam("chatScrollBar", false);
const chatField = getURLParam("chatField", false);
const chatModeration = getURLParam("chatModeration", false);
const currentLang = lang[getURLParam("language", 'ptbr')]; const currentLang = lang[getURLParam("language", 'ptbr')];
const eventsMockup = getURLParam("eventsMockup", true); const eventsMockup = getURLParam("eventsMockup", true);
@@ -36,6 +38,7 @@ const ignoreUserList = ignoreChatters.split(',').map(item => item.trim().toLower
chatContainer.style.zoom = chatFontSize; chatContainer.style.zoom = chatFontSize;
if (chatScrollBar == false) { chatContainer.classList.add('noscrollbar'); } if (chatScrollBar == false) { chatContainer.classList.add('noscrollbar'); }
if (chatField == true) { document.getElementById("chat-input").classList.add('enabled'); }
/* ----------------------- */ /* ----------------------- */
/* START */ /* START */
@@ -44,7 +47,7 @@ if (chatScrollBar == false) { chatContainer.classList.add('noscrollbar'); }
document.body.style.backgroundColor = hexToRGBA(chatBackground,chatBackgroundOpacity); document.body.style.backgroundColor = hexToRGBA(chatBackground,chatBackgroundOpacity);
if (showPlatformStatistics == false) { document.querySelector('#statistics').style.display = 'none'; } if (showPlatformStatistics == false) { document.querySelector('#statistics').style.display = 'none'; }
if (chatHorizontal == true) { chatContainer.classList.add('horizontal'); } if (chatHorizontal == true) { chatContainer.parentElement.classList.add('horizontal'); }
/* ----------------------- */ /* ----------------------- */
/* STREAMER.BOT CONNECTION */ /* STREAMER.BOT CONNECTION */
@@ -77,6 +80,8 @@ const streamerBotClient = new StreamerbotClient({
}); });
/* ----------------------- */ /* ----------------------- */
/* UTILITIES */ /* UTILITIES */
/* ----------------------- */ /* ----------------------- */
@@ -87,7 +92,7 @@ async function addMessageToChat(userID, messageID, platform, data) {
if (ttsSpeakerBotChat == true) { ttsSpeakerBotSays(data.userName, currentLang.ttschat, data.message); } if (ttsSpeakerBotChat == true) { ttsSpeakerBotSays(data.userName, currentLang.ttschat, data.message); }
const html = DOMPurify.sanitize(` let html = DOMPurify.sanitize(`
<div id="${messageID}" data-user="${userID}" class="${platform} ${data.classes} message" style=""> <div id="${messageID}" data-user="${userID}" class="${platform} ${data.classes} message" style="">
<div class="animate__animated ${chatHorizontal == true ? 'animate__fadeInRight' : 'animate__fadeInUp'} animate__faster"> <div class="animate__animated ${chatHorizontal == true ? 'animate__fadeInRight' : 'animate__fadeInUp'} animate__faster">
@@ -109,9 +114,28 @@ async function addMessageToChat(userID, messageID, platform, data) {
<span class="text">${data.message}</span> <span class="text">${data.message}</span>
</div> </div>
${chatModeration == true && platform == 'twitch' ? `[CHATMODERATIONSNIPPETTWITCH]` : ''}
${chatModeration == true && platform == 'youtube' ? `[CHATMODERATIONSNIPPETYOUTUBE]` : ''}
</div> </div>
`); `);
let chatmodtwitch = `<span class="chatmoderation twitch">
<button onclick="executeModCommand(event, '/deletemessage ${messageID}')" title="Remove Message"><i class="fa-solid fa-trash-can"></i></button>
<button onclick="executeModCommand(event, '/timeout ${userID}')" title="Timeout User"><i class="fa-solid fa-stopwatch"></i></button>
<button onclick="executeModCommand(event, '/ban ${userID}')" title="Ban User"><i class="fa-solid fa-gavel"></i></button>
</span>`;
let chatmodyoutube = `<span class="chatmoderation youtube">
<button onclick="executeModCommand(event, '/yt/timeout ${userID}')" title="Timeout User"><i class="fa-solid fa-stopwatch"></i></button>
<button onclick="executeModCommand(event, '/yt/ban ${userID}')" title="Ban User"><i class="fa-solid fa-gavel"></i></button>
</span>`;
html = html.replace('[CHATMODERATIONSNIPPETTWITCH]', chatmodtwitch);
html = html.replace('[CHATMODERATIONSNIPPETYOUTUBE]', chatmodyoutube);
chatContainer.insertAdjacentHTML('afterbegin', html); chatContainer.insertAdjacentHTML('afterbegin', html);
const messageElement = document.getElementById(messageID); const messageElement = document.getElementById(messageID);
@@ -142,7 +166,6 @@ async function addEventToChat(userID, messageID, platform, data) {
${showPlatform == true ? '<span class="platform"><img src="images/logo-'+platform+'.svg" ></span>' : '' } ${showPlatform == true ? '<span class="platform"><img src="images/logo-'+platform+'.svg" ></span>' : '' }
<span class="info"> <span class="info">
<!--<span class="avatar"><img src="${data.avatar}"></span>-->
<span style="color: ${data.color}" class="user">${data.userName}</span> <span style="color: ${data.color}" class="user">${data.userName}</span>
<span class="text">${data.message}</span> <span class="text">${data.message}</span>
</span> </span>
@@ -167,6 +190,7 @@ async function addEventToChat(userID, messageID, platform, data) {
} }
const whatTimeIsIt = () => { const whatTimeIsIt = () => {
const now = new Date(); const now = new Date();
const hours24 = now.getHours(); const hours24 = now.getHours();
@@ -373,4 +397,143 @@ function hexToRGBA(hexadecimal,opacity) {
const b = parseInt(hex.substr(5, 2), 16); const b = parseInt(hex.substr(5, 2), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`; return `rgba(${r}, ${g}, ${b}, ${alpha})`;
} }
const chatInputConfig = document.getElementById("chat-input-config");
const chatInputSend = document.getElementById("chat-input-send");
const chatInputForm = document.querySelector("#chat-input form");
const settings = document.getElementById("chat-input-settings");
chatInputForm.addEventListener("submit", function(event) {
event.preventDefault();
var chatSendPlatforms = [];
const settingsContainer = document.getElementById("chat-input-settings");
const checkboxes = settingsContainer.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
const checked = checkbox.checked;
const platform = checkbox.getAttribute('data-platform');
if (checked == true) { chatSendPlatforms.push(platform); }
});
chatSendPlatforms = chatSendPlatforms.join(',')
const chatInput = chatInputForm.querySelector("input[type=text]")
const chatInputText = chatInput.value;
// Sends Message to Twitch and YouTube
streamerBotClient.doAction(
{ name : "ChatRD Messages and Commands" },
{
"type": "chat",
"platforms": chatSendPlatforms,
"message": chatInputText,
}
).then( (sendchatstuff) => {
console.debug('Sending Chat to Streamer.Bot', sendchatstuff);
});
// Sends Message to Kick that are not commands
if (chatSendPlatforms.includes('kick')) {
if (!chatInputText.startsWith('/')) {
streamerBotClient.doAction(
{ name : "ChatRD Kick Messages" },
{
"message": chatInputText,
}
).then( (sendchatstuff) => {
console.debug('Sending Kick Chat to Streamer.Bot', sendchatstuff);
});
}
}
chatInput.value = '';
});
chatInputSend.addEventListener("click", function () {
chatInputForm.requestSubmit();
});
chatInputConfig.addEventListener("click", function () {
const isHidden = settings.style.display === "none" || settings.classList.contains("animate__fadeOutDown");
if (isHidden) {
// Remover animação de saída (caso ainda esteja presente)
settings.classList.remove("animate__fadeOutDown");
// Mostrar com animação de entrada
settings.style.display = "block";
settings.classList.add("animate__animated", "animate__fadeInUp");
// Limpa as classes após a animação
settings.addEventListener("animationend", function handler() {
settings.classList.remove("animate__animated", "animate__fadeInUp");
settings.removeEventListener("animationend", handler);
});
chatInputConfig.classList.add("active");
}
else {
// Começar animação de saída
settings.classList.remove("animate__fadeInUp");
settings.classList.add("animate__animated", "animate__fadeOutDown");
// Após animação, esconder elemento
settings.addEventListener("animationend", function handler() {
settings.style.display = "none";
settings.classList.remove("animate__animated", "animate__fadeOutDown");
settings.removeEventListener("animationend", handler);
});
chatInputConfig.classList.remove("active");
}
});
async function executeModCommand(event, command) {
event.preventDefault();
if (streamerBotConnected == true) {
const chatInput = chatInputForm.querySelector("input[type=text]")
chatInput.value = command;
chatInputForm.requestSubmit();
}
else {
notifyError({
title: currentLang.streamerbotdisconnected,
text: ``
});
}
}
document.addEventListener("DOMContentLoaded", () => {
const settingsContainer = document.getElementById("chat-input-settings");
// Seleciona apenas checkboxes DENTRO do container
const checkboxes = settingsContainer.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
const name = checkbox.name;
if (!name) return; // Ignora se o checkbox não tem 'name'
// Restaurar estado salvo
const saved = localStorage.getItem(name);
if (saved !== null) {
checkbox.checked = saved === "true";
}
// Salvar alterações
checkbox.addEventListener("change", () => {
localStorage.setItem(name, checkbox.checked);
});
});
});

View File

@@ -38,7 +38,6 @@ if (kickUserName) {
kick7TVEmojis.set(emote.name, emote.url); kick7TVEmojis.set(emote.name, emote.url);
}); });
} }
})(); })();
kickWebSocket.send( kickWebSocket.send(

View File

@@ -116,6 +116,10 @@ async function twitchChatMessage(data) {
const classes = firstMessage ? ['first-message'] : []; const classes = firstMessage ? ['first-message'] : [];
if (data.user.role == 4) {
classes.push('streamer');
}
const replyHTML = isReply ? const replyHTML = isReply ?
`<div class="reply"><i class="fa-solid fa-arrow-turn-up"></i> <strong>${replyData.userName}:</strong> ${replyData.msgBody}</div>` : `<div class="reply"><i class="fa-solid fa-arrow-turn-up"></i> <strong>${replyData.userName}:</strong> ${replyData.msgBody}</div>` :
''; '';

File diff suppressed because one or more lines are too long