Add files via upload
This commit is contained in:
685
js/app-mockup.js
Normal file
685
js/app-mockup.js
Normal file
@@ -0,0 +1,685 @@
|
||||
/* ----------------------- */
|
||||
/* MOCKUP SYSTEM */
|
||||
/* ----------------------- */
|
||||
|
||||
let mockupInterval = null;
|
||||
let isMockupActive = false;
|
||||
const mockupDelay = 2500; // 2 seconds between events
|
||||
let mockupConnectionState = false; // Track mock connection state
|
||||
|
||||
// Sample data for mockup events
|
||||
const mockData = {
|
||||
avatars: [
|
||||
'https://static-cdn.jtvnw.net/user-default-pictures-uv/dbdc9198-def8-11e9-8681-784f43822e80-profile_image-300x300.png',
|
||||
'https://static-cdn.jtvnw.net/user-default-pictures-uv/13e5fa74-defa-11e9-809c-784f43822e80-profile_image-300x300.png',
|
||||
'https://static-cdn.jtvnw.net/user-default-pictures-uv/215b7342-def9-11e9-9a66-784f43822e80-profile_image-300x300.png',
|
||||
'https://static-cdn.jtvnw.net/user-default-pictures-uv/ce57700a-def9-11e9-842d-784f43822e80-profile_image-300x300.png'
|
||||
],
|
||||
users: [
|
||||
{ name: 'Ninja' },
|
||||
{ name: 'SypherPK' },
|
||||
{ name: 'CouRageJD', },
|
||||
{ name: 'Gaules' },
|
||||
{ name: 'Nadeshot' },
|
||||
{ name: 'WILDCAT', },
|
||||
{ name: 'NickEh30' },
|
||||
{ name: 'LEGIQN' },
|
||||
{ name: 'moistcr1tikal' },
|
||||
{ name: 'FISHNOTHING' },
|
||||
{ name: 'smii7y' },
|
||||
{ name: 'kinggothalion', },
|
||||
{ name: 'harrisheller' },
|
||||
{ name: 'kaicenat' },
|
||||
{ name: 'caseoh_' }
|
||||
],
|
||||
messages: [
|
||||
"Hey everyone! How's the stream going?",
|
||||
"This game looks awesome!",
|
||||
"LOL that was hilarious",
|
||||
"GG WP!",
|
||||
"When are you playing Minecraft next?",
|
||||
"Love the new overlay!",
|
||||
"First time watching, this is great!",
|
||||
"Can you explain that strategy again?",
|
||||
"Greetings from Germany!",
|
||||
"What's your favorite game?",
|
||||
|
||||
"Let's gooooo!",
|
||||
"That clutch tho 🔥",
|
||||
"I can't stop laughing 😂",
|
||||
"You just destroyed them!",
|
||||
"What a play!",
|
||||
"Did anyone else see that??",
|
||||
"Streamer luck confirmed 😆",
|
||||
"Please do that move again!",
|
||||
"Bro you cracked?",
|
||||
"This chat is wild tonight",
|
||||
|
||||
"Can you shout out my friend?",
|
||||
"What setup are you using?",
|
||||
"Mic sounds super clean",
|
||||
"W stream fr",
|
||||
"Hydrate check! 💧",
|
||||
"Pet the dog on stream pls",
|
||||
"That reaction time 😳",
|
||||
"You're my comfort streamer 🧡",
|
||||
"This game brings back memories",
|
||||
"Why is this so intense omg",
|
||||
|
||||
"Chat, what’s your favorite snack?",
|
||||
"That edit was slick",
|
||||
"Mobile gang where you at?",
|
||||
"Sheeesh 🥶",
|
||||
"This is better than Netflix",
|
||||
"How long you been streaming?",
|
||||
"Can we get some hype in the chat?",
|
||||
"I wish I was this good",
|
||||
"Can mods ban that guy?",
|
||||
"Backseat gaming intensifies 😅",
|
||||
|
||||
"This reminds me of old-school Twitch",
|
||||
"Nice aim bot... jk (I hope)",
|
||||
"Wanna 1v1?",
|
||||
"The vibes are immaculate today",
|
||||
"Who else is vibing with the music?",
|
||||
"That moment needs to be clipped",
|
||||
"Streamer out here cooking 🔥",
|
||||
"Yo, this community is chill",
|
||||
"Day made. Thanks for the laughs!",
|
||||
"Okay but that was actually insane"
|
||||
],
|
||||
|
||||
emotes: [
|
||||
{
|
||||
"id": "emotesv2_9eade28238d64e83b0219a9025d4692d",
|
||||
"type": "Twitch",
|
||||
"name": "AnotherRecord",
|
||||
"startIndex": 24,
|
||||
"endIndex": 36,
|
||||
"imageUrl": "https://static-cdn.jtvnw.net/emoticons/v2/emotesv2_9eade28238d64e83b0219a9025d4692d/default/dark/2.0"
|
||||
},
|
||||
{
|
||||
"id": "301428702",
|
||||
"type": "Twitch",
|
||||
"name": "BOP",
|
||||
"startIndex": 20,
|
||||
"endIndex": 22,
|
||||
"imageUrl": "https://static-cdn.jtvnw.net/emoticons/v2/301428702/default/dark/2.0"
|
||||
},
|
||||
{
|
||||
"id": "354",
|
||||
"type": "Twitch",
|
||||
"name": "4Head",
|
||||
"startIndex": 14,
|
||||
"endIndex": 18,
|
||||
"imageUrl": "https://static-cdn.jtvnw.net/emoticons/v2/354/default/dark/2.0"
|
||||
},
|
||||
{
|
||||
"id": "425618",
|
||||
"type": "Twitch",
|
||||
"name": "LUL",
|
||||
"startIndex": 10,
|
||||
"endIndex": 12,
|
||||
"imageUrl": "https://static-cdn.jtvnw.net/emoticons/v2/425618/default/dark/2.0"
|
||||
},
|
||||
{
|
||||
"id": "305954156",
|
||||
"type": "Twitch",
|
||||
"name": "PogChamp",
|
||||
"startIndex": 0,
|
||||
"endIndex": 7,
|
||||
"imageUrl": "https://static-cdn.jtvnw.net/emoticons/v2/305954156/default/dark/2.0"
|
||||
}
|
||||
],
|
||||
emotesInsideMessages: [
|
||||
'PogChamp',
|
||||
'LUL',
|
||||
'BOP',
|
||||
'4Head',
|
||||
'AnotherRecord'
|
||||
],
|
||||
rewards: [
|
||||
'Highlight My Message',
|
||||
'Play Sound Effect',
|
||||
'Choose Next Game',
|
||||
'Song Request',
|
||||
'Dad Joke',
|
||||
'Hydration Check'
|
||||
],
|
||||
announcements: [
|
||||
'Welcome to the stream everyone!',
|
||||
'Don\'t forget to follow for stream notifications!',
|
||||
'We\'re going to raid someone awesome after this game!',
|
||||
'Thanks for all the subs today!',
|
||||
'New emotes coming next week!'
|
||||
],
|
||||
badges: [
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/28ac9d77-bba9-4281-aba9-716081aee210/3" },
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/c4a29737-e8a5-4420-917a-314a447f083e/3" },
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/ae1c6c62-c057-4fad-a1d4-663bf988701f/3" },
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/bbbe0db0-a598-423e-86d0-f9fb98ca1933/3" },
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/2de71f4f-b152-4308-a426-127a4cf8003a/3" },
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/4149750c-9582-4515-9e22-da7d5437643b/3" },
|
||||
{ "imageUrl": "https://static-cdn.jtvnw.net/badges/v1/5864739a-5e58-4623-9450-a2c0555ef90b/3" }
|
||||
],
|
||||
superstickers: [
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/G2OgWJkuvSullUPp2i09zG_WR0IpQu-6Ti4pFXn_FJ1OkR6zU5GdiP9cBavimQopETyojInsRCe8uefjJBqn=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/-21C0x6zYcDxpJVYKl8CCKroyjW2Hdvh2FWBipCTFhonaPy2cSJZWTGvmjsoBJu-LedOHQrw1Qu7TYXxIlxv=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/kpwCxm65pv0p2YMunCEHaYcD1A0TnwTg4uSJMDsBriu6cZSGOAjXw_CPvV5PajvWEq1LANypR_WHRpA7HU8=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/y4t53UFh4eWO9FmuXgELtXn0cWsZEAJOWCExbumx2vcNclm2VYJkd4Omo7lKLxOg78zaXBmukrN0ONPRDwM=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/aQTC6r0gjuns5TwJTMoA_mqOH3mizXxzlAJqh_CpLx8lWyKiUgS_EjTATRTX0Qzm8MlZyXAg7r6kFzlH8HgS=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/oUIeg07YsvEuUcF7wOg6U3o1dOCANoBWuF1DYr2jGPFOyQ-bEFRiFm-6gU3urJPaX_AqtZgsNpGb0KNimA=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/gauEuKs0cTW_YrtkKit45UrShY7KuK2-Kh9RV3H3Eirtx2KY6PHLeaDHbFz-l9OGMYISF0F57Wk2lzTiHw=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/PQlPxOdeVk_oY3D_Ow0JRXvV3AbVIEoApzvenbfgsAHoLF4_EgxCV7Dsd-kqMsCAqhHhNG0vUY2Ssoa03iW_=s148-rwa" },
|
||||
{ "imageUrl": "https://lh3.googleusercontent.com/Cmmj_3s8DpgpuHdOhUmIZQU0Gmex9IISD2SNk4UQY-HA1jHfSPYCk6-gZ-PEpLKGHgyEfZNRCiAV_lHC_Q=s148-rwa" },
|
||||
],
|
||||
tiktokGifts: [
|
||||
{ name: "Rose", coins: 1 },
|
||||
{ name: "Finger Heart", coins: 5 },
|
||||
{ name: "TikTok", coins: 10 },
|
||||
{ name: "Confetti", coins: 100 },
|
||||
{ name: "Galaxy", coins: 1000 }
|
||||
]
|
||||
};
|
||||
|
||||
// Function to generate a random mockup event
|
||||
function generateMockEvent() {
|
||||
const eventTypes = [
|
||||
'twitch-chat', 'twitch-chat', 'twitch-chat', 'twitch-chat', 'twitch-chat',
|
||||
|
||||
'twitch-follow', 'twitch-bits', 'twitch-sub', 'twitch-resub',
|
||||
'twitch-giftsub', 'twitch-giftbomb', 'twitch-raid',
|
||||
'twitch-announcement', 'twitch-reward', 'twitch-gigantifyemote',
|
||||
|
||||
'youtube-chat', 'youtube-chat', 'youtube-chat', 'youtube-chat', 'youtube-chat',
|
||||
'youtube-superchat', 'youtube-supersticker', 'youtube-newsponsor', 'youtube-membermilestone', 'youtube-membergift',
|
||||
|
||||
'tiktok-chat', 'tiktok-chat', 'tiktok-chat', 'tiktok-chat', 'tiktok-chat',
|
||||
'tiktok-follow', 'tiktok-sub', 'tiktok-gift',
|
||||
|
||||
'streamlabs-tip', 'streamelements-tip',
|
||||
];
|
||||
|
||||
// Select random event type and user
|
||||
|
||||
const fakeAvatar = mockData.avatars[Math.floor(Math.random() * mockData.avatars.length)];
|
||||
const eventType = eventTypes[Math.floor(Math.random() * eventTypes.length)];
|
||||
const user = mockData.users[Math.floor(Math.random() * mockData.users.length)];
|
||||
const announcement = mockData.announcements[Math.floor(Math.random() * mockData.announcements.length)];
|
||||
const reward = mockData.rewards[Math.floor(Math.random() * mockData.rewards.length)];
|
||||
const emote = mockData.emotes[Math.floor(Math.random() * mockData.emotes.length)];
|
||||
const emotesInsideMessages = mockData.emotesInsideMessages[Math.floor(Math.random() * mockData.emotesInsideMessages.length)];
|
||||
const messagetext = mockData.messages[Math.floor(Math.random() * mockData.messages.length)];
|
||||
const messageId = randomString(40);
|
||||
|
||||
const shuffledBadges = [...mockData.badges].sort(() => Math.random() - 0.5);
|
||||
const badgeCount = Math.floor(Math.random() * 3) + 1; // 1 to 3
|
||||
const badgeschosen = shuffledBadges.slice(0, badgeCount);
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * mockData.superstickers.length);
|
||||
const randomStickerUrl = mockData.superstickers[randomIndex].imageUrl;
|
||||
|
||||
const tiktokGift = mockData.tiktokGifts[Math.floor(Math.random() * mockData.tiktokGifts.length)];
|
||||
|
||||
const firstMessage = Math.random() < 0.1;
|
||||
|
||||
switch(eventType) {
|
||||
case 'twitch-chat' :
|
||||
|
||||
var data = {
|
||||
emotes: mockData.emotes,
|
||||
message: {
|
||||
username: user.name.toLowerCase(),
|
||||
color: randomColor(),
|
||||
displayName: user.name,
|
||||
message: messagetext + ' ' + emotesInsideMessages,
|
||||
firstMessage: firstMessage,
|
||||
badges: badgeschosen,
|
||||
},
|
||||
messageId: messageId,
|
||||
};
|
||||
|
||||
const ifHasReply = Math.random() < 0.05;
|
||||
if (ifHasReply) {
|
||||
data.message.isReply = true;
|
||||
var replier = mockData.users[Math.floor(Math.random() * mockData.users.length)];
|
||||
data.message.reply = {
|
||||
userName: replier.name,
|
||||
msgBody: mockData.messages[Math.floor(Math.random() * mockData.messages.length)]
|
||||
};
|
||||
}
|
||||
|
||||
const ifHasShared = Math.random() < 0.05;
|
||||
if (ifHasShared) {
|
||||
data.message.isSharedChat = true;
|
||||
var sharedParentUser = mockData.users[Math.floor(Math.random() * mockData.users.length)];
|
||||
|
||||
data.sharedChat = {
|
||||
sourceRoom: {
|
||||
name: sharedParentUser.name
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
twitchChatMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-follow' :
|
||||
|
||||
var data = {
|
||||
user_id: user.name.toLowerCase(),
|
||||
user_name: user.name,
|
||||
};
|
||||
|
||||
twitchFollowMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-bits' :
|
||||
|
||||
var data = {
|
||||
|
||||
emotes: mockData.emotes,
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
message: {
|
||||
bits: Math.floor(Math.random() * 10000) + 1,
|
||||
message: messagetext,
|
||||
},
|
||||
messageId: messageId,
|
||||
};
|
||||
|
||||
twitchBitsMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-sub' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
duration_months: 1,
|
||||
sub_tier: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
isPrime: Math.random() < 0.1,
|
||||
text: messagetext
|
||||
}
|
||||
|
||||
twitchSubMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-resub' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
cumulativeMonths: Math.floor(Math.random() * 50) + 1,
|
||||
subTier: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
isPrime: Math.random() < 0.1,
|
||||
text: messagetext
|
||||
}
|
||||
|
||||
twitchReSubMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-giftsub' :
|
||||
|
||||
var gifterUser = mockData.users[Math.floor(Math.random() * mockData.users.length)];
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
recipient: { name: gifterUser.name },
|
||||
durationMonths: Math.floor(Math.random() * 50) + 1,
|
||||
subTier: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
cumlativeTotal: Math.floor(Math.random() * 200) + 1
|
||||
}
|
||||
|
||||
twitchGiftMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-giftbomb' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
total: Math.floor(Math.random() * 50) + 1,
|
||||
sub_tier: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
cumulative_total: Math.floor(Math.random() * 200) + 1
|
||||
}
|
||||
|
||||
twitchGiftSubsMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-raid' :
|
||||
|
||||
var data = {
|
||||
from_broadcaster_user_login: user.name.toLowerCase(),
|
||||
from_broadcaster_user_name: user.name,
|
||||
viewers: Math.floor(Math.random() * 200) + 1
|
||||
}
|
||||
|
||||
twitchRaidMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-announcement' :
|
||||
|
||||
var data = {
|
||||
messageId: messageId,
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
text: announcement,
|
||||
parts: mockData.emotes
|
||||
}
|
||||
|
||||
twitchAnnouncementMessage(data);
|
||||
|
||||
break;
|
||||
case 'twitch-reward' :
|
||||
|
||||
var data = {
|
||||
user_id: user.name.toLowerCase(),
|
||||
user_name: user.name,
|
||||
user_input: announcement,
|
||||
reward: {
|
||||
title: reward
|
||||
}
|
||||
}
|
||||
|
||||
twitchRewardRedemption(data);
|
||||
|
||||
break;
|
||||
|
||||
case 'youtube-chat' :
|
||||
|
||||
var amIMod = Math.random() < 0.1;
|
||||
var amISub = Math.random() < 0.1;
|
||||
var amIOwner = Math.random() < 0.1;
|
||||
|
||||
if (amIOwner == true) {
|
||||
amIMod = false;
|
||||
amISub = false;
|
||||
}
|
||||
|
||||
var data = {
|
||||
user : {
|
||||
id: user.name.toLowerCase(),
|
||||
profileImageUrl: fakeAvatar,
|
||||
name: user.name,
|
||||
isVerified: Math.random() < 0.2,
|
||||
isSponsor: amISub,
|
||||
isModerator: amIMod,
|
||||
isOwner: amIOwner,
|
||||
},
|
||||
emotes: mockData.emotes,
|
||||
message: messagetext,
|
||||
eventId: messageId,
|
||||
};
|
||||
|
||||
youTubeChatMessage(data);
|
||||
|
||||
break;
|
||||
case 'youtube-superchat' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name,
|
||||
},
|
||||
eventId: messageId,
|
||||
amount: '$' + Math.floor(Math.random() * 2000) + 1,
|
||||
message : messagetext
|
||||
};
|
||||
|
||||
youTubeSuperChatMessage(data);
|
||||
|
||||
break;
|
||||
case 'youtube-supersticker' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name
|
||||
},
|
||||
eventId: messageId,
|
||||
amount: '$' + Math.floor(Math.random() * 2000) + 1,
|
||||
_fVK15WwYFFCcGLW0zi1jLCJXj3f: {
|
||||
imageUrl: randomStickerUrl
|
||||
}
|
||||
};
|
||||
|
||||
youTubeSuperStickerMessage(data);
|
||||
|
||||
|
||||
break;
|
||||
case 'youtube-newsponsor' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name,
|
||||
},
|
||||
eventId: messageId,
|
||||
levelName: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
months: 1
|
||||
};
|
||||
|
||||
youTubeNewSponsorMessage(data);
|
||||
|
||||
break;
|
||||
case 'youtube-membermilestone' :
|
||||
|
||||
var data = {
|
||||
user: {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name,
|
||||
},
|
||||
eventId: messageId,
|
||||
levelName: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
months: Math.floor(Math.random() * 50) + 1,
|
||||
message: messagetext
|
||||
};
|
||||
|
||||
youTubeNewSponsorMessage(data);
|
||||
|
||||
|
||||
break;
|
||||
case 'youtube-membergift' :
|
||||
|
||||
var data = {
|
||||
user : {
|
||||
id: user.name.toLowerCase(),
|
||||
name: user.name,
|
||||
},
|
||||
tier: parseInt(Math.floor(Math.random() * 3) + 1),
|
||||
count: Math.floor(Math.random() * 50) + 1,
|
||||
eventId: messageId,
|
||||
};
|
||||
|
||||
youTubeGiftedMembersMessage(data);
|
||||
|
||||
break;
|
||||
|
||||
case 'tiktok-chat' :
|
||||
|
||||
var data = {
|
||||
userId: user.name.toLowerCase(),
|
||||
nickname: user.name,
|
||||
profilePictureUrl: fakeAvatar,
|
||||
isSubscriber: Math.random() < 0.2,
|
||||
isModerator: Math.random() < 0.1,
|
||||
isOwner: Math.random() < 0.05,
|
||||
|
||||
emotes: {},
|
||||
|
||||
comment: messagetext,
|
||||
|
||||
msgId: messageId,
|
||||
};
|
||||
|
||||
tiktokChatMessage(data);
|
||||
|
||||
break;
|
||||
case 'tiktok-follow' :
|
||||
|
||||
var data = {
|
||||
userId: user.name.toLowerCase(),
|
||||
nickname: user.name,
|
||||
profilePictureUrl: fakeAvatar,
|
||||
msgId: messageId,
|
||||
};
|
||||
|
||||
tiktokFollowMessage(data);
|
||||
|
||||
break;
|
||||
case 'tiktok-sub' :
|
||||
|
||||
var data = {
|
||||
userId: user.name.toLowerCase(),
|
||||
nickname: user.name,
|
||||
profilePictureUrl: fakeAvatar,
|
||||
msgId: messageId,
|
||||
subMonth: Math.floor(Math.random() * 50) + 1
|
||||
};
|
||||
|
||||
tiktokSubMessage(data);
|
||||
|
||||
break;
|
||||
case 'tiktok-gift' :
|
||||
|
||||
var data = {
|
||||
userId: user.name.toLowerCase(),
|
||||
nickname: user.name,
|
||||
profilePictureUrl: fakeAvatar,
|
||||
msgId: messageId,
|
||||
|
||||
giftName: tiktokGift.name,
|
||||
repeatCount: Math.floor(Math.random() * 50) + 1,
|
||||
diamondCount: tiktokGift.coins,
|
||||
};
|
||||
|
||||
tiktokGiftMessage(data);
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case 'streamlabs-tip' :
|
||||
|
||||
var data = {
|
||||
from: user.name,
|
||||
formattedAmount: Math.floor(Math.random() * 2000) + 1,
|
||||
currency: 'USD',
|
||||
message: messagetext
|
||||
};
|
||||
|
||||
streamLabsEventMessage(data);
|
||||
|
||||
break;
|
||||
|
||||
case 'streamelements-tip' :
|
||||
|
||||
var data = {
|
||||
username: user.name,e,
|
||||
amount: Math.floor(Math.random() * 2000) + 1,
|
||||
currency: 'USD',
|
||||
message: messagetext
|
||||
};
|
||||
|
||||
streamElementsEventMessage(data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start the mockup system
|
||||
function startMockupSystem() {
|
||||
if (!isMockupActive) {
|
||||
console.debug('Starting mockup system...');
|
||||
isMockupActive = true;
|
||||
mockupConnectionState = false;
|
||||
|
||||
// Add a notification about mockup mode
|
||||
notifyInfo({
|
||||
title: "Streamer.Bot Disconnected",
|
||||
text: "Running in mockup mode. Showing sample events."
|
||||
});
|
||||
|
||||
// Start with a few initial events
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setTimeout(() => generateMockEvent(), i * 500);
|
||||
}
|
||||
|
||||
// Set interval for regular events
|
||||
mockupInterval = setInterval(generateMockEvent, mockupDelay);
|
||||
|
||||
// Update statistics for demo
|
||||
updateMockStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to stop the mockup system
|
||||
function stopMockupSystem() {
|
||||
if (isMockupActive) {
|
||||
console.debug('Stopping mockup system...');
|
||||
isMockupActive = false;
|
||||
mockupConnectionState = true;
|
||||
clearInterval(mockupInterval);
|
||||
mockupInterval = null;
|
||||
|
||||
document.querySelector('#statistics #twitch .viewers span').textContent = '0';
|
||||
document.querySelector('#statistics #youtube .viewers span').textContent = '0';
|
||||
document.querySelector('#statistics #youtube .likes span').textContent = '0';
|
||||
document.querySelector('#statistics #tiktok .viewers span').textContent = '0';
|
||||
document.querySelector('#statistics #tiktok .likes span').textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update mock statistics
|
||||
function updateMockStatistics() {
|
||||
if (showPlatformStatistics) {
|
||||
if (showTwitchViewers) {
|
||||
document.querySelector('#statistics #twitch .viewers span').textContent = formatNumber(Math.floor(Math.random() * 500) + 50);
|
||||
}
|
||||
|
||||
if (showYouTubeStatistics) {
|
||||
document.querySelector('#statistics #youtube .viewers span').textContent = formatNumber(Math.floor(Math.random() * 300) + 20);
|
||||
document.querySelector('#statistics #youtube .likes span').textContent = formatNumber(Math.floor(Math.random() * 1000) + 100);
|
||||
}
|
||||
|
||||
if (showTikTokStatistics) {
|
||||
document.querySelector('#statistics #tiktok .viewers span').textContent = formatNumber(Math.floor(Math.random() * 800) + 200);
|
||||
document.querySelector('#statistics #tiktok .likes span').textContent = formatNumber(Math.floor(Math.random() * 5000) + 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function randomIntFromInterval(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
|
||||
function randomString(length) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function randomColor() {
|
||||
const randomColor = "hsl(" + Math.random() * 360 + ", 100%, 75%)";
|
||||
return randomColor;
|
||||
}
|
292
js/app.js
Normal file
292
js/app.js
Normal file
@@ -0,0 +1,292 @@
|
||||
/* ----------------------- */
|
||||
/* OPTIONS */
|
||||
/* ----------------------- */
|
||||
|
||||
const streamerBotServerAddress = getURLParam("streamerBotServerAddress", "127.0.0.1");
|
||||
const streamerBotServerPort = getURLParam("streamerBotServerPort", "8080");
|
||||
let streamerBotConnected = false;
|
||||
const chatThreshhold = 50;
|
||||
const chatContainer = document.querySelector('#chat');
|
||||
const currentLang = lang[getURLParam("language", 'ptbr')];
|
||||
const eventsMockup = getURLParam("eventsMockup", true);
|
||||
const chatHorizontal = getURLParam("chatHorizontal", false);
|
||||
const showPlatform = getURLParam("showPlatform", false);
|
||||
const showAvatar = getURLParam("showAvatar", false);
|
||||
const showTimestamps = getURLParam("showTimestamps", false);
|
||||
const ampmTimeStamps = getURLParam("ampmTimeStamps", false);
|
||||
const showBadges = getURLParam("showBadges", true);
|
||||
const showPlatformStatistics = getURLParam("showPlatformStatistics", false);
|
||||
const hideAfter = getURLParam("hideAfter", 0);
|
||||
const ignoreChatters = getURLParam("ignoreChatters", "");
|
||||
const excludeCommands = getURLParam("excludeCommands", true);
|
||||
|
||||
const avatars = new Map();
|
||||
const userColors = new Map();
|
||||
|
||||
const ignoreUserList = ignoreChatters.split(',').map(item => item.trim().toLowerCase()) || [];
|
||||
|
||||
/* ----------------------- */
|
||||
/* START */
|
||||
/* ----------------------- */
|
||||
|
||||
if (showPlatformStatistics == false) { document.querySelector('#statistics').style.display = 'none'; }
|
||||
if (chatHorizontal == true) { chatContainer.classList.add('horizontal'); }
|
||||
|
||||
/* ----------------------- */
|
||||
/* STREAMER.BOT CONNECTION */
|
||||
/* ----------------------- */
|
||||
|
||||
const streamerBotClient = new StreamerbotClient({
|
||||
host: streamerBotServerAddress,
|
||||
port: streamerBotServerPort,
|
||||
onConnect: (data) => {
|
||||
console.debug( currentLang.streamerbotconnected );
|
||||
console.debug(data);
|
||||
streamerBotConnected = true;
|
||||
notifySuccess({
|
||||
title: currentLang.streamerbotconnected,
|
||||
text: ``
|
||||
});
|
||||
if (eventsMockup == true) { stopMockupSystem(); }
|
||||
},
|
||||
onDisconnect: () => {
|
||||
console.error(currentLang.streamerbotdisconnected);
|
||||
streamerBotConnected = false;
|
||||
if (eventsMockup == true) { startMockupSystem(); }
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* ----------------------- */
|
||||
/* UTILITIES */
|
||||
/* ----------------------- */
|
||||
|
||||
|
||||
|
||||
async function addMessageToChat(userID, messageID, platform, data) {
|
||||
|
||||
const html = DOMPurify.sanitize(`
|
||||
<div id="${messageID}" data-user="${userID}" class="${platform} ${data.classes} message" style="">
|
||||
<div class="animate__animated ${chatHorizontal == true ? 'animate__fadeInRight' : 'animate__fadeInUp'} animate__faster">
|
||||
|
||||
${!data.shared ? '' : data.shared}
|
||||
|
||||
${showTimestamps == true ? '<span class="time">'+whatTimeIsIt()+'</span>' : ''}
|
||||
|
||||
${showPlatform == true ? '<i class="platform fa-brands fa-'+platform+'"></i>' : '' }
|
||||
|
||||
${showAvatar == true ? '<span class="avatar"><img src="'+data.avatar+'"></span>' : ''}
|
||||
|
||||
${showBadges == true ? '<span class="badges">'+data.badges+'</span>' : ''}
|
||||
|
||||
<span style="color: ${data.color}" class="user">${data.userName}:</span>
|
||||
|
||||
${!data.reply ? '' : data.reply}
|
||||
|
||||
<span class="text">${data.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
chatContainer.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
const messageElement = document.getElementById(messageID);
|
||||
|
||||
if (hideAfter > 0) {
|
||||
setTimeout(function () {
|
||||
messageElement.style.opacity = 0;
|
||||
setTimeout(function () {
|
||||
messageElement.remove();
|
||||
}, 1000);
|
||||
}, Math.floor(hideAfter * 1000));
|
||||
}
|
||||
|
||||
removeExtraChatMessages();
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function addEventToChat(userID, messageID, platform, data) {
|
||||
|
||||
const html = DOMPurify.sanitize(`
|
||||
<div id="${messageID}" data-user="${userID}" class="${platform} ${data.classes} message event" style="">
|
||||
<div class="animate__animated ${chatHorizontal == true ? 'animate__fadeInRight' : 'animate__fadeInUp'} animate__faster">
|
||||
${!data.reply ? '' : data.reply}
|
||||
|
||||
${showPlatform == true ? '<i class="platform '+(platform == 'money' ? 'fa-solid' : 'fa-brands')+' fa-'+platform+'"></i>' : ' ' }
|
||||
|
||||
<span class="info">
|
||||
<!--<span class="avatar"><img src="${data.avatar}"></span>-->
|
||||
<span style="color: ${data.color}" class="user">${data.userName}</span>
|
||||
<span class="text">${data.message}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
chatContainer.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
const messageElement = document.getElementById(messageID);
|
||||
|
||||
if (hideAfter > 0) {
|
||||
setTimeout(function () {
|
||||
messageElement.style.opacity = 0;
|
||||
setTimeout(function () {
|
||||
messageElement.remove();
|
||||
}, 1000);
|
||||
}, Math.floor(hideAfter * 1000));
|
||||
}
|
||||
|
||||
removeExtraChatMessages();
|
||||
}
|
||||
|
||||
|
||||
const whatTimeIsIt = () => {
|
||||
const now = new Date();
|
||||
const hours24 = now.getHours();
|
||||
const minutes = now.getMinutes().toString().padStart(2, '0');
|
||||
const ampm = hours24 >= 12 ? 'PM' : 'AM';
|
||||
const hours12 = (hours24 % 12) || 12;
|
||||
|
||||
if (ampmTimeStamps == true) { return `${hours12}:${minutes} ${ampm}`; }
|
||||
else { return `${hours24}:${minutes}`; }
|
||||
};
|
||||
|
||||
|
||||
function removeExtraChatMessages() {
|
||||
const chatMessages = chatContainer.querySelectorAll('div.message').length;
|
||||
if (chatMessages >= chatThreshhold) {
|
||||
for (let i = 0; i < Math.floor(chatThreshhold/2); i++) {
|
||||
chatContainer.removeChild(chatContainer.firstElementChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function to format large numbers (e.g., 1000 => '1K')
|
||||
function formatNumber(num) {
|
||||
if (num >= 1000000) {
|
||||
let numStr = (num / 1000000).toFixed(1);
|
||||
if (numStr.endsWith('.0')) {
|
||||
numStr = numStr.slice(0, -2);
|
||||
}
|
||||
return numStr + 'M';
|
||||
}
|
||||
else if (num >= 1000) {
|
||||
let numStr = (num / 1000).toFixed(1);
|
||||
if (numStr.endsWith('.0')) {
|
||||
numStr = numStr.slice(0, -2);
|
||||
}
|
||||
return numStr + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
|
||||
function formatCurrency(amount, currencyCode) {
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
style: 'currency',
|
||||
currency: currencyCode,
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function createRandomColor(platform, username) {
|
||||
if (userColors.get(platform).has(username)) {
|
||||
return userColors.get(platform).get(username);
|
||||
}
|
||||
else {
|
||||
const randomColor = "hsl(" + Math.random() * 360 + ", 100%, 75%)";
|
||||
userColors.get(platform).set(username, randomColor);
|
||||
return randomColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createRandomString(length) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getURLParam(param, defaultValue) {
|
||||
const chatQueryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(chatQueryString);
|
||||
const paramVar = urlParams.get(param);
|
||||
|
||||
switch (paramVar) {
|
||||
case 'true':
|
||||
return true;
|
||||
|
||||
case 'false':
|
||||
return false;
|
||||
|
||||
case null:
|
||||
case undefined:
|
||||
return defaultValue;
|
||||
|
||||
default:
|
||||
return paramVar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const pushNotify = (data) => {
|
||||
|
||||
const SimpleNotify = {
|
||||
effect: 'fade',
|
||||
speed: 500,
|
||||
customClass: 'toasty',
|
||||
customIcon: '',
|
||||
showIcon: true,
|
||||
showCloseButton: true,
|
||||
autoclose: true,
|
||||
autotimeout: 5000,
|
||||
notificationsGap: null,
|
||||
notificationsPadding: null,
|
||||
type: 'outline',
|
||||
position: 'x-center bottom',
|
||||
customWrapper: '',
|
||||
};
|
||||
|
||||
const mergedData = {
|
||||
...SimpleNotify,
|
||||
...data
|
||||
}
|
||||
|
||||
new Notify (mergedData);
|
||||
}
|
||||
|
||||
const notifyError = (err) => {
|
||||
err.status = 'error';
|
||||
pushNotify(err);
|
||||
}
|
||||
|
||||
const notifyInfo = (info) => {
|
||||
info.status = 'info';
|
||||
pushNotify(info);
|
||||
}
|
||||
|
||||
const notifyWarning = (warn) => {
|
||||
warn.status = 'warning';
|
||||
pushNotify(warn);
|
||||
}
|
||||
|
||||
|
||||
const notifySuccess = (success) => {
|
||||
success.status = 'success';
|
||||
pushNotify(success);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
76
js/lang/en.js
Normal file
76
js/lang/en.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const en = {
|
||||
streamerbotconnected: 'Streamer.bot Online!',
|
||||
streamerbotdisconnected: 'Streamer.bot Disconnected!',
|
||||
|
||||
twitch : {
|
||||
follow : () => ` followed the channel`,
|
||||
announcement : () => ` <div class="reply">📢 <strong>Announcement</strong></div>`,
|
||||
channelpoints : ({ title }) => ` <div class="reply"><i class="fa-solid fa-wand-magic-sparkles"></i> <strong>Channel Points - ${title}</strong></div>`,
|
||||
bits : ({ bits, message }) => ` cheered <i class="fa-regular fa-gem fall-and-bounce"></i> <strong>${bits} bits</strong>${message ? '<br>'+message : ''}`,
|
||||
|
||||
sub : ({ months, isPrime, tier }) => ` subscribed for
|
||||
${isPrime == true ? '<i class="fa-solid fa-crown"></i>' : '<i class="fa-solid fa-star"></i>'}
|
||||
<strong>${months || 1 } ${months == 1 ? 'month' : 'months'}
|
||||
(${isPrime == true ? 'Prime' : 'Tier '+tier.toString().charAt(0)})</strong>`,
|
||||
|
||||
resub : ({ months, isPrime, tier, message }) => ` subscribed for
|
||||
${isPrime == true ? '<i class="fa-solid fa-crown"></i>' : '<i class="fa-solid fa-star"></i>'}
|
||||
<strong>${months || 1 } ${months == 1 ? 'month' : 'months'}
|
||||
(${isPrime == true ? 'Prime' : 'Tier '+tier.toString().charAt(0)})</strong>
|
||||
${message ? '<br>'+message : '' }`,
|
||||
|
||||
gifted : ({ gifted, months, tier }) => ` gifted
|
||||
<strong>${months || 1 } ${months == 1 ? 'month' : 'months'}
|
||||
of Tier ${tier.toString().charAt(0)} ${months == 1 ? 'sub' : 'subs'}</strong>
|
||||
to <i class="fa-solid fa-gift"></i> <strong>${gifted}</strong>`,
|
||||
|
||||
giftedbomb : ({ count, total, tier }) => ` gifted <i class="fa-solid fa-gift"></i> <strong>${count} subs (Tier ${tier.toString().charAt(0)})</strong> to the Community, <strong>${total || 1} ${total == 1 ? 'gift' : 'gifts'} in total</strong>`,
|
||||
|
||||
raid : ({ viewers }) => ` raided the channel with <i class="fa-solid fa-users"></i> <strong>${viewers} viewers</strong>`
|
||||
|
||||
},
|
||||
|
||||
|
||||
youtube : {
|
||||
superchat : ({ money, message }) => ` superchatted <i class="fa-solid fa-comments-dollar"></i> <strong>${money}</strong>
|
||||
${message ? '<br>'+message : ''}
|
||||
`,
|
||||
|
||||
supersticker : ({ money, sticker }) => `
|
||||
${sticker ? '<br>': ''}
|
||||
sent a supersticker of <i class="fa-solid fa-comments-dollar"></i> <strong>${money}</strong>
|
||||
${sticker ? '</span></span><span class="sticker"><img src="'+sticker+'"></span>': ''}
|
||||
`,
|
||||
|
||||
member : ({ months, tier, message }) => ` became a member for
|
||||
<i class="fa-solid fa-star"></i>
|
||||
<strong>${months || 1 } ${months && months > 1 ? 'months' : 'month'}
|
||||
(Tier ${tier})</strong>
|
||||
${message ? '<br>'+message : ''}`,
|
||||
|
||||
giftedmembers : ({ total, tier }) => ` gifted <i class="fa-solid fa-gift"></i> <strong>${total} ${total == 1 ? 'membership' : 'memberships'} (Tier ${tier}) to the channel</strong>`,
|
||||
|
||||
giftedtrainmembers : ({ gifted, tier }) => ` gifted a membership
|
||||
<strong>(${tier})</strong>
|
||||
to <i class="fa-solid fa-gift"></i> <strong>${gifted}</strong>`,
|
||||
|
||||
},
|
||||
|
||||
|
||||
streamlabs : {
|
||||
tip : ({ money, message }) => ` donated 🪙 <strong>${money}</strong>${message ? '<br>'+message : ''}`,
|
||||
},
|
||||
|
||||
|
||||
streamelements : {
|
||||
tip : ({ money, message }) => ` donated 🪙 <strong>${money}</strong>${message ? '<br>'+message : ''}`,
|
||||
},
|
||||
|
||||
|
||||
tiktok : {
|
||||
follow : () => ` followed the channel`,
|
||||
sub : ({ months }) => ` subscribed for <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'months' : 'month'}</strong>`,
|
||||
gift : ({ gift, count, coins }) => ` gifted <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'coins' : 'coin'})</strong>`,
|
||||
|
||||
}
|
||||
}
|
69
js/lang/es.js
Normal file
69
js/lang/es.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const es = {
|
||||
streamerbotconnected: '¡Streamer.bot en línea!',
|
||||
streamerbotdisconnected: '¡Streamer.bot desconectado!',
|
||||
|
||||
twitch : {
|
||||
follow : () => ` siguió el canal`,
|
||||
announcement : () => ` <div class="reply">📢 <strong>Anuncio</strong></div>`,
|
||||
channelpoints : ({ title }) => ` <div class="reply"><i class="fa-solid fa-wand-magic-sparkles"></i> <strong>Puntos del canal - ${title}</strong></div>`,
|
||||
bits : ({ bits, message }) => ` envió <i class="fa-regular fa-gem fall-and-bounce"></i> <strong>${bits} bits</strong>${message ? '<br>'+message : ''}`,
|
||||
|
||||
sub : ({ months, isPrime, tier }) => ` se suscribió por
|
||||
${isPrime == true ? '<i class="fa-solid fa-crown"></i>' : '<i class="fa-solid fa-star"></i>'}
|
||||
<strong>${months || 1 } ${months == 1 ? 'mes' : 'meses'}
|
||||
(${isPrime == true ? 'Prime' : 'Tier '+tier.toString().charAt(0)})</strong>`,
|
||||
|
||||
resub : ({ months, isPrime, tier, message }) => ` se volvió a suscribir por
|
||||
${isPrime == true ? '<i class="fa-solid fa-crown"></i>' : '<i class="fa-solid fa-star"></i>'}
|
||||
<strong>${months || 1 } ${months == 1 ? 'mes' : 'meses'}
|
||||
(${isPrime == true ? 'Prime' : 'Tier '+tier.toString().charAt(0)})</strong>
|
||||
${message ? '<br>'+message : '' }`,
|
||||
|
||||
gifted : ({ gifted, months, tier }) => ` regaló
|
||||
<strong>${months || 1 } ${months == 1 ? 'mes' : 'meses'}
|
||||
de Tier ${tier.toString().charAt(0)} ${months == 1 ? 'suscripción' : 'suscripciones'}</strong>
|
||||
a <i class="fa-solid fa-gift"></i> <strong>${gifted}</strong>`,
|
||||
|
||||
giftedbomb : ({ count, total, tier }) => ` regaló <i class="fa-solid fa-gift"></i> <strong>${count} suscripciones (Tier ${tier.toString().charAt(0)})</strong> a la comunidad, <strong>${total || 1} ${total == 1 ? 'regalo' : 'regalos'} en total</strong>`,
|
||||
|
||||
raid : ({ viewers }) => ` hizo una raid al canal con <i class="fa-solid fa-users"></i> <strong>${viewers} espectadores</strong>`
|
||||
},
|
||||
|
||||
youtube : {
|
||||
superchat : ({ money, message }) => ` envió un superchat <i class="fa-solid fa-comments-dollar"></i> <strong>${money}</strong>
|
||||
${message ? '<br>'+message : ''}
|
||||
`,
|
||||
|
||||
supersticker : ({ money, sticker }) => `
|
||||
${sticker ? '<br>': ''}
|
||||
envió un supersticker de <i class="fa-solid fa-comments-dollar"></i> <strong>${money}</strong>
|
||||
${sticker ? '</span></span><span class="sticker"><img src="'+sticker+'"></span>': ''}
|
||||
`,
|
||||
|
||||
member : ({ months, tier, message }) => ` se hizo miembro por
|
||||
<i class="fa-solid fa-star"></i>
|
||||
<strong>${months || 1 } ${months && months > 1 ? 'meses' : 'mes'}
|
||||
(Tier ${tier})</strong>
|
||||
${message ? '<br>'+message : ''}`,
|
||||
|
||||
giftedmembers : ({ total, tier }) => ` regaló <i class="fa-solid fa-gift"></i> <strong>${total} ${total == 1 ? 'membresía' : 'membresías'} (Tier ${tier}) al canal</strong>`,
|
||||
|
||||
giftedtrainmembers : ({ gifted, tier }) => ` regaló una membresía
|
||||
<strong>(Tier ${tier})</strong>
|
||||
a <i class="fa-solid fa-gift"></i> <strong>${gifted}</strong>`,
|
||||
},
|
||||
|
||||
streamlabs : {
|
||||
tip : ({ money, message }) => ` donó 🪙 <strong>${money}</strong>${message ? '<br>'+message : ''}`,
|
||||
},
|
||||
|
||||
streamelements : {
|
||||
tip : ({ money, message }) => ` donó 🪙 <strong>${money}</strong>${message ? '<br>'+message : ''}`,
|
||||
},
|
||||
|
||||
tiktok : {
|
||||
follow : () => ` siguió el canal`,
|
||||
sub : ({ months }) => ` se suscribió por <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'meses' : 'mes'}</strong>`,
|
||||
gift : ({ gift, count, coins }) => ` regaló <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'monedas' : 'moneda'})</strong>`,
|
||||
}
|
||||
}
|
5
js/lang/lang.js
Normal file
5
js/lang/lang.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const lang = {
|
||||
ptbr : ptbr,
|
||||
en : en,
|
||||
es: es
|
||||
}
|
76
js/lang/ptbr.js
Normal file
76
js/lang/ptbr.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const ptbr = {
|
||||
streamerbotconnected: 'Streamer.bot Conectado!',
|
||||
streamerbotdisconnected: 'Streamer.bot Desconectado!',
|
||||
|
||||
twitch : {
|
||||
follow : () => ` seguiu o canal`,
|
||||
announcement : () => ` <div class="reply">📢 <strong>Anúncio</strong></div>`,
|
||||
channelpoints : ({ title }) => ` <div class="reply"><i class="fa-solid fa-wand-magic-sparkles"></i> <strong>Pontos do Canal - ${title}</strong></div>`,
|
||||
bits : ({ bits, message }) => ` doou <i class="fa-regular fa-gem fall-and-bounce"></i> <strong>${bits} bits</strong>${message ? '<br>'+message : ''}`,
|
||||
|
||||
sub : ({ months, isPrime, tier }) => ` se inscreveu por
|
||||
${isPrime == true ? '<i class="fa-solid fa-crown"></i>' : '<i class="fa-solid fa-star"></i>'}
|
||||
<strong>${months || 1 } ${months == 1 ? 'mês' : 'meses'}
|
||||
(${isPrime == true ? 'Prime' : 'Tier '+tier.toString().charAt(0)})</strong>`,
|
||||
|
||||
resub : ({ months, isPrime, tier, message }) => ` se inscreveu por
|
||||
${isPrime == true ? '<i class="fa-solid fa-crown"></i>' : '<i class="fa-solid fa-star"></i>'}
|
||||
<strong>${months || 1 } ${months == 1 ? 'mês' : 'meses'}
|
||||
(${isPrime == true ? 'Prime' : 'Tier '+tier.toString().charAt(0)})</strong>
|
||||
${message ? '<br>'+message : '' }`,
|
||||
|
||||
gifted : ({ gifted, months, tier }) => ` doou
|
||||
<strong>${months || 1 } ${months == 1 ? 'mês' : 'meses'}
|
||||
de Tier ${tier.toString().charAt(0)}</strong>
|
||||
para <i class="fa-solid fa-gift"></i> <strong>${gifted}</strong>`,
|
||||
|
||||
giftedbomb : ({ count, total, tier }) => ` doou <i class="fa-solid fa-gift"></i> <strong>${count} inscrições (Tier ${tier.toString().charAt(0)})</strong> para a Comunidade, totalizando <strong>${total || 1} ${total == 1 ? 'doação' : 'doações'}</strong>`,
|
||||
|
||||
raid : ({ viewers }) => ` raidou o canal com <i class="fa-solid fa-users"></i> <strong>${viewers} pessoas</strong>`
|
||||
|
||||
},
|
||||
|
||||
|
||||
youtube : {
|
||||
superchat : ({ money, message }) => ` fez um superchat de <i class="fa-solid fa-comments-dollar"></i> <strong>${money}</strong>
|
||||
${message ? '<br>'+message : ''}
|
||||
`,
|
||||
|
||||
supersticker : ({ money, sticker }) => `
|
||||
${sticker ? '<br>': ''}
|
||||
enviou um super sticker de <i class="fa-solid fa-comments-dollar"></i> <strong>${money}</strong>
|
||||
${sticker ? '</span></span><span class="sticker"><img src="'+sticker+'"></span>': ''}
|
||||
`,
|
||||
|
||||
member : ({ months, tier, message }) => ` se inscreveu por
|
||||
<i class="fa-solid fa-star"></i>
|
||||
<strong>${months || 1 } ${months && months > 1 ? 'meses' : 'mês'}
|
||||
(Tier ${tier})</strong>
|
||||
${message ? '<br>'+message : ''}`,
|
||||
|
||||
giftedmembers : ({ total, tier }) => ` doou <i class="fa-solid fa-gift"></i> <strong>${total} ${total == 1 ? 'inscrição' : 'inscrições'} (Tier ${tier}) para o canal</strong>`,
|
||||
|
||||
giftedtrainmembers : ({ gifted, tier }) => ` doou uma assinatura
|
||||
<strong>(${tier})</strong>
|
||||
para <i class="fa-solid fa-gift"></i> <strong>${gifted}</strong>`,
|
||||
|
||||
},
|
||||
|
||||
|
||||
streamlabs : {
|
||||
tip : ({ money, message }) => ` doou 🪙 <strong>${money}</strong>${message ? '<br>'+message : ''}`,
|
||||
},
|
||||
|
||||
|
||||
streamelements : {
|
||||
tip : ({ money, message }) => ` doou 🪙 <strong>${money}</strong>${message ? '<br>'+message : ''}`,
|
||||
},
|
||||
|
||||
|
||||
tiktok : {
|
||||
follow : () => ` seguiu o canal`,
|
||||
sub : ({ months }) => ` se inscreveu por <i class="fa-solid fa-star"></i> <strong>${months || 1 } ${(months && months > 1) ? 'meses' : 'mês'}</strong>`,
|
||||
gift : ({ gift, count, coins }) => ` doou <strong>${gift} x${count}</strong> (🪙 <strong>${coins} ${(coins && coins > 1) ? 'moedas' : 'moeda'})</strong>`,
|
||||
|
||||
}
|
||||
}
|
316
js/settings.js
Normal file
316
js/settings.js
Normal file
@@ -0,0 +1,316 @@
|
||||
let streamerBotClient;
|
||||
let streamerBotConnected = false;
|
||||
|
||||
async function saveSettingsToLocalStorage() {
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
|
||||
const hiddenField = document.querySelector("textarea[name=youTubeCustomEmotes]:not(.avoid)");
|
||||
|
||||
const settings = {};
|
||||
|
||||
checkboxes.forEach((checkbox) => {
|
||||
settings[checkbox.name] = checkbox.checked;
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
settings[textfield.name] = textfield.value;
|
||||
});
|
||||
numberfields.forEach((numberfield) => {
|
||||
settings[numberfield.name] = numberfield.value;
|
||||
});
|
||||
selects.forEach((select) => {
|
||||
settings[select.name] = select.value;
|
||||
});
|
||||
|
||||
localStorage.setItem("chatWidgetSettings", JSON.stringify(settings));
|
||||
|
||||
if (streamerBotConnected == true) {
|
||||
streamerBotClient.doAction(
|
||||
{ name : "YouTube Custom Emotes" },
|
||||
{
|
||||
"chatrdytcustomemotes": JSON.stringify(hiddenField.value.trim()),
|
||||
}
|
||||
).then( (setglobals) => {
|
||||
console.debug('Saving YouTube Emotes from Streamer.Bot', setglobals);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function loadSettingsFromLocalStorage() {
|
||||
const saved = localStorage.getItem("chatWidgetSettings");
|
||||
if (!saved) return;
|
||||
|
||||
const settings = JSON.parse(saved);
|
||||
|
||||
Object.keys(settings).forEach((key) => {
|
||||
const input = document.querySelector(`[name="${key}"]`);
|
||||
if (input) {
|
||||
if (input.type === "checkbox") {
|
||||
input.checked = settings[key];
|
||||
} else {
|
||||
input.value = settings[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var streamerBotServerAddress = document.querySelector('input[type=text][name=streamerBotServerAddress]').value;
|
||||
var streamerBotServerPort = document.querySelector('input[type=text][name=streamerBotServerPort]').value;
|
||||
|
||||
streamerBotClient = new StreamerbotClient({
|
||||
host: streamerBotServerAddress,
|
||||
port: streamerBotServerPort,
|
||||
onConnect: (data) => {
|
||||
streamerBotConnected = true;
|
||||
|
||||
var sbstatus = document.getElementById('memberemotesbstatus');
|
||||
|
||||
sbstatus.style.color = '#00dd63';
|
||||
sbstatus.textContent = 'Streamer.Bot is Online!';
|
||||
|
||||
streamerBotClient.getGlobals().then( (getglobals) => {
|
||||
const settings = JSON.parse(getglobals.variables.chatrdytcustomemotes.value);
|
||||
console.debug('Getting YouTube Emotes from Streamer.Bot', settings);
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
textarea.value = settings;
|
||||
|
||||
populateEmoteList();
|
||||
});
|
||||
|
||||
},
|
||||
onDisconnect: () => {
|
||||
console.error('Streamer.bot Disconnected!');
|
||||
|
||||
streamerBotConnected = false;
|
||||
|
||||
var sbstatus = document.getElementById('memberemotesbstatus');
|
||||
sbstatus.style.color = '#ff0000';
|
||||
sbstatus.textContent = 'Streamer.Bot Needs to be Online!';
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function pushChangeEvents() {
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
textfield.addEventListener('input', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
numberfields.forEach((numberfield) => {
|
||||
numberfield.addEventListener('input', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
selects.forEach((select) => {
|
||||
select.addEventListener('change', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
textfield.addEventListener('input', () => {
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function generateUrl() {
|
||||
document.getElementById("outputUrl").value = '';
|
||||
|
||||
var runThisLocally = document.querySelector("input[type=checkbox][name=runThisLocally]").checked;
|
||||
var baseUrl = '';
|
||||
|
||||
if (runThisLocally == false) {
|
||||
baseUrl = 'https://vortisrd.github.io/chatrd/chat.html'
|
||||
}
|
||||
|
||||
const checkboxes = document.querySelectorAll("input[type=checkbox]:not(.avoid)");
|
||||
const textfields = document.querySelectorAll("input[type=text]:not(.avoid)");
|
||||
const numberfields = document.querySelectorAll("input[type=number]:not(.avoid)");
|
||||
const selects = document.querySelectorAll("select:not(.avoid)");
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
selects.forEach((select) => {
|
||||
params.set(select.name, select.value);
|
||||
});
|
||||
checkboxes.forEach((checkbox) => {
|
||||
params.set(checkbox.name, checkbox.checked);
|
||||
});
|
||||
textfields.forEach((textfield) => {
|
||||
params.set(textfield.name, textfield.value);
|
||||
});
|
||||
numberfields.forEach((numberfield) => {
|
||||
params.set(numberfield.name, numberfield.value);
|
||||
});
|
||||
|
||||
document.getElementById("outputUrl").value = baseUrl + '?' + params.toString();
|
||||
document.querySelector('#chat-preview iframe').src = 'chat.html?'+params.toString();
|
||||
}
|
||||
|
||||
async function copyUrl() {
|
||||
|
||||
const output = document.getElementById("outputUrl");
|
||||
|
||||
output.select();
|
||||
document.execCommand("copy");
|
||||
|
||||
const button = document.querySelector('.url-bar button');
|
||||
const buttonDefaulText = 'Copy URL';
|
||||
|
||||
button.textContent = 'ChatRD URL Copied!';
|
||||
button.style.backgroundColor = "#00dd63";
|
||||
|
||||
setTimeout(() => {
|
||||
button.textContent = buttonDefaulText;
|
||||
button.removeAttribute('style');
|
||||
}, 3000);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function setupAddEmoteModal() {
|
||||
const modal = document.getElementById("addEmoteModal");
|
||||
const nameInput = document.getElementById("newEmoteName");
|
||||
const urlInput = document.getElementById("newEmoteURL");
|
||||
const confirmBtn = document.getElementById("confirmAddEmote");
|
||||
const cancelBtn = document.getElementById("cancelAddEmote");
|
||||
const addButton = document.querySelector("#youtube .emote-item:last-child .add");
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
|
||||
if (!modal || !addButton || !textarea) return;
|
||||
|
||||
// Show modal
|
||||
addButton.onclick = () => {
|
||||
if (streamerBotConnected == true) {
|
||||
nameInput.value = "";
|
||||
urlInput.value = "";
|
||||
modal.classList.remove("hidden");
|
||||
nameInput.focus();
|
||||
}
|
||||
else {
|
||||
alert("Streamer.bot is Offline!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel
|
||||
cancelBtn.onclick = () => {
|
||||
modal.classList.add("hidden");
|
||||
};
|
||||
|
||||
// Confirm
|
||||
confirmBtn.onclick = () => {
|
||||
const name = nameInput.value.trim();
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
if (!name || !url) {
|
||||
alert("Both fields are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
let emotes;
|
||||
try {
|
||||
emotes = JSON.parse(textarea.value);
|
||||
} catch (err) {
|
||||
console.error("Invalid JSON", err);
|
||||
alert("Emote data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (emotes[name]) {
|
||||
alert(`Emote "${name}" already exists.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add and update
|
||||
emotes[name] = url;
|
||||
textarea.value = JSON.stringify(emotes, null, 4);
|
||||
modal.classList.add("hidden");
|
||||
populateEmoteList();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function populateEmoteList() {
|
||||
const textarea = document.querySelector("textarea[name=youTubeCustomEmotes]");
|
||||
const emoteList = document.querySelector("#youtube .emote-list");
|
||||
|
||||
if (!textarea || !emoteList) return;
|
||||
|
||||
const addButtonSpan = emoteList.querySelector(".emote-item:last-child");
|
||||
|
||||
// Remove all emote items except the add button
|
||||
emoteList.querySelectorAll(".emote-item").forEach(item => {
|
||||
if (item !== addButtonSpan) {
|
||||
item.remove();
|
||||
}
|
||||
});
|
||||
|
||||
let emotes;
|
||||
try {
|
||||
emotes = JSON.parse(textarea.value);
|
||||
} catch (e) {
|
||||
console.error("Invalid JSON in YouTube Emotes textarea", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate each emote item
|
||||
for (const [emoteName, emoteUrl] of Object.entries(emotes)) {
|
||||
const span = document.createElement("span");
|
||||
span.classList.add("emote-item");
|
||||
span.innerHTML = `
|
||||
<img data-emote="${emoteName}" src="${emoteUrl}" alt="">
|
||||
<em>${emoteName}</em>
|
||||
<button class="delete"><i class="fa-solid fa-trash-can"></i></button>
|
||||
`;
|
||||
|
||||
// Add delete handler directly to the button
|
||||
const deleteBtn = span.querySelector(".delete");
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
if (confirm(`Are you sure you want to delete '${emoteName}'?`)) {
|
||||
delete emotes[emoteName];
|
||||
textarea.value = JSON.stringify(emotes, null, 4);
|
||||
populateEmoteList(); // Re-render everything
|
||||
}
|
||||
});
|
||||
|
||||
emoteList.insertBefore(span, addButtonSpan);
|
||||
}
|
||||
|
||||
setupAddEmoteModal();
|
||||
generateUrl();
|
||||
saveSettingsToLocalStorage();
|
||||
}
|
||||
|
||||
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
loadSettingsFromLocalStorage();
|
||||
generateUrl();
|
||||
pushChangeEvents();
|
||||
populateEmoteList();
|
||||
});
|
45
js/streamelements/module.js
Normal file
45
js/streamelements/module.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const showStreamElementsTips = getURLParam("showStreamElementsTips", true);
|
||||
|
||||
const streamElementsHandlers = {
|
||||
'StreamElements.Tip': (response) => {
|
||||
console.debug(response.data);
|
||||
if (showStreamElementsTips == false)
|
||||
return;
|
||||
streamElementsEventMessage(response.data);
|
||||
},
|
||||
};
|
||||
for (const [event, handler] of Object.entries(streamElementsHandlers)) {
|
||||
streamerBotClient.on(event, handler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function streamElementsEventMessage(data) {
|
||||
const {
|
||||
username: userName,
|
||||
amount: moneyFromUser,
|
||||
currency: currencyFromUser,
|
||||
message: messageFromUser,
|
||||
} = data;
|
||||
|
||||
const userID = createRandomString(40);
|
||||
const messageID = createRandomString(40);
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.streamlabs.tip({
|
||||
money : formatCurrency(moneyFromUser,currencyFromUser),
|
||||
message : messageFromUser
|
||||
})
|
||||
]);
|
||||
const classes = 'streamelements';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
addEventToChat(userID, messageID, 'dollar-sign', messageData);
|
||||
}
|
43
js/streamlabs/module.js
Normal file
43
js/streamlabs/module.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const showStreamlabsDonations = getURLParam("showStreamlabsDonations", true);
|
||||
|
||||
const streamLabsHandlers = {
|
||||
'Streamlabs.Donation': (response) => {
|
||||
console.debug(response.data);
|
||||
if (showStreamlabsDonations == false)
|
||||
return;
|
||||
streamLabsEventMessage(response.data);
|
||||
},
|
||||
};
|
||||
for (const [event, handler] of Object.entries(streamLabsHandlers)) {
|
||||
streamerBotClient.on(event, handler);
|
||||
}
|
||||
|
||||
async function streamLabsEventMessage(data) {
|
||||
const {
|
||||
from: userName,
|
||||
formattedAmount: moneyFromUser,
|
||||
currency: currencyFromUser,
|
||||
message: messageFromUser,
|
||||
} = data;
|
||||
|
||||
const userID = createRandomString(40);
|
||||
const messageID = createRandomString(40);
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.streamlabs.tip({
|
||||
money : formatCurrency(moneyFromUser,currencyFromUser),
|
||||
message : messageFromUser
|
||||
})
|
||||
]);
|
||||
const classes = 'streamlabs';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
addEventToChat(userID, messageID, 'dollar-sign', messageData);
|
||||
}
|
231
js/tiktok/module.js
Normal file
231
js/tiktok/module.js
Normal file
@@ -0,0 +1,231 @@
|
||||
/* ----------------------------------------------------------------------------------------- */
|
||||
/* TikFinity >< Streamer.Bot */
|
||||
/* ----------------------------------------------------------------------------------------- */
|
||||
/* In Streamer.bot, go into Servers/Clients, then Websocket clients, */
|
||||
/* and add the server info for TikFinity Desktop App. */
|
||||
/* ----------------------------------------------------------------------------------------- */
|
||||
/* If it's also running on the same computer, the address will be: ws://127.0.0.1:21213/ */
|
||||
/* ----------------------------------------------------------------------------------------- */
|
||||
|
||||
const showTikTokMessages = getURLParam("showTikTokMessages", true);
|
||||
const showTikTokFollows = getURLParam("showTikTokFollows", true);
|
||||
const showTikTokGifts = getURLParam("showTikTokGifts", true);
|
||||
const showTikTokSubs = getURLParam("showTikTokSubs", true);
|
||||
const showTikTokStatistics = getURLParam("showTikTokStatistics", true);
|
||||
|
||||
userColors.set('tiktok', new Map());
|
||||
|
||||
if (showTikTokStatistics == false) { document.querySelector('#statistics #tiktok').style.display = 'none'; }
|
||||
|
||||
|
||||
|
||||
streamerBotClient.on('General.Custom', (response) => {
|
||||
if (response.data.platform === 'TikTok') {
|
||||
|
||||
let json = response.data;
|
||||
let jsonData = json.data.data;
|
||||
|
||||
switch (json.data.event) {
|
||||
case 'roomUser' :
|
||||
tiktokUpdateStatistics(jsonData, 'viewers');
|
||||
break;
|
||||
case 'like' :
|
||||
tiktokUpdateStatistics(jsonData, 'likes');
|
||||
break;
|
||||
case 'chat' :
|
||||
console.log('TikTok Chat', jsonData);
|
||||
tiktokChatMessage(jsonData);
|
||||
break;
|
||||
case 'follow' :
|
||||
console.log('TikTok Follow', jsonData);
|
||||
tiktokFollowMessage(jsonData);
|
||||
break;
|
||||
case 'subscribe' :
|
||||
console.log('TikTok Sub', jsonData);
|
||||
tiktokSubMessage(jsonData);
|
||||
break;
|
||||
case 'gift' :
|
||||
console.log('TikTok Gift', jsonData);
|
||||
tiktokGiftMessage(jsonData);
|
||||
break;
|
||||
default:
|
||||
//console.debug(json);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
async function tiktokChatMessage(data) {
|
||||
|
||||
if (showTikTokMessages == false) return;
|
||||
if (ignoreUserList.includes(data.nickname.toLowerCase())) return;
|
||||
if (data.comment.startsWith("!") && excludeCommands == true) return;
|
||||
|
||||
|
||||
const {
|
||||
userId: userID,
|
||||
msgId: messageID,
|
||||
profilePictureUrl: avatar,
|
||||
comment: message,
|
||||
emotes,
|
||||
nickname: userName,
|
||||
isSubscriber,
|
||||
isModerator,
|
||||
} = data;
|
||||
|
||||
const badgesHTML = [
|
||||
isSubscriber && '<i class="fa-solid fa-star"></i>',
|
||||
isModerator && '<i class="fa-solid fa-user-gear"></i>',
|
||||
].filter(Boolean).join('');
|
||||
|
||||
const classes = [
|
||||
isSubscriber && 'sub',
|
||||
isModerator && 'mod',
|
||||
].filter(Boolean);
|
||||
|
||||
var fullmessage = message;
|
||||
|
||||
if (emotes.length > 0) {
|
||||
emotes.forEach(emote => {
|
||||
var emotetoadd = ` <img src="${emote.emoteImageUrl}" class="emote" data-emote-id="${emote.emoteId}"> `;
|
||||
var position = emote.placeInComment;
|
||||
fullmessage = [fullmessage.slice(0, position), emotetoadd, fullmessage.slice(position)].join('');
|
||||
});
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
classes: classes.join(' '),
|
||||
avatar,
|
||||
badges: badgesHTML,
|
||||
userName,
|
||||
color: await createRandomColor('tiktok', userID),
|
||||
message: fullmessage,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addMessageToChat(userID, messageID, 'tiktok', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function tiktokFollowMessage(data) {
|
||||
|
||||
if (showTikTokFollows == false) return;
|
||||
|
||||
const {
|
||||
userId: userID,
|
||||
msgId: messageID,
|
||||
profilePictureUrl: avatar,
|
||||
nickname: userName,
|
||||
} = data;
|
||||
|
||||
const message = currentLang.tiktok.follow();
|
||||
const classes = 'follow'
|
||||
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'tiktok', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function tiktokSubMessage(data) {
|
||||
|
||||
if (showTikTokSubs == false) return;
|
||||
|
||||
const {
|
||||
userId: userID,
|
||||
msgId: messageID,
|
||||
profilePictureUrl: avatar,
|
||||
nickname: userName,
|
||||
} = data;
|
||||
|
||||
const message = currentLang.tiktok.sub({
|
||||
months : data.subMonth
|
||||
});
|
||||
|
||||
const classes = 'sub'
|
||||
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'tiktok', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function tiktokGiftMessage(data) {
|
||||
|
||||
if (showTikTokGifts == false) return;
|
||||
if (data.giftType === 1 && !data.repeatEnd) {}
|
||||
else {
|
||||
const {
|
||||
userId: userID,
|
||||
msgId: messageID,
|
||||
profilePictureUrl: avatar,
|
||||
nickname: userName,
|
||||
} = data;
|
||||
|
||||
var coins = Math.floor(data.repeatCount*data.diamondCount);
|
||||
|
||||
const message = currentLang.tiktok.gift({
|
||||
gift : data.giftName,
|
||||
count : data.repeatCount,
|
||||
coins : coins
|
||||
});
|
||||
|
||||
const classes = 'gift'
|
||||
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'tiktok', messageData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async function tiktokUpdateStatistics(data, type) {
|
||||
|
||||
if (showPlatformStatistics == false || showTikTokStatistics == false) return;
|
||||
|
||||
if (type == 'viewers') {
|
||||
const viewers = DOMPurify.sanitize(data.viewerCount);
|
||||
document.querySelector('#statistics #tiktok .viewers span').textContent = formatNumber(viewers);
|
||||
}
|
||||
|
||||
if (type == 'likes') {
|
||||
const likes = DOMPurify.sanitize(data.totalLikeCount);
|
||||
document.querySelector('#statistics #tiktok .likes span').textContent = formatNumber(likes);
|
||||
}
|
||||
|
||||
|
||||
}
|
579
js/twitch/module.js
Normal file
579
js/twitch/module.js
Normal file
@@ -0,0 +1,579 @@
|
||||
const showTwitchMessages = getURLParam("showTwitchMessages", true);
|
||||
const showTwitchFollows = getURLParam("showTwitchFollows", true);
|
||||
const showTwitchBits = getURLParam("showTwitchBits", true);
|
||||
const showTwitchAnnouncements = getURLParam("showTwitchAnnouncements", true);
|
||||
const showTwitchSubs = getURLParam("showTwitchSubs", true);
|
||||
const showTwitchGiftedSubs = getURLParam("showTwitchGiftedSubs", true);
|
||||
const showTwitchGiftedSubsUserTrain = getURLParam("showTwitchGiftedSubsUserTrain", true);
|
||||
const showTwitchMassGiftedSubs = getURLParam("showTwitchMassGiftedSubs", true);
|
||||
const showTwitchRewardRedemptions = getURLParam("showTwitchRewardRedemptions", true);
|
||||
const showTwitchRaids = getURLParam("showTwitchRaids", true);
|
||||
const showTwitchSharedChat = getURLParam("showTwitchSharedChat", true);
|
||||
const showTwitchViewers = getURLParam("showTwitchViewers", true);
|
||||
|
||||
if (showTwitchViewers == false) { document.querySelector('#statistics #twitch').style.display = 'none'; }
|
||||
|
||||
const twitchMessageHandlers = {
|
||||
'Twitch.ChatMessage': (response) => {
|
||||
console.debug('Twitch Chat', response.data);
|
||||
twitchChatMessage(response.data);
|
||||
},
|
||||
'Twitch.Follow': (response) => {
|
||||
console.debug('Twitch Follow', response.data);
|
||||
twitchFollowMessage(response.data);
|
||||
},
|
||||
'Twitch.Announcement': (response) => {
|
||||
console.debug('Twitch Announcements', response.data);
|
||||
twitchAnnouncementMessage(response.data);
|
||||
},
|
||||
'Twitch.Cheer': (response) => {
|
||||
console.debug('Twitch Cheer/Bits', response.data);
|
||||
twitchBitsMessage(response.data);
|
||||
},
|
||||
'Twitch.AutomaticRewardRedemption': (response) => {
|
||||
console.debug('Twitch Auto Reward Redemption', response.data);
|
||||
twitchChatMessageGiantEmote(response.data);
|
||||
},
|
||||
'Twitch.RewardRedemption': (response) => {
|
||||
console.debug('Twitch Reward Redemption', response.data);
|
||||
twitchRewardRedemption(response.data);
|
||||
},
|
||||
'Twitch.Sub': (response) => {
|
||||
console.debug('Twitch Sub', response.data);
|
||||
twitchSubMessage(response.data);
|
||||
},
|
||||
'Twitch.ReSub': (response) => {
|
||||
console.debug('Twitch Resub', response.data);
|
||||
twitchReSubMessage(response.data);
|
||||
},
|
||||
'Twitch.GiftSub': (response) => {
|
||||
console.debug('Twitch Gift Sub', response.data);
|
||||
twitchGiftMessage(response.data);
|
||||
|
||||
},
|
||||
'Twitch.GiftBomb': (response) => {
|
||||
console.debug('Twitch Gift Bomb', response.data);
|
||||
twitchGiftSubsMessage(response.data);
|
||||
},
|
||||
'Twitch.Raid': (response) => {
|
||||
console.debug('Twitch Raid', response.data);
|
||||
twitchRaidMessage(response.data);
|
||||
},
|
||||
'Twitch.ChatMessageDeleted': (response) => {
|
||||
console.debug('Twitch Chat Deleted', response.data);
|
||||
twitchChatMessageDeleted(response.data);
|
||||
},
|
||||
'Twitch.UserBanned': (response) => {
|
||||
console.debug('Twitch Ban', response.data);
|
||||
twitchUserBanned(response.data);
|
||||
},
|
||||
'Twitch.UserTimedOut': (response) => {
|
||||
console.debug('Twitch Timeout', response.data);
|
||||
twitchUserBanned(response.data);
|
||||
},
|
||||
'Twitch.ViewerCountUpdate': (response) => {
|
||||
console.debug('Twitch View Count Update', response.data);
|
||||
twitchUpdateStatistics(response.data);
|
||||
},
|
||||
'Twitch.ChatCleared': (response) => {
|
||||
console.debug('Twitch Chat Clear', response.data);
|
||||
twitchChatClearMessages();
|
||||
}
|
||||
};
|
||||
|
||||
for (const [event, handler] of Object.entries(twitchMessageHandlers)) {
|
||||
streamerBotClient.on(event, handler);
|
||||
}
|
||||
|
||||
|
||||
async function twitchChatMessage(data) {
|
||||
|
||||
if (showTwitchMessages == false) return;
|
||||
if (ignoreUserList.includes(data.message.username.toLowerCase())) return;
|
||||
if (data.message.message.startsWith("!") && excludeCommands == true) return;
|
||||
|
||||
const {
|
||||
message: {
|
||||
username: userID,
|
||||
color,
|
||||
displayName: userName,
|
||||
message : text,
|
||||
firstMessage,
|
||||
isReply,
|
||||
isSharedChat,
|
||||
reply: replyData,
|
||||
},
|
||||
messageId,
|
||||
} = data;
|
||||
|
||||
const [avatar, message, badges] = await Promise.all([
|
||||
getTwitchAvatar(userID),
|
||||
getTwitchEmotes(data),
|
||||
getTwitchBadges(data),
|
||||
]);
|
||||
|
||||
const classes = firstMessage ? ['first-message'] : [];
|
||||
|
||||
const replyHTML = isReply ?
|
||||
`<div class="reply"><i class="fa-solid fa-arrow-turn-up"></i> <strong>${replyData.userName}:</strong> ${replyData.msgBody}</div>` :
|
||||
'';
|
||||
|
||||
var sharedChat = '';
|
||||
if (isSharedChat) {
|
||||
if (showTwitchSharedChat == true) {
|
||||
if (!data.sharedChat.primarySource)
|
||||
{
|
||||
var sharedChat = `<div class="shared"><span><i class="fa-solid fa-comments"></i> <strong>${data.sharedChat.sourceRoom.name}</strong></span> <i class="fa-solid fa-arrow-turn-down"></i></div>`;
|
||||
}
|
||||
}
|
||||
else if (!data.sharedChat.primarySource && showTwitchSharedChat == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
classes: classes.join(' '),
|
||||
avatar,
|
||||
badges,
|
||||
userName,
|
||||
color,
|
||||
message,
|
||||
shared: sharedChat,
|
||||
reply: replyHTML,
|
||||
};
|
||||
|
||||
addMessageToChat(userID, messageId, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function twitchChatMessageGiantEmote(data) {
|
||||
|
||||
if (showTwitchMessages == false) return;
|
||||
|
||||
const {
|
||||
user_login: userID,
|
||||
gigantified_emote: { id: emoteGigantify }
|
||||
} = data;
|
||||
const userMessages = chatContainer.querySelectorAll(`.twitch.message[data-user="${userID}"]`);
|
||||
|
||||
if (userMessages.length === 0) return;
|
||||
|
||||
const firstMessage = userMessages[0];
|
||||
const emoteImages = firstMessage.querySelectorAll(`img[data-emote-id="${emoteGigantify}"]`);
|
||||
|
||||
if (emoteImages.length === 0) return;
|
||||
|
||||
emoteImages.forEach(img => {
|
||||
img.classList.add("gigantify");
|
||||
if (img.src.endsWith("2.0")) {
|
||||
img.src = img.src.replace("2.0", "3.0");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function twitchFollowMessage(data) {
|
||||
|
||||
if (showTwitchFollows == false) return;
|
||||
|
||||
const {
|
||||
user_id : userID,
|
||||
user_name : userName
|
||||
} = data;
|
||||
|
||||
const messageID = createRandomString(40);
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.follow(),
|
||||
]);
|
||||
|
||||
const classes = 'follow';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function twitchBitsMessage(data) {
|
||||
|
||||
if (showTwitchBits == false) return;
|
||||
|
||||
const {
|
||||
messageId : messageID,
|
||||
user : {
|
||||
id : userID,
|
||||
name : userName
|
||||
}
|
||||
} = data;
|
||||
|
||||
data.message.message = data.message.message.replace(/\bCheer\d+\b/g, '').replace(/\s+/g, ' ').trim();
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.bits({
|
||||
bits: data.message.bits,
|
||||
message : await getTwitchEmotes(data)
|
||||
}),
|
||||
]);
|
||||
|
||||
const classes = 'bits';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function twitchAnnouncementMessage(data) {
|
||||
|
||||
if (showTwitchAnnouncements == false) return;
|
||||
|
||||
const {
|
||||
messageId : messageID,
|
||||
user : {
|
||||
id : userID,
|
||||
name : userName
|
||||
}
|
||||
} = data;
|
||||
|
||||
|
||||
data.message = {
|
||||
message: await getTwitchAnnouncementEmotes(data)
|
||||
};
|
||||
|
||||
|
||||
const replyHTML = currentLang.twitch.announcement();
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
` ${data.message.message}`
|
||||
]);
|
||||
|
||||
const classes = 'announcement';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: replyHTML,
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function twitchRewardRedemption(data) {
|
||||
|
||||
if (showTwitchRewardRedemptions == false) return;
|
||||
|
||||
const {
|
||||
user_id : userID,
|
||||
user_name : userName,
|
||||
} = data;
|
||||
const messageID = createRandomString(40);
|
||||
const replyHTML = currentLang.twitch.channelpoints({ title : data.reward.title });
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
` ${data.user_input}`
|
||||
]);
|
||||
const classes = 'rewards-redemption';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: replyHTML,
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function twitchSubMessage(data) {
|
||||
|
||||
if (showTwitchSubs == false) return;
|
||||
|
||||
const {
|
||||
user : {
|
||||
id : userID,
|
||||
name : userName
|
||||
}
|
||||
} = data;
|
||||
|
||||
const messageID = createRandomString(40);
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.sub({
|
||||
months : data.duration_months,
|
||||
isPrime : data.isPrime,
|
||||
tier : data.sub_tier
|
||||
})
|
||||
]);
|
||||
|
||||
const classes = 'sub';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function twitchReSubMessage(data) {
|
||||
|
||||
if (showTwitchSubs == false) return;
|
||||
|
||||
const {
|
||||
user : {
|
||||
id : userID,
|
||||
name : userName
|
||||
},
|
||||
text
|
||||
} = data;
|
||||
|
||||
const messageID = createRandomString(40);
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.resub({
|
||||
months : data.cumulativeMonths,
|
||||
isPrime : data.isPrime,
|
||||
tier : data.subTier,
|
||||
message : text
|
||||
})
|
||||
]);
|
||||
|
||||
const classes = 'sub';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function twitchGiftMessage(data) {
|
||||
|
||||
if (data.fromCommunitySubGift === false) {
|
||||
if (showTwitchSubs == false || showTwitchGiftedSubs == false) return;
|
||||
}
|
||||
else {
|
||||
if (showTwitchSubs == false || showTwitchGiftedSubsUserTrain == false) return;
|
||||
}
|
||||
|
||||
|
||||
const {
|
||||
user : {
|
||||
id : userID,
|
||||
name : userName
|
||||
}
|
||||
} = data;
|
||||
|
||||
const messageID = createRandomString(40);
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.gifted({
|
||||
gifted : data.recipient.name,
|
||||
months : data.durationMonths,
|
||||
tier : data.subTier,
|
||||
total : data.cumlativeTotal
|
||||
})
|
||||
]);
|
||||
|
||||
const classes = 'sub';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function twitchGiftSubsMessage(data) {
|
||||
|
||||
if (showTwitchSubs == false || showTwitchMassGiftedSubs == false) return;
|
||||
|
||||
const {
|
||||
user : {
|
||||
id : userID,
|
||||
name : userName
|
||||
}
|
||||
} = data;
|
||||
|
||||
const messageID = createRandomString(40);
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.giftedbomb({ count : data.total, tier : data.sub_tier, total : data.cumulative_total })
|
||||
]);
|
||||
|
||||
const classes = 'sub';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function twitchRaidMessage(data) {
|
||||
|
||||
if (showTwitchRaids == false) return;
|
||||
|
||||
const {
|
||||
from_broadcaster_user_login: userID,
|
||||
from_broadcaster_user_name: userName
|
||||
} = data;
|
||||
|
||||
const messageID = createRandomString(40);
|
||||
const [avatar, message] = await Promise.all([
|
||||
'',
|
||||
currentLang.twitch.raid({ viewers : data.viewers })
|
||||
]);
|
||||
|
||||
const classes = 'raid';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
};
|
||||
|
||||
addEventToChat(userID, messageID, 'twitch', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function twitchChatMessageDeleted(data) {
|
||||
document.getElementById(data.messageId)?.remove();
|
||||
}
|
||||
|
||||
|
||||
async function twitchUserBanned(data) {
|
||||
chatContainer.querySelectorAll(`[data-user="${data.user_login}"]`).forEach(element => {
|
||||
element.remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function twitchChatClearMessages() {
|
||||
chatContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
|
||||
async function twitchUpdateStatistics(data) {
|
||||
|
||||
if (showPlatformStatistics == false || showTwitchViewers == false) return;
|
||||
|
||||
const viewers = DOMPurify.sanitize(data.viewers);
|
||||
document.querySelector('#statistics #twitch .viewers span').textContent = formatNumber(viewers);
|
||||
}
|
||||
|
||||
|
||||
async function getTwitchEmotes(data) {
|
||||
const message = data.message.message;
|
||||
const emotes = data.emotes;
|
||||
const words = message.split(" ");
|
||||
emotes.sort((a, b) => b.startIndex - a.startIndex);
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
emotes.forEach(emote => {
|
||||
if (words[i] === emote.name) {
|
||||
words[i] = `<img src="${emote.imageUrl}" data-emote-id="${emote.id}" alt="${emote.name}" class="emote">`;
|
||||
}
|
||||
});
|
||||
}
|
||||
return words.join(" ");
|
||||
}
|
||||
|
||||
|
||||
async function getTwitchAnnouncementEmotes(data) {
|
||||
const message = data.text;
|
||||
const emotes = data.parts;
|
||||
const words = message.split(" ");
|
||||
emotes.sort((a, b) => b.startIndex - a.startIndex);
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
emotes.forEach(emote => {
|
||||
if (words[i] === emote.text) {
|
||||
words[i] = `<img src="${emote.imageUrl}" alt="${emote.text}" class="emote">`;
|
||||
}
|
||||
});
|
||||
}
|
||||
return words.join(" ");
|
||||
}
|
||||
|
||||
|
||||
async function getTwitchBadges(data) {
|
||||
const badges = data.message.badges;
|
||||
var htmlBadges = '';
|
||||
badges.forEach((badge) => {
|
||||
htmlBadges += `<img src="${badge.imageUrl}" class="badge">`;
|
||||
});
|
||||
return htmlBadges;
|
||||
}
|
||||
|
||||
|
||||
async function getTwitchAvatar(user) {
|
||||
if (showAvatar == true) {
|
||||
if (avatars.has(user)) {
|
||||
console.debug(`Avatar found for ${user}!`);
|
||||
return avatars.get(user);
|
||||
}
|
||||
else {
|
||||
console.debug(`Avatar not found for ${user}! Getting it from DECAPI!`);
|
||||
var decapi = await fetch('https://decapi.me/twitch/avatar/' + user);
|
||||
var newavatar = await decapi.text()
|
||||
avatars.set(user, newavatar);
|
||||
return newavatar;
|
||||
}
|
||||
}
|
||||
}
|
401
js/youtube/module.js
Normal file
401
js/youtube/module.js
Normal file
@@ -0,0 +1,401 @@
|
||||
const showYouTubeMessages = getURLParam("showYouTubeMessages", true);
|
||||
const showYouTubeSuperChats = getURLParam("showYouTubeSuperChats", true);
|
||||
const showYouTubeSuperStickers = getURLParam("showYouTubeSuperStickers", true);
|
||||
const showYouTubeSuperStickerGif = getURLParam("showYouTubeSuperStickerGif", true);
|
||||
const showYouTubeMemberships = getURLParam("showYouTubeMemberships", true);
|
||||
const showYouTubeGiftMemberships = getURLParam("showYouTubeGiftMemberships", true);
|
||||
const showYouTubeMembershipsTrain = getURLParam("showYouTubeMembershipsTrain", true);
|
||||
const showYouTubeStatistics = getURLParam("showYouTubeStatistics", true);
|
||||
|
||||
let youTubeCustomEmotes = [];
|
||||
|
||||
let youTubeBTTVEmotes = [];
|
||||
|
||||
userColors.set('youtube', new Map());
|
||||
|
||||
if (showYouTubeStatistics == false) { document.querySelector('#statistics #youtube').style.display = 'none'; }
|
||||
|
||||
const youtubeMessageHandlers = {
|
||||
'YouTube.Message': (response) => {
|
||||
console.debug('YouTube Chat', response.data);
|
||||
youTubeChatMessage(response.data);
|
||||
},
|
||||
'YouTube.UserBanned': (response) => {
|
||||
console.debug('YouTube Timeout/Hide/Ban', response.data);
|
||||
youTubeUserBanned(response.data);
|
||||
},
|
||||
'YouTube.SuperChat': (response) => {
|
||||
console.debug('YouTube SuperChat', response.data);
|
||||
youTubeSuperChatMessage(response.data);
|
||||
},
|
||||
'YouTube.SuperSticker': (response) => {
|
||||
console.debug('YouTube Super Sticker', response.data);
|
||||
youTubeSuperStickerMessage(response.data);
|
||||
},
|
||||
'YouTube.NewSponsor': (response) => {
|
||||
console.debug('YouTube New Member', response.data);
|
||||
youTubeNewSponsorMessage(response.data);
|
||||
},
|
||||
'YouTube.MemberMileStone': (response) => {
|
||||
console.debug('YouTube Member Milestone', response.data);
|
||||
youTubeNewSponsorMessage(response.data);
|
||||
},
|
||||
'YouTube.MembershipGift': (response) => {
|
||||
console.debug('YouTube Gifted Membership', response.data);
|
||||
youTubeGiftedMembersMessage(response.data);
|
||||
},
|
||||
'YouTube.GiftMembershipReceived': (response) => {
|
||||
console.debug('YouTube Gifted Membership Bomb', response.data);
|
||||
YouTubeGiftReceivedMessage(response.data);
|
||||
},
|
||||
'YouTube.StatisticsUpdated': (response) => {
|
||||
console.debug(response.data);
|
||||
youTubeUpdateStatistics(response.data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
for (const [event, handler] of Object.entries(youtubeMessageHandlers)) {
|
||||
streamerBotClient.on(event, handler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function youTubeChatMessage(data) {
|
||||
|
||||
if (showYouTubeMessages == false) return;
|
||||
if (ignoreUserList.includes(data.user.name.toLowerCase())) return;
|
||||
if (data.message.startsWith("!") && excludeCommands == true) return;
|
||||
|
||||
if (streamerBotConnected == true) {
|
||||
if (youTubeCustomEmotes.length == 0) {
|
||||
streamerBotClient.getGlobals().then( (getglobals) => {
|
||||
youTubeCustomEmotes = JSON.parse(JSON.parse(getglobals.variables.chatrdytcustomemotes.value));
|
||||
console.debug('Getting YouTube Emotes from Streamer.Bot', youTubeCustomEmotes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
user: {
|
||||
id: userID,
|
||||
profileImageUrl: avatar,
|
||||
name: userName,
|
||||
isVerified,
|
||||
isSponsor,
|
||||
isModerator,
|
||||
isOwner,
|
||||
},
|
||||
eventId: messageID,
|
||||
} = data;
|
||||
|
||||
var messageHTML = await getYouTubeEmotes(data);
|
||||
|
||||
const badgesHTML = [
|
||||
isVerified && '<i class="fa-solid fa-check"></i>',
|
||||
isSponsor && '<i class="fa-solid fa-star"></i>',
|
||||
isModerator && '<i class="fa-solid fa-wrench"></i>',
|
||||
isOwner && '<i class="fa-solid fa-video"></i>',
|
||||
].filter(Boolean).join('');
|
||||
|
||||
const classes = [
|
||||
isSponsor && 'sub',
|
||||
isModerator && 'mod',
|
||||
isOwner && 'owner',
|
||||
].filter(Boolean);
|
||||
|
||||
const messageData = {
|
||||
classes: classes.join(' '),
|
||||
avatar,
|
||||
badges: badgesHTML,
|
||||
userName,
|
||||
color: await createRandomColor('youtube', userID),
|
||||
message : messageHTML,
|
||||
reply: '',
|
||||
};
|
||||
addMessageToChat(userID, messageID, 'youtube', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function youTubeUserBanned(data) {
|
||||
chatContainer.querySelectorAll(`[data-user="${data.bannedUser.id}"]`).forEach(element => {
|
||||
element.remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function youTubeSuperChatMessage(data) {
|
||||
|
||||
if (showYouTubeSuperChats == false) return;
|
||||
|
||||
const {
|
||||
user: {
|
||||
id: userID,
|
||||
name: userName,
|
||||
},
|
||||
eventId: messageID,
|
||||
amount,
|
||||
message : textmessage
|
||||
} = data;
|
||||
|
||||
var money = amount;
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
``,
|
||||
currentLang.youtube.superchat({
|
||||
money : money,
|
||||
message : textmessage
|
||||
})
|
||||
]);
|
||||
|
||||
const classes = 'superchat';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
}
|
||||
addEventToChat(userID, messageID, 'youtube', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function youTubeSuperStickerMessage(data) {
|
||||
|
||||
if (showYouTubeSuperStickers == false) return;
|
||||
|
||||
const {
|
||||
user: {
|
||||
id: userID,
|
||||
name: userName,
|
||||
},
|
||||
eventId: messageID,
|
||||
amount
|
||||
} = data;
|
||||
|
||||
var money = amount;
|
||||
var youtubeStickerUrl = '';
|
||||
|
||||
if (showYouTubeSuperStickerGif == true) {
|
||||
youtubeStickerUrl = await getYouTubeStickerImage(data);
|
||||
}
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
``,
|
||||
currentLang.youtube.supersticker({
|
||||
money : money,
|
||||
sticker : youtubeStickerUrl
|
||||
})
|
||||
]);
|
||||
|
||||
const classes = 'supersticker';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
}
|
||||
addEventToChat(userID, messageID, 'youtube', messageData);
|
||||
}
|
||||
|
||||
async function youTubeNewSponsorMessage(data) {
|
||||
|
||||
if (showYouTubeMemberships == false) return;
|
||||
|
||||
const {
|
||||
user: {
|
||||
id: userID,
|
||||
name: userName,
|
||||
},
|
||||
eventId: messageID,
|
||||
levelName,
|
||||
months,
|
||||
message: messagetext,
|
||||
} = data;
|
||||
|
||||
const [avatar, message] = await Promise.all([
|
||||
``,
|
||||
currentLang.youtube.member({
|
||||
months : months,
|
||||
tier : levelName,
|
||||
message: messagetext
|
||||
})
|
||||
]);
|
||||
|
||||
const classes = 'member';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
}
|
||||
addEventToChat(userID, messageID, 'youtube', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function youTubeGiftedMembersMessage(data) {
|
||||
|
||||
if (showYouTubeGiftMemberships == false) return;
|
||||
|
||||
const {
|
||||
user: {
|
||||
id: userID,
|
||||
name: userName,
|
||||
},
|
||||
eventId: messageID,
|
||||
tier,
|
||||
count
|
||||
} = data;
|
||||
const [avatar, message] = await Promise.all([
|
||||
``,
|
||||
currentLang.youtube.giftedmembers({
|
||||
total : count,
|
||||
tier : tier
|
||||
})
|
||||
]);
|
||||
const classes = 'giftedmembers';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
}
|
||||
addEventToChat(userID, messageID, 'youtube', messageData);
|
||||
}
|
||||
|
||||
async function YouTubeGiftReceivedMessage(data) {
|
||||
|
||||
if (showYouTubeMembershipsTrain == false) return;
|
||||
|
||||
const {
|
||||
user: {
|
||||
id: userID,
|
||||
name: userName,
|
||||
},
|
||||
gifter: {
|
||||
id : gifterUserId,
|
||||
name: gifterUserName
|
||||
},
|
||||
eventId: messageID,
|
||||
tier
|
||||
} = data;
|
||||
const [avatar, message] = await Promise.all([
|
||||
``,
|
||||
currentLang.youtube.giftedmembers({
|
||||
gifted : gifterUserName,
|
||||
tier : tier
|
||||
})
|
||||
]);
|
||||
const classes = 'giftedtrainmembers';
|
||||
const messageData = {
|
||||
classes: classes,
|
||||
avatar,
|
||||
badges: '',
|
||||
userName,
|
||||
color: '#FFF',
|
||||
message,
|
||||
reply: '',
|
||||
}
|
||||
addEventToChat(gifterUserId, messageID, 'youtube', messageData);
|
||||
}
|
||||
|
||||
|
||||
async function youTubeUpdateStatistics(data) {
|
||||
|
||||
if (showYouTubeStatistics == false) return;
|
||||
|
||||
const viewers = DOMPurify.sanitize(data.concurrentViewers);
|
||||
const likes = DOMPurify.sanitize(data.likeCount);
|
||||
document.querySelector('#statistics #youtube .viewers span').textContent = formatNumber(viewers);
|
||||
document.querySelector('#statistics #youtube .likes span').textContent = formatNumber(likes);
|
||||
}
|
||||
|
||||
|
||||
async function getYouTubeEmotes(data) {
|
||||
let message = data.message;
|
||||
|
||||
const channelId = data.broadcast?.channelId;
|
||||
if (!channelId) return message;
|
||||
|
||||
// Load emotes if not already loaded
|
||||
if (youTubeBTTVEmotes.length === 0) {
|
||||
try {
|
||||
const res = await fetch(`https://api.betterttv.net/3/cached/users/youtube/${channelId}`);
|
||||
const emoteData = await res.json();
|
||||
youTubeBTTVEmotes = [
|
||||
...(emoteData.sharedEmotes || []),
|
||||
...(emoteData.channelEmotes || [])
|
||||
];
|
||||
} catch (err) {
|
||||
console.warn("Failed to load BTTV emotes:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace BTTV emotes
|
||||
for (const emote of youTubeBTTVEmotes) {
|
||||
const escapedCode = escapeRegex(emote.code);
|
||||
const emoteRegex = new RegExp(`(?<!\\S)${escapedCode}(?!\\S)`, 'g');
|
||||
|
||||
const imageUrl = `https://cdn.betterttv.net/emote/${emote.id}/1x`;
|
||||
const emoteElement = `<img src="${imageUrl}" class="emote">`;
|
||||
|
||||
message = message.replace(emoteRegex, emoteElement);
|
||||
}
|
||||
|
||||
// Replace built-in YouTube emotes
|
||||
if (data.emotes) {
|
||||
for (const emote of data.emotes) {
|
||||
const emoteRegex = new RegExp(escapeRegex(emote.name), 'g');
|
||||
const emoteElement = `<img src="${emote.imageUrl}" class="emote">`;
|
||||
message = message.replace(emoteRegex, emoteElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace Custom Member Emotes Defined at Settings.
|
||||
// Shows if user is a Member
|
||||
if (data.user.isSponsor == true) {
|
||||
message = message.replace(/:([a-zA-Z0-9_]+):/g, (match, emoteName) => {
|
||||
if (youTubeCustomEmotes[emoteName]) {
|
||||
return `<img src="${youTubeCustomEmotes[emoteName]}" class="emote">`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
// ChatGPT created this. :)
|
||||
async function getYouTubeStickerImage(data) {
|
||||
const stack = [data];
|
||||
|
||||
while (stack.length) {
|
||||
const current = stack.pop();
|
||||
|
||||
if (current && typeof current === 'object') {
|
||||
if ('imageUrl' in current && typeof current.imageUrl === 'string') {
|
||||
return current.imageUrl;
|
||||
}
|
||||
|
||||
for (const key in current) {
|
||||
if (Object.hasOwn(current, key)) {
|
||||
stack.push(current[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
Reference in New Issue
Block a user