Initial
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
61
modules/alert-banner/alert-banner.js
Normal file
61
modules/alert-banner/alert-banner.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
;(function() {
|
||||||
|
const moduleUrl = window.getModuleUrl();
|
||||||
|
loadCSSModule('overlay-alertbanner-css', moduleUrl + '/css/alert-banner.css');
|
||||||
|
|
||||||
|
function initModule() {
|
||||||
|
const container = document.getElementById('mainContainer') || document.body;
|
||||||
|
if (!document.getElementById('alertBannerContainer')) {
|
||||||
|
const alertBannerContainer = document.createElement('div');
|
||||||
|
alertBannerContainer.id = 'alertBannerContainer';
|
||||||
|
|
||||||
|
const alertBannerTitle = document.createElement('div');
|
||||||
|
alertBannerTitle.id = 'alertBannerTitle';
|
||||||
|
alertBannerTitle.innerHTML = '<p></p>';
|
||||||
|
|
||||||
|
const alertBannerMessage = document.createElement('div');
|
||||||
|
alertBannerMessage.id = 'alertBannerMessage';
|
||||||
|
alertBannerMessage.innerHTML = '<p></p>';
|
||||||
|
|
||||||
|
alertBannerContainer.appendChild(alertBannerTitle);
|
||||||
|
alertBannerContainer.appendChild(alertBannerMessage);
|
||||||
|
container.appendChild(alertBannerContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initModule();
|
||||||
|
|
||||||
|
if (window.SBdispatcher) {
|
||||||
|
SBdispatcher.on('stream-alertbanner', data => {
|
||||||
|
showAlert(data.param1,data.param2);
|
||||||
|
});
|
||||||
|
SBdispatcher.on('stream-alertbanner-hide', () => {
|
||||||
|
hideAlert();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert(title = "" , message = "") {
|
||||||
|
const container = document.getElementById('alertBannerContainer');
|
||||||
|
const alertTitle = document.getElementById('alertBannerTitle').querySelector('p');
|
||||||
|
const alertText = document.getElementById('alertBannerMessage').querySelector('p');
|
||||||
|
|
||||||
|
if (title.length > 0) {
|
||||||
|
alertTitle.innerText = title;
|
||||||
|
}
|
||||||
|
if (message.length > 0) {
|
||||||
|
alertText.innerText = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
container._positionDivHandler = () => positionDiv(container);
|
||||||
|
container.addEventListener('animationend', container._positionDivHandler);
|
||||||
|
container.style.animation = "slide-in-right 2s ease forwards";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAlert() {
|
||||||
|
const container = document.getElementById('alertBannerContainer');
|
||||||
|
container._positionDivHandler = () => positionDiv(container);
|
||||||
|
container.addEventListener('animationend', container._positionDivHandler);
|
||||||
|
container.style.animation = "slide-out-right 2s ease forwards";
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
49
modules/alert-banner/css/alert-banner.css
Normal file
49
modules/alert-banner/css/alert-banner.css
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#alertBannerContainer {
|
||||||
|
z-index: 500;
|
||||||
|
position: fixed;
|
||||||
|
top: 30px;
|
||||||
|
left: auto;
|
||||||
|
right: -100%;
|
||||||
|
width: 25%;
|
||||||
|
height: 75px;
|
||||||
|
background: linear-gradient(to left, black, #00007f);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: polygon(30px 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
#alertBannerContainer #alertBannerTitle {
|
||||||
|
color: white;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 26px;
|
||||||
|
text-decoration: underline;
|
||||||
|
white-space: nowrap;
|
||||||
|
left: 30px;
|
||||||
|
top: 0px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#alertBannerContainer #alertBannerTitle p {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px 1em 0px 0px;
|
||||||
|
background: linear-gradient(to bottom, #ffff7f, #ffffff);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
#alertBannerContainer #alertBannerMessage {
|
||||||
|
color: white;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 36px;
|
||||||
|
white-space: nowrap;
|
||||||
|
left: 30px;
|
||||||
|
bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#alertBannerContainer #alertBannerMessage p {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px 1em 0px 0px;
|
||||||
|
}
|
||||||
56
modules/alert-banner/css/alert-banner.less
Normal file
56
modules/alert-banner/css/alert-banner.less
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// out: alert-banner.css, sourcemap: false, compress: false
|
||||||
|
|
||||||
|
#alertBannerContainer {
|
||||||
|
z-index: 500;
|
||||||
|
position: fixed;
|
||||||
|
top: 30px;
|
||||||
|
left: auto;
|
||||||
|
right: -100%;
|
||||||
|
width: 25%;
|
||||||
|
height: 75px;
|
||||||
|
background: linear-gradient(to left, black, #00007f);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: polygon( 30px 0, 100% 0, 100% 100%, 0 100% );
|
||||||
|
|
||||||
|
#alertBannerTitle {
|
||||||
|
color: white;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 26px;
|
||||||
|
text-decoration: underline;
|
||||||
|
white-space: nowrap;
|
||||||
|
left: 30px;
|
||||||
|
top: 0px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px 1em 0px 0px;
|
||||||
|
background: linear-gradient(to bottom, #ffff7f, #ffffff);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertBannerMessage {
|
||||||
|
color: white;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 36px;
|
||||||
|
white-space: nowrap;
|
||||||
|
left: 30px;
|
||||||
|
bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px 1em 0px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
114
modules/alert-video/alert-video.js
Normal file
114
modules/alert-video/alert-video.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
;(function() {
|
||||||
|
const moduleUrl = window.getModuleUrl();
|
||||||
|
loadCSSModule('overlay-alertvideo-css', moduleUrl + '/css/alert-video.css');
|
||||||
|
|
||||||
|
function initModule() {
|
||||||
|
const container = document.getElementById('mainContainer') || document.body;
|
||||||
|
if (!document.getElementById('alertVideoContainer')) {
|
||||||
|
const alertVideoContainer = document.createElement('div');
|
||||||
|
alertVideoContainer.id = 'alertVideoContainer';
|
||||||
|
container.appendChild(alertVideoContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initModule();
|
||||||
|
|
||||||
|
if (window.SBdispatcher) {
|
||||||
|
SBdispatcher.on('stream-alert:Follow', data => {
|
||||||
|
showAlert({
|
||||||
|
userName: data.user,
|
||||||
|
videoUrl: moduleUrl + '/files/ALERT_follow.webm',
|
||||||
|
delay: 12
|
||||||
|
});
|
||||||
|
});
|
||||||
|
SBdispatcher.on('stream-alert:Sub', data => {
|
||||||
|
showAlert({
|
||||||
|
userName: data.user,
|
||||||
|
videoUrl: moduleUrl + '/files/ALERT_sub.webm',
|
||||||
|
delay: 12
|
||||||
|
});
|
||||||
|
});
|
||||||
|
SBdispatcher.on('stream-alert:SubGift', data => {
|
||||||
|
showAlert({
|
||||||
|
userName: data.user,
|
||||||
|
videoUrl: moduleUrl + '/files/ALERT_gift.webm',
|
||||||
|
delay: 12,
|
||||||
|
topLine: 'Abonnement offert à <span class="alert_text-accent alert_variable-username">' + data.param1 + '</span>',
|
||||||
|
// bottomLine: 'Il a déjà offert <span class="alert_text-accent alert_variable-username">' + data.param2 +'</span> abonnements à la communauté !'
|
||||||
|
}); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert({ userName, videoUrl, delay = 12, topLine = null, bottomLine = null }) {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.classList.add('alert_outer-container', 'playing');
|
||||||
|
|
||||||
|
const widget = document.createElement('div');
|
||||||
|
widget.classList.add('alert_widget-container');
|
||||||
|
|
||||||
|
const textContainer = document.createElement('div');
|
||||||
|
textContainer.classList.add('alert_text-container');
|
||||||
|
|
||||||
|
// Zone nom d’utilisateur (au centre de la vidéo)
|
||||||
|
const centerTextWrapper = document.createElement('div');
|
||||||
|
const usernameText = document.createElement('p');
|
||||||
|
usernameText.classList.add('alert_text');
|
||||||
|
|
||||||
|
const usernameSpan = document.createElement('span');
|
||||||
|
usernameSpan.classList.add('alert_text-accent', 'alert_variable-username');
|
||||||
|
usernameSpan.textContent = userName;
|
||||||
|
|
||||||
|
usernameText.appendChild(usernameSpan);
|
||||||
|
centerTextWrapper.appendChild(usernameText);
|
||||||
|
|
||||||
|
// Vidéo
|
||||||
|
const videoContainer = document.createElement('div');
|
||||||
|
videoContainer.classList.add('alertVideoContainer');
|
||||||
|
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.classList.add('alertVideo');
|
||||||
|
video.autoplay = true;
|
||||||
|
video.muted = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
|
||||||
|
const source = document.createElement('source');
|
||||||
|
source.src = videoUrl;
|
||||||
|
source.type = 'video/webm';
|
||||||
|
video.appendChild(source);
|
||||||
|
videoContainer.appendChild(video);
|
||||||
|
|
||||||
|
// Optionnel : ligne de texte en haut
|
||||||
|
if (topLine) {
|
||||||
|
const topTextContainer = document.createElement('div');
|
||||||
|
topTextContainer.classList.add('alert_top-text-container');
|
||||||
|
const topText = document.createElement('p');
|
||||||
|
topText.classList.add('alert_top-text');
|
||||||
|
topText.innerHTML = topLine;
|
||||||
|
topTextContainer.appendChild(topText);
|
||||||
|
container.appendChild(topTextContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionnel : ligne de texte en bas
|
||||||
|
if (bottomLine) {
|
||||||
|
const bottomTextContainer = document.createElement('div');
|
||||||
|
bottomTextContainer.classList.add('alert_bottom-text-container');
|
||||||
|
const bottomText = document.createElement('p');
|
||||||
|
bottomText.classList.add('alert_bottom-text');
|
||||||
|
bottomText.innerHTML = bottomLine;
|
||||||
|
bottomTextContainer.appendChild(bottomText);
|
||||||
|
container.appendChild(bottomTextContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble tout
|
||||||
|
textContainer.appendChild(centerTextWrapper);
|
||||||
|
textContainer.appendChild(videoContainer);
|
||||||
|
widget.appendChild(textContainer);
|
||||||
|
container.appendChild(widget);
|
||||||
|
document.getElementById('alertVideoContainer').appendChild(container);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
container.remove();
|
||||||
|
}, delay*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
133
modules/alert-video/css/alert-video.css
Normal file
133
modules/alert-video/css/alert-video.css
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#alertVideoContainer {
|
||||||
|
z-index: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_outer-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 800px;
|
||||||
|
height: 600px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_outer-container.playing {
|
||||||
|
animation: 12s alert-animation ease-in-out forwards;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_widget-container {
|
||||||
|
display: flex;
|
||||||
|
background-color: #FFFFFF00;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_text-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
animation: hideIn 1.5s, fadeIn 2s 1.5s, fadeOut 2s 8.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_text-accent {
|
||||||
|
color: #ccaaff;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_top-text-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 1rem;
|
||||||
|
z-index: 2;
|
||||||
|
animation: fadeIn 2s 0.5s, fadeOut 2s 10s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_top-text-container .alert_top-text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
text-shadow: 0 0 4px #000, 0 0 10px #000;
|
||||||
|
animation: hideIn 1.5s, fadeIn 2s 1.5s, fadeOut 2s 8.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_bottom-text-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
z-index: 2;
|
||||||
|
animation: fadeIn 2s 0.5s, fadeOut 2s 10s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alert_bottom-text-container .alert_bottom-text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
text-shadow: 0 0 4px #000, 0 0 10px #000;
|
||||||
|
animation: hideIn 1.5s, fadeIn 2s 1.5s, fadeOut 2s 8.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alertVideoContainer {
|
||||||
|
display: flex;
|
||||||
|
width: 50%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
justify-content: center;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
#alertVideoContainer .alertVideoContainer .alertVideo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
@keyframes alert-animation {
|
||||||
|
0% {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
8.333333333333332% {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
91.66666666666667% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
154
modules/alert-video/css/alert-video.less
Normal file
154
modules/alert-video/css/alert-video.less
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// out: alert-video.css, sourcemap: false, compress: false
|
||||||
|
|
||||||
|
#alertVideoContainer {
|
||||||
|
z-index: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.alert_outer-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 800px;
|
||||||
|
height: 600px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.alert_outer-container.playing {
|
||||||
|
animation: 12s alert-animation ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert_widget-container {
|
||||||
|
display: flex;
|
||||||
|
background-color: #FFFFFF00;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert_text-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert_text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
animation: hideIn 1.5s, fadeIn 2s 1.5s, fadeOut 2s 8.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert_text-accent {
|
||||||
|
color: #ccaaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert_top-text-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 1rem;
|
||||||
|
z-index: 2;
|
||||||
|
animation: fadeIn 2s 0.5s, fadeOut 2s 10s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
.alert_top-text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
|
||||||
|
text-shadow: 0 0 4px #000, 0 0 10px #000;
|
||||||
|
|
||||||
|
animation: hideIn 1.5s, fadeIn 2s 1.5s, fadeOut 2s 8.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert_bottom-text-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
z-index: 2;
|
||||||
|
animation: fadeIn 2s 0.5s, fadeOut 2s 10s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
.alert_bottom-text {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
|
||||||
|
text-shadow: 0 0 4px #000, 0 0 10px #000;
|
||||||
|
|
||||||
|
animation: hideIn 1.5s, fadeIn 2s 1.5s, fadeOut 2s 8.5s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertVideoContainer {
|
||||||
|
display: flex;
|
||||||
|
width: 50%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
justify-content: center;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
.alertVideo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes alert-animation {
|
||||||
|
0% {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
8.333333333333332% {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
91.66666666666667% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
modules/alert-video/files/ALERT_cheer.webm
Normal file
BIN
modules/alert-video/files/ALERT_cheer.webm
Normal file
Binary file not shown.
BIN
modules/alert-video/files/ALERT_follow.webm
Normal file
BIN
modules/alert-video/files/ALERT_follow.webm
Normal file
Binary file not shown.
BIN
modules/alert-video/files/ALERT_gift.webm
Normal file
BIN
modules/alert-video/files/ALERT_gift.webm
Normal file
Binary file not shown.
BIN
modules/alert-video/files/ALERT_raid.webm
Normal file
BIN
modules/alert-video/files/ALERT_raid.webm
Normal file
Binary file not shown.
BIN
modules/alert-video/files/ALERT_sub.webm
Normal file
BIN
modules/alert-video/files/ALERT_sub.webm
Normal file
Binary file not shown.
BIN
modules/alert-video/files/HypeTrainStarted.webm
Normal file
BIN
modules/alert-video/files/HypeTrainStarted.webm
Normal file
Binary file not shown.
125
modules/big-emote/big-emote.js
Normal file
125
modules/big-emote/big-emote.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
;(function() {
|
||||||
|
const moduleUrl = window.getModuleUrl();
|
||||||
|
loadCSSModule('overlay-bigemote-css', moduleUrl + '/css/big-emote.css');
|
||||||
|
|
||||||
|
function initModule() {
|
||||||
|
const container = document.getElementById('mainContainer') || document.body;
|
||||||
|
if (!document.getElementById('bigEmoteContainer')) {
|
||||||
|
const bigEmoteContainer = document.createElement('div');
|
||||||
|
bigEmoteContainer.id = 'bigEmoteContainer';
|
||||||
|
container.appendChild(bigEmoteContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initModule();
|
||||||
|
|
||||||
|
if (window.SBdispatcher) {
|
||||||
|
SBdispatcher.on('stream-alert:gigantify_an_emote', data => {
|
||||||
|
spawnAnimatedImage(data.param1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnAnimatedImage(url) {
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = url;
|
||||||
|
img.classList.add("bigEmote", "hidden-scale");
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
const container = document.getElementById("bigEmoteContainer");
|
||||||
|
container.appendChild(img);
|
||||||
|
|
||||||
|
img.classList.remove("hidden-scale");
|
||||||
|
|
||||||
|
// Apparition de l'emote avec un zoom
|
||||||
|
img.classList.add("appear");
|
||||||
|
|
||||||
|
// Lancer le déplacement de l'emote après animation initiale
|
||||||
|
setTimeout(() => {
|
||||||
|
img.classList.remove("appear");
|
||||||
|
|
||||||
|
// Démarrer le rebond à partir de la position actuelle
|
||||||
|
const x = window.innerWidth / 2;
|
||||||
|
const y = window.innerHeight / 2;
|
||||||
|
|
||||||
|
startBouncing(img, x, y, true); // flag to keep transform translate
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function startBouncing(img, startX, startY) {
|
||||||
|
const imgSize = 112;
|
||||||
|
const halfSize = imgSize / 2;
|
||||||
|
const margin = 50;
|
||||||
|
|
||||||
|
const minX = margin + halfSize;
|
||||||
|
const maxX = window.innerWidth - margin - halfSize;
|
||||||
|
const minY = margin + halfSize;
|
||||||
|
const maxY = window.innerHeight - margin - halfSize;
|
||||||
|
|
||||||
|
let x = startX;
|
||||||
|
let y = startY;
|
||||||
|
|
||||||
|
const pageDiagonal = Math.sqrt(window.innerWidth ** 2 + window.innerHeight ** 2);
|
||||||
|
const totalDistance = 3 * pageDiagonal;
|
||||||
|
const duration = 30000; // 30s
|
||||||
|
const speed = totalDistance / duration;
|
||||||
|
|
||||||
|
const angle = getValidAngle();
|
||||||
|
let dx = Math.cos(angle) * speed;
|
||||||
|
let dy = Math.sin(angle) * speed;
|
||||||
|
|
||||||
|
let startTime = null;
|
||||||
|
let fadeOutStarted = false;
|
||||||
|
|
||||||
|
function getValidAngle() {
|
||||||
|
while (true) {
|
||||||
|
const angle = Math.random() * 2 * Math.PI;
|
||||||
|
const deg = angle * (180 / Math.PI);
|
||||||
|
const prohibited = [0, 90, 180, 270];
|
||||||
|
const tooClose = prohibited.some(a => {
|
||||||
|
const delta = Math.abs(deg - a) % 360;
|
||||||
|
return delta < 15 || delta > 345;
|
||||||
|
});
|
||||||
|
if (!tooClose) return angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate(timestamp) {
|
||||||
|
if (!startTime) startTime = timestamp;
|
||||||
|
const elapsed = timestamp - startTime;
|
||||||
|
const dt = timestamp - (animate.lastTime || timestamp);
|
||||||
|
animate.lastTime = timestamp;
|
||||||
|
|
||||||
|
x += dx * dt;
|
||||||
|
y += dy * dt;
|
||||||
|
|
||||||
|
if (x <= minX || x >= maxX) {
|
||||||
|
dx = -dx;
|
||||||
|
x = Math.max(minX, Math.min(x, maxX));
|
||||||
|
}
|
||||||
|
if (y <= minY || y >= maxY) {
|
||||||
|
dy = -dy;
|
||||||
|
y = Math.max(minY, Math.min(y, maxY));
|
||||||
|
}
|
||||||
|
|
||||||
|
img.style.left = `${x}px`;
|
||||||
|
img.style.top = `${y}px`;
|
||||||
|
img.style.transform = "translate(-50%, -50%)";
|
||||||
|
|
||||||
|
// Déclencher le fade-out à 29s
|
||||||
|
if (!fadeOutStarted && elapsed >= duration - 1000) {
|
||||||
|
fadeOutStarted = true;
|
||||||
|
img.classList.add("fade-out");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed < duration) {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
} else {
|
||||||
|
img.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
47
modules/big-emote/css/big-emote.css
Normal file
47
modules/big-emote/css/big-emote.css
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#bigEmoteContainer {
|
||||||
|
z-index: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#bigEmoteContainer .bigEmote {
|
||||||
|
position: absolute;
|
||||||
|
width: 112px;
|
||||||
|
height: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
#bigEmoteContainer .hidden-scale {
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
#bigEmoteContainer .appear {
|
||||||
|
animation: appear-grow-shrink 3s ease-out forwards;
|
||||||
|
}
|
||||||
|
#bigEmoteContainer .fade-out {
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
@keyframes appear-grow-shrink {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translate(-50%, -50%) scale(5);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translate(-50%, -50%) scale(5);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
modules/big-emote/css/big-emote.less
Normal file
54
modules/big-emote/css/big-emote.less
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// out: big-emote.css, sourcemap: false, compress: false
|
||||||
|
#bigEmoteContainer {
|
||||||
|
z-index: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.bigEmote {
|
||||||
|
position: absolute;
|
||||||
|
width: 112px;
|
||||||
|
height: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden-scale {
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appear {
|
||||||
|
animation: appear-grow-shrink 3s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-out {
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes appear-grow-shrink {
|
||||||
|
0% {
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: translate(-50%, -50%) scale(5);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translate(-50%, -50%) scale(5);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
modules/chat/assets/avatar.png
Normal file
BIN
modules/chat/assets/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
modules/chat/assets/role-broadcaster.png
Normal file
BIN
modules/chat/assets/role-broadcaster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
modules/chat/assets/role-moderator.png
Normal file
BIN
modules/chat/assets/role-moderator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
modules/chat/assets/role-vip.png
Normal file
BIN
modules/chat/assets/role-vip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
455
modules/chat/chat.js
Normal file
455
modules/chat/chat.js
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
//////////////////
|
||||||
|
// CHAT OVERLAY //
|
||||||
|
//////////////////
|
||||||
|
|
||||||
|
;(function() {
|
||||||
|
let sbShowChat = true;
|
||||||
|
const moduleUrl = window.getModuleUrl();
|
||||||
|
loadCSSModule('overlay-chat-css', moduleUrl + '/css/chat.css');
|
||||||
|
|
||||||
|
function initModule() {
|
||||||
|
const container = document.body;
|
||||||
|
|
||||||
|
if (!document.getElementById('chat-container')) {
|
||||||
|
const chatContainer = document.createElement('div');
|
||||||
|
chatContainer.className = 'chat-container';
|
||||||
|
chatContainer.id = 'chat-container';
|
||||||
|
|
||||||
|
const sticky = document.createElement('div');
|
||||||
|
sticky.id = 'stickyContainer';
|
||||||
|
const announceList = document.createElement('ul');
|
||||||
|
announceList.id = 'announcementList';
|
||||||
|
sticky.appendChild(announceList);
|
||||||
|
|
||||||
|
const messages = document.createElement('div');
|
||||||
|
messages.id = 'messagesContainer';
|
||||||
|
const messageList = document.createElement('ul');
|
||||||
|
messageList.id = 'messageList';
|
||||||
|
messages.appendChild(messageList);
|
||||||
|
|
||||||
|
chatContainer.append(sticky, messages);
|
||||||
|
container.appendChild(chatContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectTemplate(id, html) {
|
||||||
|
if (!document.getElementById(id)) {
|
||||||
|
const tpl = document.createElement('template');
|
||||||
|
tpl.id = id;
|
||||||
|
tpl.innerHTML = html.trim();
|
||||||
|
document.body.appendChild(tpl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
injectTemplate('messageTemplate', `
|
||||||
|
<div class="chat-message" id="messageContainer">
|
||||||
|
<span class="label username" id="username"></span>
|
||||||
|
<span class="label bottom-timestamp" id="timestamp"></span>
|
||||||
|
<span class="label badges" id="badges"></span>
|
||||||
|
<img class="avatar" id="avatar" alt="Avatar" />
|
||||||
|
<span class="role-icon" id="role"></span>
|
||||||
|
<div class="message-content" id="messageContent"></div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
injectTemplate('announcementTemplate', `
|
||||||
|
<div class="announcement-message" id="messageContainer">
|
||||||
|
<div class="message-content" id="messageContent"></div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
initModule();
|
||||||
|
|
||||||
|
StreamerBot.on('Twitch.ChatMessage', (data) => {
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.log(data.data);
|
||||||
|
TwitchChatMessage(data.data);
|
||||||
|
})
|
||||||
|
|
||||||
|
StreamerBot.on('Twitch.Announcement', (data) => {
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.log(data.data);
|
||||||
|
TwitchAnnouncement(data.data);
|
||||||
|
})
|
||||||
|
|
||||||
|
StreamerBot.on('Twitch.ChatMessageDeleted', (data) => {
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.log(data.data);
|
||||||
|
TwitchChatMessageDeleted(data.data);
|
||||||
|
})
|
||||||
|
|
||||||
|
StreamerBot.on('Twitch.UserBanned', (data) => {
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.log(data.data);
|
||||||
|
TwitchUserBanned(data.data);
|
||||||
|
})
|
||||||
|
|
||||||
|
StreamerBot.on('Twitch.UserTimedOut', (data) => {
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.log(data.data);
|
||||||
|
TwitchUserBanned(data.data);
|
||||||
|
})
|
||||||
|
|
||||||
|
StreamerBot.on('Twitch.ChatCleared', (data) => {
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.log(data.data);
|
||||||
|
TwitchChatCleared(data.data);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (window.SBdispatcher) {
|
||||||
|
SBdispatcher.on('chat-show', () => {
|
||||||
|
divShow("chat-container");
|
||||||
|
});
|
||||||
|
SBdispatcher.on('chat-hide', () => {
|
||||||
|
divHide("chat-container");
|
||||||
|
});
|
||||||
|
SBdispatcher.on('chat-toggle', () => {
|
||||||
|
divToggle("chat-container");
|
||||||
|
});
|
||||||
|
SBdispatcher.on('chat-left', () => {
|
||||||
|
divAnimate("chat-container", "chat-container-left");
|
||||||
|
});
|
||||||
|
SBdispatcher.on('chat-right', () => {
|
||||||
|
divAnimate("chat-container", "chat-container-right");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// CHAT OPTIONS //
|
||||||
|
//////////////////
|
||||||
|
|
||||||
|
const showPlatform = getBooleanParam("showPlatform", true);
|
||||||
|
const showAvatar = getBooleanParam("showAvatar", true);
|
||||||
|
const showTimestamps = getBooleanParam("showTimestamps", true);
|
||||||
|
const showBadges = getBooleanParam("showBadges", true);
|
||||||
|
const showPronouns = getBooleanParam("showPronouns", true);
|
||||||
|
const showUsername = getBooleanParam("showUsername", true);
|
||||||
|
const hideAfter = getIntParam("hideAfter", 900);
|
||||||
|
const hideAnnounceAfter = getIntParam("hideAfter", 180);
|
||||||
|
|
||||||
|
const ignoreChatters = urlParams.get("ignoreChatters") || "botarex";
|
||||||
|
const ignoreUserList = ignoreChatters.split(',').map(item => item.trim().toLowerCase()) || [];
|
||||||
|
|
||||||
|
const chatContainer = document.getElementById('chat-container');
|
||||||
|
|
||||||
|
if (!sbShowChat) {
|
||||||
|
chatContainer.style.opacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// CHAT CODE //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
function twitchChatDisplay() {
|
||||||
|
setTimeout(function () {
|
||||||
|
chatContainer.style.transition = "all 1s ease";
|
||||||
|
chatContainer.style.opacity = 1;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function twitchChatHide() {
|
||||||
|
setTimeout(function () {
|
||||||
|
chatContainer.style.transition = "all 1s ease";
|
||||||
|
chatContainer.style.opacity = 0;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function TwitchChatMessage(data) {
|
||||||
|
// Don't post messages starting with "!"
|
||||||
|
if (data.message.message.startsWith("!") && excludeCommands)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Don't post messages from users from the ignore list
|
||||||
|
if (ignoreUserList.includes(data.message.username.toLowerCase()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get a reference to the template
|
||||||
|
const template = document.getElementById('messageTemplate');
|
||||||
|
|
||||||
|
// Create a new instance of the template
|
||||||
|
const chatMessage = template.content.cloneNode(true);
|
||||||
|
|
||||||
|
// Get divs
|
||||||
|
const messageContainer = chatMessage.querySelector("#messageContainer");
|
||||||
|
const messageDiv = chatMessage.querySelector("#messageContent");
|
||||||
|
const usernameDiv = chatMessage.querySelector("#username");
|
||||||
|
const timestampDiv = chatMessage.querySelector("#timestamp");
|
||||||
|
const badgeListDiv = chatMessage.querySelector("#badges");
|
||||||
|
const avatarImg = chatMessage.querySelector("#avatar");
|
||||||
|
const roleIconDiv = chatMessage.querySelector("#role");
|
||||||
|
|
||||||
|
// Set timestamp
|
||||||
|
if (showTimestamps) {
|
||||||
|
timestampDiv.innerText = GetCurrentTimeFormatted();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the username info
|
||||||
|
if (showUsername) {
|
||||||
|
usernameDiv.innerText = data.message.displayName;
|
||||||
|
usernameDiv.style.color = data.message.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the message data
|
||||||
|
let message = data.message.message;
|
||||||
|
const messageColor = data.message.color;
|
||||||
|
messageDiv.innerText = message;
|
||||||
|
|
||||||
|
// Set the "action" color
|
||||||
|
if (data.message.isMe)
|
||||||
|
messageDiv.style.color = messageColor;
|
||||||
|
|
||||||
|
// Render badges
|
||||||
|
if (showBadges) {
|
||||||
|
badgeListDiv.innerHTML = "";
|
||||||
|
for (i in data.message.badges) {
|
||||||
|
const badge = new Image();
|
||||||
|
badge.src = data.message.badges[i].imageUrl;
|
||||||
|
badge.classList.add("badge");
|
||||||
|
badgeListDiv.appendChild(badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render emotes
|
||||||
|
for (i in data.emotes) {
|
||||||
|
const emoteElement = `<img src="${data.emotes[i].imageUrl}" class="emote"/>`;
|
||||||
|
const emoteName = EscapeRegExp(data.emotes[i].name);
|
||||||
|
|
||||||
|
let regexPattern = emoteName;
|
||||||
|
|
||||||
|
// Check if the emote name consists only of word characters (alphanumeric and underscore)
|
||||||
|
if (/^\w+$/.test(emoteName)) {
|
||||||
|
regexPattern = `\\b${emoteName}\\b`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For non-word emotes, ensure they are surrounded by non-word characters or boundaries
|
||||||
|
regexPattern = `(?:^|[^\\w])${emoteName}(?:$|[^\\w])`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(regexPattern, 'g');
|
||||||
|
messageDiv.innerHTML = messageDiv.innerHTML.replace(regex, emoteElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render cheermotes
|
||||||
|
for (i in data.cheerEmotes) {
|
||||||
|
const bits = data.cheerEmotes[i].bits;
|
||||||
|
const imageUrl = data.cheerEmotes[i].imageUrl;
|
||||||
|
const name = data.cheerEmotes[i].name;
|
||||||
|
const cheerEmoteElement = `<img src="${imageUrl}" class="emote"/>`;
|
||||||
|
const bitsElements = `<span class="bits">${bits}</span>`
|
||||||
|
messageDiv.innerHTML = messageDiv.innerHTML.replace(new RegExp(`\\b${name}${bits}\\b`, 'i'), cheerEmoteElement + bitsElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render avatars
|
||||||
|
if (showAvatar) {
|
||||||
|
const username = data.message.username;
|
||||||
|
const avatarURL = await GetAvatar(username);
|
||||||
|
avatarImg.src = avatarURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add User Role Style
|
||||||
|
switch (data.user.role) {
|
||||||
|
case 2:
|
||||||
|
// User VIP
|
||||||
|
messageContainer.classList.add("vip");
|
||||||
|
const roleVip = new Image();
|
||||||
|
roleVip.src = moduleUrl + "/assets/role-vip.png";
|
||||||
|
roleIconDiv.appendChild(roleVip);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// User Moderator
|
||||||
|
messageContainer.classList.add("mod");
|
||||||
|
const roleMod = new Image();
|
||||||
|
roleMod.src = moduleUrl + "/assets/role-moderator.png";
|
||||||
|
roleIconDiv.appendChild(roleMod);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// User Broadcaster
|
||||||
|
messageContainer.classList.add("streamer");
|
||||||
|
const roleStreamer = new Image();
|
||||||
|
roleStreamer.src = moduleUrl + "/assets/role-broadcaster.png";
|
||||||
|
roleIconDiv.appendChild(roleStreamer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
messageContainer.classList.add("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessage(chatMessage,data.message.msgId, data.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwitchChatMessageDeleted(data) {
|
||||||
|
const messageList = document.getElementById("messageList");
|
||||||
|
|
||||||
|
// Maintain a list of chat messages to delete
|
||||||
|
const messagesToRemove = [];
|
||||||
|
|
||||||
|
// ID of the message to remove
|
||||||
|
const messageId = data.messageId;
|
||||||
|
|
||||||
|
// Find the items to remove
|
||||||
|
for (let i = 0; i < messageList.children.length; i++) {
|
||||||
|
if (messageList.children[i].id === messageId) {
|
||||||
|
messagesToRemove.push(messageList.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the items
|
||||||
|
messagesToRemove.forEach(item => {
|
||||||
|
item.style.opacity = 0;
|
||||||
|
item.style.height = 0;
|
||||||
|
setTimeout(function () {
|
||||||
|
messageList.removeChild(item);
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwitchUserBanned(data) {
|
||||||
|
const messageList = document.getElementById("messageList");
|
||||||
|
|
||||||
|
// Maintain a list of chat messages to delete
|
||||||
|
const messagesToRemove = [];
|
||||||
|
|
||||||
|
// ID of the message to remove
|
||||||
|
const userId = data.user_id;
|
||||||
|
|
||||||
|
// Find the items to remove
|
||||||
|
for (let i = 0; i < messageList.children.length; i++) {
|
||||||
|
if (messageList.children[i].dataset.userId === userId) {
|
||||||
|
messagesToRemove.push(messageList.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the items
|
||||||
|
messagesToRemove.forEach(item => {
|
||||||
|
messageList.removeChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwitchChatCleared(data) {
|
||||||
|
const messageList = document.getElementById("messageList");
|
||||||
|
|
||||||
|
while (messageList.firstChild) {
|
||||||
|
messageList.removeChild(messageList.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function TwitchAnnouncement(data) {
|
||||||
|
// Get a reference to the template
|
||||||
|
const template = document.getElementById('announcementTemplate');
|
||||||
|
|
||||||
|
// Create a new instance of the template
|
||||||
|
const stickyMessage = template.content.cloneNode(true);
|
||||||
|
const messageDiv = stickyMessage.querySelector("#messageContent");
|
||||||
|
|
||||||
|
// Set the message data
|
||||||
|
let message = data.text;
|
||||||
|
messageDiv.innerText = message;
|
||||||
|
|
||||||
|
for (i in data.parts) {
|
||||||
|
if (data.parts[i].type == `emote`) {
|
||||||
|
const emoteElement = `<img src="${data.parts[i].imageUrl}" class="emote"/>`;
|
||||||
|
const emoteName = EscapeRegExp(data.parts[i].text);
|
||||||
|
|
||||||
|
let regexPattern = emoteName;
|
||||||
|
|
||||||
|
// Check if the emote name consists only of word characters (alphanumeric and underscore)
|
||||||
|
if (/^\w+$/.test(emoteName)) {
|
||||||
|
regexPattern = `\\b${emoteName}\\b`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For non-word emotes, ensure they are surrounded by non-word characters or boundaries
|
||||||
|
regexPattern = `(?:^|[^\\w])${emoteName}(?:$|[^\\w])`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(regexPattern, 'g');
|
||||||
|
messageDiv.innerHTML = messageDiv.innerHTML.replace(regex, emoteElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addAnnounce(stickyMessage, data.messageId, data.user.id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessage(messageElement,elementId,userId) {
|
||||||
|
const chatMessages = document.getElementById("messageList");
|
||||||
|
var lineItem = document.createElement('li');
|
||||||
|
lineItem.id = elementId;
|
||||||
|
lineItem.dataset.userId = userId;
|
||||||
|
lineItem.appendChild(messageElement);
|
||||||
|
|
||||||
|
chatMessages.appendChild(lineItem);
|
||||||
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||||
|
|
||||||
|
if (hideAfter > 0) {
|
||||||
|
setTimeout(function () {
|
||||||
|
lineItem.style.opacity = 0;
|
||||||
|
setTimeout(function () {
|
||||||
|
chatMessages.removeChild(lineItem);
|
||||||
|
}, 500);
|
||||||
|
}, hideAfter * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAnnounce(messageElement,elementId,userId) {
|
||||||
|
const chatMessages = document.getElementById("announcementList");
|
||||||
|
// Clear the previous announcements
|
||||||
|
while (chatMessages.firstChild) {
|
||||||
|
chatMessages.removeChild(chatMessages.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineItem = document.createElement('li');
|
||||||
|
lineItem.id = elementId;
|
||||||
|
lineItem.dataset.userId = userId;
|
||||||
|
lineItem.appendChild(messageElement);
|
||||||
|
|
||||||
|
chatMessages.appendChild(lineItem);
|
||||||
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||||
|
|
||||||
|
if (hideAnnounceAfter > 0) {
|
||||||
|
setTimeout(function () {
|
||||||
|
lineItem.style.opacity = 0;
|
||||||
|
setTimeout(function () {
|
||||||
|
chatMessages.removeChild(lineItem);
|
||||||
|
}, 500);
|
||||||
|
}, hideAnnounceAfter * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetCurrentTimeFormatted() {
|
||||||
|
const now = new Date();
|
||||||
|
let hours = now.getHours();
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
|
||||||
|
const formattedTime = `${hours}:${minutes}`;
|
||||||
|
return formattedTime;
|
||||||
|
|
||||||
|
// const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||||
|
|
||||||
|
// hours = hours % 12;
|
||||||
|
// hours = hours ? hours : 12; // the hour '0' should be '12'
|
||||||
|
|
||||||
|
// const formattedTime = `${hours}:${minutes} ${ampm}`;
|
||||||
|
// return formattedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function GetAvatar(username) {
|
||||||
|
if (avatarMap.has(username)) {
|
||||||
|
console.debug(`Avatar found for ${username}. Retrieving from hash map.`)
|
||||||
|
return avatarMap.get(username);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.debug(`No avatar found for ${username}. Retrieving from Decapi.`)
|
||||||
|
let response = await fetch('https://decapi.me/twitch/avatar/' + username);
|
||||||
|
let data = await response.text()
|
||||||
|
avatarMap.set(username, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function EscapeRegExp(string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||||
|
}
|
||||||
|
})();
|
||||||
167
modules/chat/css/chat.css
Normal file
167
modules/chat/css/chat.css
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
#chat-container {
|
||||||
|
z-index: 200;
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
width: 700px;
|
||||||
|
height: 750px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
#chat-container #stickyContainer {
|
||||||
|
width: 100%;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#chat-container #messagesContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#chat-container ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
#chat-container li {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#chat-container .chat-message {
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(51, 51, 51, 0.75);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 10px 42px 40px 24px;
|
||||||
|
color: #f0f0f0;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
#chat-container .chat-message.mod {
|
||||||
|
border-left: 4px solid #4aa3ff;
|
||||||
|
}
|
||||||
|
#chat-container .chat-message.streamer {
|
||||||
|
border-left: 4px solid #ff5f5f;
|
||||||
|
}
|
||||||
|
#chat-container .label {
|
||||||
|
position: absolute;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
background: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#chat-container .label.username {
|
||||||
|
top: 0;
|
||||||
|
left: 24px;
|
||||||
|
transform: translateY(-60%);
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #a0d2ff;
|
||||||
|
text-shadow: 1px 1px 5px rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
#chat-container .label.top-timestamp {
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
#chat-container .label.bottom-timestamp {
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
position: absolute;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
#chat-container .label.badges {
|
||||||
|
top: 0;
|
||||||
|
right: 48px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
#chat-container .label.badges img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
#chat-container .avatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
transform: translate(50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid #444;
|
||||||
|
}
|
||||||
|
#chat-container .role-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
#chat-container .role-icon img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
#chat-container .chat-message.message-content {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
#chat-container .announcement-message {
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(51, 51, 51, 0.75);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 5px 5px 10px 5px;
|
||||||
|
color: #f0f0f0;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
#chat-container .announcement-message.message-content {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
#chat-container .emote {
|
||||||
|
height: 1.5em;
|
||||||
|
margin: 1px;
|
||||||
|
transform: translate(0px, 0.4em);
|
||||||
|
}
|
||||||
|
@keyframes chat-container-left {
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes chat-container-right {
|
||||||
|
to {
|
||||||
|
transform: translateX(calc(100vw - 40px - 100%));
|
||||||
|
}
|
||||||
|
}
|
||||||
188
modules/chat/css/chat.less
Normal file
188
modules/chat/css/chat.less
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// out: chat.css, sourcemap: false, compress: false
|
||||||
|
|
||||||
|
#chat-container {
|
||||||
|
z-index: 200;
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
width: 700px;
|
||||||
|
height: 750px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
|
||||||
|
#stickyContainer {
|
||||||
|
width: 100%;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messagesContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message {
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(51, 51, 51, 0.75);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 10px 42px 40px 24px;
|
||||||
|
color: #f0f0f0;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.mod {
|
||||||
|
border-left: 4px solid #4aa3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.streamer {
|
||||||
|
border-left: 4px solid #ff5f5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 0.80rem;
|
||||||
|
background: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.username {
|
||||||
|
top: 0;
|
||||||
|
left: 24px;
|
||||||
|
transform: translateY(-60%);
|
||||||
|
font-size: 1.30rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #a0d2ff;
|
||||||
|
text-shadow: 1px 1px 5px rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.top-timestamp {
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.bottom-timestamp {
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
position: absolute;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.badges {
|
||||||
|
top: 0;
|
||||||
|
right: 48px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.badges img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
transform: translate(50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-icon img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message.message-content {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-message {
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(51, 51, 51, 0.75);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 5px 5px 10px 5px;
|
||||||
|
color: #f0f0f0;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-message.message-content {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emote {
|
||||||
|
height: 1.5em;
|
||||||
|
margin: 1px;
|
||||||
|
transform: translate(0px, 0.4em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes chat-container-left {
|
||||||
|
to { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes chat-container-right {
|
||||||
|
to { transform: translateX(calc(100vw - 40px - 100%)); }
|
||||||
|
}
|
||||||
24
modules/popup/css/popup.css
Normal file
24
modules/popup/css/popup.css
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#popupContainer {
|
||||||
|
z-index: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#popupContainer .overlay-popup {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 1em 1.5em;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
max-width: 20%;
|
||||||
|
text-align: center;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
#popupContainer .overlay-popup.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
31
modules/popup/css/popup.less
Normal file
31
modules/popup/css/popup.less
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// out: popup.css, sourcemap: false, compress: false
|
||||||
|
|
||||||
|
#popupContainer {
|
||||||
|
z-index: 400;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.overlay-popup {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 1em 1.5em;
|
||||||
|
background: rgba(0,0,0,0.75);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||||
|
max-width: 20%;
|
||||||
|
text-align: center;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-popup.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
98
modules/popup/popup.js
Normal file
98
modules/popup/popup.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
;(function() {
|
||||||
|
const moduleUrl = window.getModuleUrl();
|
||||||
|
loadCSSModule('overlay-popup-css', moduleUrl + '/css/popup.css');
|
||||||
|
|
||||||
|
function initModule() {
|
||||||
|
const container = document.getElementById('mainContainer') || document.body;
|
||||||
|
if (!document.getElementById('popupContainer')) {
|
||||||
|
const popupContainer = document.createElement('div');
|
||||||
|
popupContainer.id = 'popupContainer';
|
||||||
|
container.appendChild(popupContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initModule();
|
||||||
|
|
||||||
|
if (window.SBdispatcher) {
|
||||||
|
SBdispatcher.on('stream-popup', data => {
|
||||||
|
showPopup(
|
||||||
|
data.param1,
|
||||||
|
data.param2,
|
||||||
|
parseFloat(data.param3) || 3,
|
||||||
|
data.param4 != null ? parseFloat(data.param4) : -1,
|
||||||
|
data.param5 != null ? parseFloat(data.param5) : -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomPercent(min = 0, max = 100) {
|
||||||
|
const minClamped = Math.ceil(min);
|
||||||
|
const maxClamped = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (maxClamped - minClamped + 1)) + minClamped;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOverlapping(r1, r2) {
|
||||||
|
return !(r1.right < r2.left || r1.left > r2.right || r1.bottom < r2.top || r1.top > r2.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tableau global des popups actifs pour éviter le chevauchement
|
||||||
|
window._activePopups = window._activePopups || [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Affiche un popup dans #mainContainer (ou body si absent).
|
||||||
|
* @param {string} title Titre du popup
|
||||||
|
* @param {string} message Contenu HTML du message
|
||||||
|
* @param {number} delaySeconds Durée d'affichage en secondes
|
||||||
|
* @param {number|null} posX Position horizontale en %, -1 ou null = aléatoire
|
||||||
|
* @param {number|null} posY Position verticale en %, -1 ou null = aléatoire
|
||||||
|
*/
|
||||||
|
function showPopup(title, message, delaySeconds, posX = -1, posY = -1) {
|
||||||
|
const container = document.getElementById('popupContainer') || document.body;
|
||||||
|
const activePopups = window._activePopups;
|
||||||
|
const maxAttempts = 10;
|
||||||
|
let attempt = 0;
|
||||||
|
let popup, rect;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (popup) {
|
||||||
|
popup.remove();
|
||||||
|
}
|
||||||
|
// Calcul position (entre 10% et 90% si aléatoire)
|
||||||
|
const x = (posX == null || posX < 0) ? randomPercent(10, 90) : posX;
|
||||||
|
const y = (posY == null || posY < 0) ? randomPercent(10, 90) : posY;
|
||||||
|
|
||||||
|
// Création de l'élément popup
|
||||||
|
popup = document.createElement('div');
|
||||||
|
popup.classList.add('overlay-popup');
|
||||||
|
popup.style.position = 'absolute';
|
||||||
|
popup.style.left = `${x}%`;
|
||||||
|
popup.style.top = `${y}%`;
|
||||||
|
|
||||||
|
// Contenu
|
||||||
|
const header = document.createElement('h3');
|
||||||
|
header.innerText = title;
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.innerHTML = message;
|
||||||
|
popup.append(header, body);
|
||||||
|
|
||||||
|
container.appendChild(popup);
|
||||||
|
rect = popup.getBoundingClientRect();
|
||||||
|
attempt++;
|
||||||
|
} while (
|
||||||
|
attempt < maxAttempts &&
|
||||||
|
activePopups.some(other => isOverlapping(rect, other.getBoundingClientRect()))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Conserve ce popup dans la liste des actifs
|
||||||
|
activePopups.push(popup);
|
||||||
|
|
||||||
|
// Suppression après délai et retrait de la liste
|
||||||
|
setTimeout(() => {
|
||||||
|
popup.classList.add('fade-out');
|
||||||
|
setTimeout(() => {
|
||||||
|
popup.remove();
|
||||||
|
const idx = activePopups.indexOf(popup);
|
||||||
|
if (idx !== -1) activePopups.splice(idx, 1);
|
||||||
|
}, 500);
|
||||||
|
}, delaySeconds * 1000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
BIN
modules/scrolling-banner/css/fonts/led_counter-7.ttf
Normal file
BIN
modules/scrolling-banner/css/fonts/led_counter-7.ttf
Normal file
Binary file not shown.
BIN
modules/scrolling-banner/css/fonts/led_counter-7_italic.ttf
Normal file
BIN
modules/scrolling-banner/css/fonts/led_counter-7_italic.ttf
Normal file
Binary file not shown.
BIN
modules/scrolling-banner/css/fonts/led_counter-7_screen.gif
Normal file
BIN
modules/scrolling-banner/css/fonts/led_counter-7_screen.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
35
modules/scrolling-banner/css/scrolling-banner.css
Normal file
35
modules/scrolling-banner/css/scrolling-banner.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'led_counter-7';
|
||||||
|
src: url('fonts/led_counter-7.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
#scrollingBannerContainer {
|
||||||
|
z-index: 500;
|
||||||
|
position: fixed;
|
||||||
|
top: 30px;
|
||||||
|
left: -100%;
|
||||||
|
width: 70%;
|
||||||
|
height: 75px;
|
||||||
|
background: linear-gradient(to right, black, #800000);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: polygon(0 0, 100% 0, calc(100% - 30px) 100%, 0 100%);
|
||||||
|
}
|
||||||
|
#scrollingBannerContainer #scrollingBannerMessage {
|
||||||
|
width: 100%;
|
||||||
|
color: limegreen;
|
||||||
|
font-family: 'led_counter-7', monospace;
|
||||||
|
font-size: 64px;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
animation: scrolling 10s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes scrolling {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
modules/scrolling-banner/css/scrolling-banner.less
Normal file
40
modules/scrolling-banner/css/scrolling-banner.less
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// out: scrolling-banner.css, sourcemap: false, compress: false
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'led_counter-7';
|
||||||
|
src: url('fonts/led_counter-7.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
#scrollingBannerContainer {
|
||||||
|
z-index: 500;
|
||||||
|
position: fixed;
|
||||||
|
top: 30px;
|
||||||
|
left: -100%;
|
||||||
|
width: 70%;
|
||||||
|
height: 75px;
|
||||||
|
background: linear-gradient(to right, black, #800000);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
clip-path: polygon( 0 0, 100% 0, calc(100% - 30px) 100%, 0 100% );
|
||||||
|
|
||||||
|
#scrollingBannerMessage {
|
||||||
|
width: 100%;
|
||||||
|
color: limegreen;
|
||||||
|
font-family: 'led_counter-7', monospace;
|
||||||
|
font-size: 64px;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
animation: scrolling 10s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scrolling {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
modules/scrolling-banner/scrolling-banner.js
Normal file
50
modules/scrolling-banner/scrolling-banner.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
;(function() {
|
||||||
|
const moduleUrl = window.getModuleUrl();
|
||||||
|
loadCSSModule('overlay-scrollingbanner-css', moduleUrl + '/css/scrolling-banner.css');
|
||||||
|
|
||||||
|
function initModule() {
|
||||||
|
const container = document.getElementById('mainContainer') || document.body;
|
||||||
|
if (!document.getElementById('scrollingBannerContainer')) {
|
||||||
|
const scrollingBannerContainer = document.createElement('div');
|
||||||
|
scrollingBannerContainer.id = 'scrollingBannerContainer';
|
||||||
|
|
||||||
|
const scrollingBannerMessage = document.createElement('div');
|
||||||
|
scrollingBannerMessage.id = 'scrollingBannerMessage';
|
||||||
|
scrollingBannerMessage.innerText = 'Bienvenue sur le stream !'
|
||||||
|
|
||||||
|
scrollingBannerContainer.appendChild(scrollingBannerMessage);
|
||||||
|
container.appendChild(scrollingBannerContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initModule();
|
||||||
|
|
||||||
|
if (window.SBdispatcher) {
|
||||||
|
SBdispatcher.on('stream-scrollingbanner', data => {
|
||||||
|
showAnnounce(data.message);
|
||||||
|
});
|
||||||
|
SBdispatcher.on('stream-scrollingbanner-hide', () => {
|
||||||
|
hideAnnounce();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAnnounce(message = "") {
|
||||||
|
const container = document.getElementById('scrollingBannerContainer');
|
||||||
|
const announceText = document.getElementById('scrollingBannerMessage');
|
||||||
|
|
||||||
|
if (message.length > 0) {
|
||||||
|
announceText.innerText = message;
|
||||||
|
}
|
||||||
|
container._positionDivHandler = () => positionDiv(container);
|
||||||
|
container.addEventListener('animationend', container._positionDivHandler);
|
||||||
|
container.style.animation = "slide-in-left 2s ease forwards";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAnnounce() {
|
||||||
|
const container = document.getElementById('scrollingBannerContainer');
|
||||||
|
container._positionDivHandler = () => positionDiv(container);
|
||||||
|
container.addEventListener('animationend', container._positionDivHandler);
|
||||||
|
container.style.animation = "slide-out-left 2s ease forwards";
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
248
overlay-core.js
Normal file
248
overlay-core.js
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
////////////////
|
||||||
|
// PARAMETERS //
|
||||||
|
////////////////
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
|
||||||
|
const sbDebugMode = urlParams.get("debug") || false;
|
||||||
|
const sbServerAddress = urlParams.get("address") || "127.0.0.1";
|
||||||
|
const sbServerPort = urlParams.get("port") || "8080";
|
||||||
|
const sbServerPassword = urlParams.get("password") || "VerySecretPassword";
|
||||||
|
|
||||||
|
const avatarMap = new Map();
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// STREAMER.BOT CLIENT //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
const StreamerBot = new StreamerbotClient({
|
||||||
|
host: sbServerAddress,
|
||||||
|
port: sbServerPort,
|
||||||
|
password: sbServerPassword,
|
||||||
|
|
||||||
|
onConnect: (data) => {
|
||||||
|
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
|
||||||
|
if (sbDebugMode)
|
||||||
|
console.debug(data);
|
||||||
|
SetConnectionStatus(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDisconnect: () => {
|
||||||
|
console.error(`Streamer.bot disconnected from ${sbServerAddress}:${sbServerPort}`)
|
||||||
|
SetConnectionStatus(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// STREAMER.BOT STATUS //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
function SetConnectionStatus(connected) {
|
||||||
|
let statusContainer = document.getElementById("statusContainer");
|
||||||
|
if (connected) {
|
||||||
|
statusContainer.style.background = "#2FB774";
|
||||||
|
statusContainer.innerText = "Connected!";
|
||||||
|
statusContainer.style.opacity = 1;
|
||||||
|
setTimeout(() => {
|
||||||
|
statusContainer.style.transition = "all 2s ease";
|
||||||
|
statusContainer.style.opacity = 0;
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
statusContainer.style.background = "#D12025";
|
||||||
|
statusContainer.innerText = "Connecting...";
|
||||||
|
statusContainer.style.transition = "";
|
||||||
|
statusContainer.style.opacity = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
// STREAMER.BOT DISPATCHER //
|
||||||
|
/////////////////////////////
|
||||||
|
const SBdispatcher = {
|
||||||
|
_handlers: {},
|
||||||
|
on(eventName, handler) {
|
||||||
|
if (!this._handlers[eventName]) this._handlers[eventName] = []
|
||||||
|
this._handlers[eventName].push(handler)
|
||||||
|
},
|
||||||
|
emit(eventName, data) {
|
||||||
|
(this._handlers[eventName] || []).forEach(h => {
|
||||||
|
try { h(data) }
|
||||||
|
catch(e) { console.error(`Error in handler for ${eventName}:`, e) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.SBdispatcher = SBdispatcher
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
// STREAMER.BOT SUBSCRIPTION //
|
||||||
|
///////////////////////////////
|
||||||
|
StreamerBot.on('General.Custom', (data) => {
|
||||||
|
const d = data.data
|
||||||
|
if (sbDebugMode) console.log(d)
|
||||||
|
if (d.origin !== "antarex-overlay") return
|
||||||
|
SBdispatcher.emit(d.action, d)
|
||||||
|
if (d.type) {
|
||||||
|
SBdispatcher.emit(`${d.action}:${d.type}`, d)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// OVERLAY FUNCTIONS //
|
||||||
|
///////////////////////
|
||||||
|
function loadCSSModule(id, href) {
|
||||||
|
if (document.getElementById(id)) return;
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.id = id;
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = href;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleUrl() {
|
||||||
|
const thisScript = document.currentScript;
|
||||||
|
if (!thisScript) {
|
||||||
|
console.warn('getModuleUrl: document.currentScript non disponible');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const url = thisScript.src;
|
||||||
|
return url.substring(0, url.lastIndexOf('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBooleanParam(paramName, defaultValue) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const paramValue = urlParams.get(paramName);
|
||||||
|
|
||||||
|
if (paramValue === null) {
|
||||||
|
return defaultValue; // Parameter not found
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowercaseValue = paramValue.toLowerCase(); // Handle case-insensitivity
|
||||||
|
|
||||||
|
if (lowercaseValue === 'true') {
|
||||||
|
return true;
|
||||||
|
} else if (lowercaseValue === 'false') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return paramValue; // Return original string if not 'true' or 'false'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIntParam(paramName, defaultValue) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const paramValue = urlParams.get(paramName);
|
||||||
|
|
||||||
|
if (paramValue === null) {
|
||||||
|
return defaultValue; // or undefined, or a default value, depending on your needs
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(paramValue);
|
||||||
|
|
||||||
|
const intValue = parseInt(paramValue, 10); // Parse as base 10 integer
|
||||||
|
|
||||||
|
if (isNaN(intValue)) {
|
||||||
|
return null; // or handle the error in another way, e.g., throw an error
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function divShow(divId) {
|
||||||
|
const div = document.getElementById(divId);
|
||||||
|
if (div) {
|
||||||
|
div.style.transition = "all 1s ease";
|
||||||
|
div.style.opacity = 1;
|
||||||
|
} else {
|
||||||
|
console.error(`Element with ID ${divId} not found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function divHide(divId) {
|
||||||
|
const div = document.getElementById(divId);
|
||||||
|
if (div) {
|
||||||
|
div.style.transition = "all 1s ease";
|
||||||
|
div.style.opacity = 0;
|
||||||
|
} else {
|
||||||
|
console.error(`Element with ID ${divId} not found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function divToggle(divId) {
|
||||||
|
const div = document.getElementById(divId);
|
||||||
|
if (div) {
|
||||||
|
div.style.transition = "all 1s ease";
|
||||||
|
|
||||||
|
const currentOpacity = window.getComputedStyle(div).opacity;
|
||||||
|
|
||||||
|
if (parseFloat(currentOpacity) === 0) {
|
||||||
|
div.style.opacity = 1;
|
||||||
|
} else {
|
||||||
|
div.style.opacity = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`Element with ID ${divId} not found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function divAnimate(divId, animationName) {
|
||||||
|
const div = document.getElementById(divId);
|
||||||
|
if (div) {
|
||||||
|
function positionDiv() {
|
||||||
|
finalizeTransformPosition(div);
|
||||||
|
div.removeEventListener('animationend', positionDiv);
|
||||||
|
}
|
||||||
|
div.addEventListener('animationend', positionDiv);
|
||||||
|
div.style.animation = `${animationName} 2s ease forwards`;
|
||||||
|
} else {
|
||||||
|
console.error(`Element with ID ${divId} not found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizePosition(frame) {
|
||||||
|
const rect = frame.getBoundingClientRect();
|
||||||
|
const parentRect = frame.offsetParent
|
||||||
|
? frame.offsetParent.getBoundingClientRect()
|
||||||
|
: { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight };
|
||||||
|
|
||||||
|
const fromLeft = rect.left - parentRect.left;
|
||||||
|
const fromRight = parentRect.right - rect.right;
|
||||||
|
const fromTop = rect.top - parentRect.top;
|
||||||
|
const fromBottom = parentRect.bottom - rect.bottom;
|
||||||
|
|
||||||
|
if (fromRight <= fromLeft) {
|
||||||
|
// Closer to right edge → use right
|
||||||
|
frame.style.right = `${fromRight}px`;
|
||||||
|
frame.style.left = '';
|
||||||
|
} else {
|
||||||
|
frame.style.left = `${fromLeft}px`;
|
||||||
|
frame.style.right = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromBottom <= fromTop) {
|
||||||
|
frame.style.bottom = `${fromBottom}px`;
|
||||||
|
frame.style.top = '';
|
||||||
|
} else {
|
||||||
|
frame.style.top = `${fromTop}px`;
|
||||||
|
frame.style.bottom = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.style.animation = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeTransformPosition(div) {
|
||||||
|
const computedStyle = window.getComputedStyle(div);
|
||||||
|
const transform = computedStyle.transform;
|
||||||
|
div.style.transform = transform;
|
||||||
|
div.style.animation = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionDiv(container) {
|
||||||
|
finalizePosition(container);
|
||||||
|
container.removeEventListener('animationend', container._positionDivHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.loadCSSModule = loadCSSModule;
|
||||||
|
window.getModuleUrl = getModuleUrl;
|
||||||
|
window.getBooleanParam = getBooleanParam;
|
||||||
69
overlay.css
Normal file
69
overlay.css
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: transparent;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#statusContainer {
|
||||||
|
z-index: 100;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #D12025;
|
||||||
|
color: white;
|
||||||
|
padding: 5px 20px;
|
||||||
|
border-radius: 7px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
#mainContainer {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-left {
|
||||||
|
to { left: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-out-left {
|
||||||
|
to { left: -100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-right {
|
||||||
|
to {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-out-right {
|
||||||
|
to {
|
||||||
|
right: -100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hideIn {
|
||||||
|
99% {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fadeOut {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
overlay.html
Normal file
20
overlay.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Antarex | Streamer.bot Overlay</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="overlay.css">
|
||||||
|
<script type="text/javascript" src="https://unpkg.com/@streamerbot/client/dist/streamerbot-client.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="statusContainer">Connecting...</div>
|
||||||
|
<div id="mainContainer"></div>
|
||||||
|
</body>
|
||||||
|
<script type="text/javascript" src="overlay-core.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/popup/popup.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/chat/chat.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/scrolling-banner/scrolling-banner.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/alert-banner/alert-banner.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/big-emote/big-emote.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/alert-video/alert-video.js"></script>
|
||||||
|
</html>
|
||||||
31
websocket-streamerbot.cs
Normal file
31
websocket-streamerbot.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
public class CPHInline
|
||||||
|
{
|
||||||
|
public bool Execute()
|
||||||
|
{
|
||||||
|
Dictionary<string, object> message = new Dictionary<string, object>();
|
||||||
|
Dictionary<string, object> content = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
message.Add("origin",args.ContainsKey("wsOrigin") ? args["wsOrigin"].ToString() : "antarex-overlay");
|
||||||
|
message.Add("action",args.ContainsKey("wsAction") ? args["wsAction"].ToString() : "none");
|
||||||
|
|
||||||
|
if (args.ContainsKey("wsType")) message.Add("type",args["wsType"].ToString());
|
||||||
|
if (args.ContainsKey("user")) message.Add("user",args["user"].ToString());
|
||||||
|
if (args.ContainsKey("userName")) message.Add("username",args["userName"].ToString());
|
||||||
|
if (args.ContainsKey("userId")) message.Add("userid",args["userId"].ToString());
|
||||||
|
if (args.ContainsKey("message")) message.Add("message",args["message"].ToString());
|
||||||
|
|
||||||
|
if (args.ContainsKey("wsParam1")) message.Add("param1",args["wsParam1"]);
|
||||||
|
if (args.ContainsKey("wsParam2")) message.Add("param2",args["wsParam2"]);
|
||||||
|
if (args.ContainsKey("wsParam3")) message.Add("param3",args["wsParam3"]);
|
||||||
|
if (args.ContainsKey("wsParam4")) message.Add("param4",args["wsParam4"]);
|
||||||
|
if (args.ContainsKey("wsParam5")) message.Add("param5",args["wsParam5"]);
|
||||||
|
|
||||||
|
var jsonMessage = JsonConvert.SerializeObject(message);
|
||||||
|
CPH.WebsocketBroadcastJson(jsonMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user