heystreamer/views/overlay.ejs
Bashy 6c7facadce
Some checks failed
CI / check (push) Has been cancelled
rework
2026-04-26 23:00:55 +03:00

193 lines
6 KiB
Text

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
:root {
--chat-left: <%= layout.chat_left %>%;
--chat-top: <%= layout.chat_top %>%;
--chat-width: <%= layout.chat_width %>%;
--chat-height: <%= layout.chat_height %>%;
--chat-font-size: <%= layout.chat_font_size %>px;
--chat-font-family: <%= layout.chat_font_family %>;
--chat-bg-opacity: <%= layout.chat_bg_opacity %>;
--chat-text-color: <%= layout.chat_text_color %>;
--chat-border-radius: <%= layout.chat_border_radius %>px;
--alert-left: <%= layout.alert_left %>%;
--alert-top: <%= layout.alert_top %>%;
--alert-font-size: <%= layout.alert_font_size %>px;
--alert-bg-opacity: <%= layout.alert_bg_opacity %>;
--alert-text-color: <%= layout.alert_text_color %>;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: transparent; overflow: hidden; width: 1920px; height: 1080px; }
#chat {
position: fixed;
left: var(--chat-left);
top: var(--chat-top);
width: var(--chat-width);
max-height: var(--chat-height);
overflow: hidden;
display: flex;
flex-direction: column;
gap: 4px;
font-family: var(--chat-font-family);
font-size: var(--chat-font-size);
color: var(--chat-text-color);
}
.chat-msg {
background: rgba(0, 0, 0, var(--chat-bg-opacity));
border-radius: var(--chat-border-radius);
padding: 5px 10px;
word-break: break-word;
animation: fadeIn 0.25s ease;
transition: opacity 0.5s ease;
}
.chat-msg .username { font-weight: 700; margin-right: 4px; }
#alert-box {
position: fixed;
left: var(--alert-left);
top: var(--alert-top);
transform: translate(-50%, -50%);
font-family: var(--chat-font-family);
font-size: var(--alert-font-size);
color: var(--alert-text-color);
background: rgba(0, 0, 0, var(--alert-bg-opacity));
border-radius: 12px;
padding: 16px 28px;
text-align: center;
display: none;
}
#alert-video {
position: fixed;
left: var(--alert-left);
top: var(--alert-top);
transform: translate(-50%, -50%);
max-width: 640px;
max-height: 480px;
border-radius: 12px;
display: none;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
<%- settings.custom_css %>
</style>
</head>
<body>
<div id="chat"></div>
<div id="alert-box"></div>
<video id="alert-video" autoplay></video>
<script src="https://cdn.jsdelivr.net/npm/tmi.js@1.8.5/build/tmi.min.js"></script>
<script>
const S = <%- JSON.stringify(settings) %>;
const chat = document.getElementById('chat');
const alertBox = document.getElementById('alert-box');
const alertVideo = document.getElementById('alert-video');
let lastMsgTime = 0;
// --- Chat via tmi.js ---
if (S.channel) {
const client = new tmi.Client({ channels: [S.channel] });
client.connect().catch(console.error);
client.on('message', (channel, tags, message) => {
const now = Date.now();
const silence = (now - lastMsgTime) / 1000;
if (S.gap_audio_enabled && S.gap_audio_url && lastMsgTime > 0 && silence >= S.gap_audio_min_silence) {
const t = Math.min(
(silence - S.gap_audio_min_silence) / Math.max(S.gap_audio_loud_after - S.gap_audio_min_silence, 1),
1
);
const vol = S.gap_audio_min_volume + t * (S.gap_audio_max_volume - S.gap_audio_min_volume);
playAudio(S.gap_audio_url, vol);
}
lastMsgTime = now;
addMessage(tags['display-name'] || tags.username, tags.color || '#b4a0d4', message);
});
}
function addMessage(username, color, text) {
const div = document.createElement('div');
div.className = 'chat-msg';
div.innerHTML = `<span class="username" style="color:${esc(color)}">${esc(username)}</span>: ${esc(text)}`;
chat.appendChild(div);
while (chat.children.length > S.max_messages) chat.removeChild(chat.firstChild);
if (S.message_timeout > 0) {
setTimeout(() => {
div.style.opacity = '0';
setTimeout(() => div.remove(), 500);
}, S.message_timeout * 1000);
}
}
// --- Alert WebSocket ---
function connectWS() {
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
const ws = new WebSocket(`${proto}://${location.host}/ws/overlay`);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
document.dispatchEvent(new CustomEvent(`bashyoverlay:${msg.event_type}`, { detail: msg }));
handleAction(msg);
};
ws.onclose = () => setTimeout(connectWS, 3000);
}
connectWS();
function handleAction(msg) {
if (msg.action_type === 'sound' && msg.media_url) {
playAudio(msg.media_url, 1.0);
} else if (msg.action_type === 'video' && msg.media_url) {
showVideo(msg.media_url);
} else if (msg.action_type === 'alert') {
showAlert(msg.alert_text || '', msg.username);
}
}
function playAudio(url, volume) {
const a = new Audio(url);
a.volume = Math.max(0, Math.min(1, volume));
a.play().catch(() => {});
}
function showAlert(text, username) {
alertBox.textContent = text.replace('{username}', username || '');
alertBox.style.display = 'block';
setTimeout(() => { alertBox.style.display = 'none'; }, 5000);
}
function showVideo(url) {
alertVideo.src = url;
alertVideo.style.display = 'block';
alertVideo.play();
alertVideo.onended = () => {
alertVideo.style.display = 'none';
alertVideo.src = '';
};
}
function esc(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
<%- settings.custom_js %>
</script>
</body>
</html>