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