diff --git a/horizontal-chat/script.js b/horizontal-chat/script.js
index 0d4c64b..c8c3b8b 100644
--- a/horizontal-chat/script.js
+++ b/horizontal-chat/script.js
@@ -1050,6 +1050,9 @@ function CustomCodeEvent(data) {
case "kickChatMessage":
KickChatMessage(eventArgs);
break;
+ case "kickFollow":
+ KickFollow(eventArgs);
+ break;
case "kickSub":
KickSub(eventArgs);
break;
diff --git a/multichat-overlay/script.js b/multichat-overlay/script.js
index 66e849b..cbf6550 100644
--- a/multichat-overlay/script.js
+++ b/multichat-overlay/script.js
@@ -1869,6 +1869,9 @@ function CustomCodeEvent(data) {
case "kickChatMessage":
KickChatMessage(eventArgs);
break;
+ case "kickFollow":
+ KickFollow(eventArgs);
+ break;
case "kickSub":
KickSub(eventArgs);
break;
diff --git a/multistream-alerts/script.js b/multistream-alerts/script.js
index 91845af..76fad6b 100644
--- a/multistream-alerts/script.js
+++ b/multistream-alerts/script.js
@@ -60,6 +60,16 @@ const twitchCheerAction = urlParams.get("twitchCheerAction") || "";
const showTwitchRaids = GetBooleanParam("showTwitchRaids", true);
const twitchRaidAction = urlParams.get("twitchRaidAction") || "";
+// Which Kick alerts do you want to see?
+const showKickFollows = GetBooleanParam("showKickFollows", false);
+const kickFollowAction = urlParams.get("kickFollowAction") || "";
+const showKickSubs = GetBooleanParam("showKickSubs", true);
+const kickSubAction = urlParams.get("kickSubAction") || "";
+const showKickChannelPointRedemptions = GetBooleanParam("showKickChannelPointRedemptions", true);
+const kickChannelPointRedemptionAction = urlParams.get("kickChannelPointRedemptionAction") || "";
+const showKickHosts = GetBooleanParam("showKickHosts", true);
+const kickHostAction = urlParams.get("kickHostAction") || "";
+
// Which YouTube alerts do you want to see?
const showYouTubeSuperChats = GetBooleanParam("showYouTubeSuperChats", true);
const youtubeSuperChatAction = urlParams.get("youtubeSuperChatAction") || "";
@@ -275,6 +285,11 @@ client.on('Fourthwall.GiftDrawEnded', (response) => {
FourthwallGiftDrawEnded(response.data);
})
+client.on('Custom.CodeEvent', (response) => {
+ console.debug(response.data);
+ CustomCodeEvent(response.data);
+})
+
///////////////////////
@@ -289,7 +304,7 @@ async function TwitchFollow(data) {
const username = data.user_name;
// Render avatars
- const avatarURL = await GetAvatar(username);
+ const avatarURL = await GetAvatar(username, 'twitch');
UpdateAlertBox(
'twitch',
@@ -314,7 +329,7 @@ async function TwitchCheer(data) {
let message = data.message.message;
// Render avatars
- const avatarURL = await GetAvatar(username);
+ const avatarURL = await GetAvatar(username, 'twitch');
// Render emotes
for (i in data.emotes) {
@@ -369,7 +384,7 @@ async function TwitchSub(data) {
const isPrime = data.is_prime;
// Render avatars
- const avatarURL = await GetAvatar(username);
+ const avatarURL = await GetAvatar(username, 'twitch');
if (!isPrime)
UpdateAlertBox(
@@ -409,7 +424,7 @@ async function TwitchResub(data) {
const message = data.text;
// Render avatars
- const avatarURL = await GetAvatar(username);
+ const avatarURL = await GetAvatar(username, 'twitch');
if (!isPrime)
UpdateAlertBox(
@@ -453,7 +468,7 @@ async function TwitchGiftSub(data) {
return;
// Render avatars
- const avatarURL = await GetAvatar(username);
+ const avatarURL = await GetAvatar(username, 'twitch');
let messageText = '';
if (cumlativeTotal > 0)
@@ -488,7 +503,7 @@ async function TwitchGiftBomb(data) {
const subTier = data.sub_tier.charAt(0);
// Render avatars
- const avatarURL = await GetAvatar(login);
+ const avatarURL = await GetAvatar(login, 'twitch');
let message = ``;
if (totalGifts > 0)
@@ -518,7 +533,7 @@ async function TwitchRewardRedemption(data) {
const channelPointIcon = `
`;
// Render avatars
- const avatarURL = await GetAvatar(data.user_login);
+ const avatarURL = await GetAvatar(data.user_login, 'twitch');
UpdateAlertBox(
'twitch',
@@ -538,7 +553,7 @@ async function TwitchRaid(data) {
return;
// Render avatars
- const avatarURL = await GetAvatar(data.from_broadcaster_user_login);
+ const avatarURL = await GetAvatar(data.from_broadcaster_user_login, 'twitch');
// Set the text
const username = data.from_broadcaster_user_login;
@@ -1072,6 +1087,205 @@ function FourthwallGiftDrawEnded(data) {
);
}
+function CustomCodeEvent(data) {
+ const eventName = data.eventName;
+ const eventArgs = data.args;
+
+ switch (eventName) {
+ case "kickFollow":
+ KickFollow(eventArgs);
+ break;
+ case "kickSub":
+ KickSub(eventArgs);
+ break;
+ case "kickGift":
+ KickGift(eventArgs);
+ break;
+ case "kickGifts":
+ KickGifts(eventArgs);
+ break;
+ case "kickRewardRedeemed":
+ KickRewardRedeemed(eventArgs);
+ break;
+ case "kickIncomingRaid":
+ KickIncomingRaid(eventArgs);
+ break;
+ }
+}
+
+async function KickFollow(data) {
+ if (!showKickFollows)
+ return;
+
+ // Set the text
+ const username = data.user;
+
+ // Render avatars
+ const avatarURL = await GetAvatar(username, 'kick');
+
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username}`,
+ `followed`,
+ ``,
+ username,
+ ``,
+ kickFollowAction,
+ data
+ );
+}
+
+async function KickSub(data) {
+ if (!showKickSubs)
+ return;
+
+ // Set the text
+ const username = data.user;
+ // const subTier = data.sub_tier;
+ // const isPrime = data.is_prime;
+
+ // Take the raw message and remove the first word which should be the username
+ const description = data.rawInput.trim().split(" ").slice(1).join(" ");
+
+ // Render avatars
+ const avatarURL = await GetAvatar(username, 'kick');
+
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username}`,
+ description,
+ '',
+ username,
+ '',
+ kickSubAction,
+ data
+ );
+}
+
+async function KickGift(data) {
+ if (!showKickSubs)
+ return;
+
+ // Set the text
+ const username = data.user;
+ // const subTier = data.subTier;
+ // const recipient = data.recipient.name;
+ // const cumlativeTotal = data.cumlativeTotal;
+ // const fromCommunitySubGift = data.fromCommunitySubGift;
+
+ // Take the raw message and remove the first word which should be the username
+ const description = data.rawInput.trim().split(" ").slice(1, -2).join(" ");
+
+ // Take the raw message and remove the first word which should be the username
+ const attribute = data.rawInput.trim().split(" ").slice(-2).join(" ");
+
+ // // Don't post alerts for gift bombs
+ // if (fromCommunitySubGift)
+ // return;
+
+ // Render avatars
+ const avatarURL = await GetAvatar(username, 'kick');
+
+ // let messageText = '';
+ // if (cumlativeTotal > 0)
+ // messageText = `They've gifted ${cumlativeTotal} subs in total!`;
+
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username}`,
+ description,
+ attribute,
+ username,
+ '', //messageText,
+ kickSubAction,
+ data
+ );
+}
+
+async function KickGifts(data) {
+ if (!showKickSubs)
+ return;
+
+ // Set the text
+ const username = data.user;
+ const gifts = data.gifts;
+ // const subTier = data.subTier;
+ // const recipient = data.recipient.name;
+ // const cumlativeTotal = data.cumlativeTotal;
+ // const fromCommunitySubGift = data.fromCommunitySubGift;
+
+ // Take the raw message and remove the first word which should be the username
+ const description = `gifted ${gifts} subs to the community!`;
+
+ // Render avatars
+ const avatarURL = await GetAvatar(username, 'kick');
+
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username}`,
+ description,
+ '',
+ '',
+ '', //messageText,
+ kickSubAction,
+ data
+ );
+}
+
+async function KickRewardRedeemed(data) {
+ if (!showKickChannelPointRedemptions)
+ return;
+
+ const username = data.user;
+ const rewardName = data.rewardTitle;
+ // const cost = data.reward.cost;
+ const userInput = data.rewardUserInput;
+ // const channelPointIcon = `
`;
+
+ // Render avatars
+ const avatarURL = await GetAvatar(data.user, 'kick');
+
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username} redeemed`,
+ `${rewardName}`,
+ '',
+ username,
+ userInput,
+ kickChannelPointRedemptionAction,
+ data
+ );
+}
+
+async function KickIncomingRaid(data) {
+ if (!showKickHosts)
+ return;
+
+ // Render avatars
+ const avatarURL = await GetAvatar(data.user, 'kick');
+
+ // Set the text
+ const username = data.user;
+ const viewers = data.viewers;
+
+ UpdateAlertBox(
+ 'kick',
+ avatarURL,
+ `${username}`,
+ `is raiding with a party of ${viewers}`,
+ '',
+ username,
+ '',
+ kickHostAction,
+ data
+ );
+}
+
//////////////////////
@@ -1116,17 +1330,36 @@ function GetIntParam(paramName, defaultValue) {
return intValue;
}
-async function GetAvatar(username) {
- if (avatarMap.has(username)) {
- console.debug(`Avatar found for ${username}. Retrieving from hash map.`)
- return avatarMap.get(username);
+async function GetAvatar(username, platform) {
+
+ // First, check if the username is hashed already
+ if (avatarMap.has(`${username}-${platform}`)) {
+ console.debug(`Avatar found for ${username} (${platform}). Retrieving from hash map.`)
+ return avatarMap.get(`${username}-${platform}`);
}
- 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;
+
+ // If code reaches this point, the username hasn't been hashed, so retrieve avatar
+ switch (platform) {
+ case 'twitch':
+ {
+ console.debug(`No avatar found for ${username} (${platform}). Retrieving from Decapi.`)
+ let response = await fetch('https://decapi.me/twitch/avatar/' + username);
+ let data = await response.text();
+ avatarMap.set(`${username}-${platform}`, data);
+ return data;
+ }
+ case 'kick':
+ {
+ console.debug(`No avatar found for ${username} (${platform}). Retrieving from Kick.`)
+ let response = await fetch('https://kick.com/api/v2/channels/' + username);
+ console.log('https://kick.com/api/v2/channels/' + username)
+ let data = await response.json();
+ let avatarURL = data.user.profile_pic;
+ if (!avatarURL)
+ avatarURL = 'https://kick.com/img/default-profile-pictures/default2.jpeg';
+ avatarMap.set(`${username}-${platform}`, avatarURL);
+ return avatarURL;
+ }
}
}
@@ -1348,7 +1581,7 @@ async function testWidget()
{
UpdateAlertBox(
'twitch',
- await GetAvatar('nutty'),
+ await GetAvatar('nutty', 'twitch'),
`nutty`,
`subscribed with Tier 3`,
'',
@@ -1384,29 +1617,21 @@ function SetConnectionStatus(connected) {
}
// let data = {
-// cumulative_total: 77,
-// id: "6616253944106387595",
-// isTest: false,
-// messageId: "00e4258c-a880-4f11-a93c-4734ee92199f",
-// recipients: [
-// {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
-// ],
-// sub_tier: "1000", // Usually "1000" = Tier 1
-// systemMessage: "thetrickster1973 is gifting 10 Tier 1 Subs to nutty's community! They've gifted a total of 77 in the channel!",
-// total: 10,
-// user: {
-// badges: [{}, {}, {}],
-// color: "#1E90FF",
-// id: "52175891",
-// login: "thetrickster1973",
-// monthsSubscribed: 14,
-// name: "thetrickster1973",
-// role: 2, // 2 - VIP
-// subscribed: true,
-// type: "twitch"
-// }
-// };
+// "user": "BnBriTV",
+// "viewers": 34,
+// "eventSource": "kick",
+// "fromKick": true,
+// "__source": 18002,
+// "triggerId": "e7be4c30-180c-40cd-b2cf-0f1bcc04e106",
+// "triggerName": "Custom Code Event",
+// "triggerCategory": "Custom",
+// "triggerCustomCodeEventName": "kickIncomingRaid",
+// "actionId": "fe5fc792-7e28-4ad9-bdb5-f32f8229810f",
+// "actionName": "Kick Data Scraper | Save Data",
+// "runningActionId": "1508eed5-7e08-49a9-9699-f3285b0200d8",
+// "actionQueuedAt": "2025-07-03T06:13:40.3382156+10:00"
+// }
-// TwitchGiftBomb(data);
+// KickIncomingRaid(data);
-//testWidget();
\ No newline at end of file
+// testWidget();
\ No newline at end of file
diff --git a/multistream-alerts/settings/settings.json b/multistream-alerts/settings/settings.json
index c2e60a6..4dd17f4 100644
--- a/multistream-alerts/settings/settings.json
+++ b/multistream-alerts/settings/settings.json
@@ -312,6 +312,74 @@
"showIf": "showTwitchRaids",
"group": "Which Twitch alerts do you want to see?"
},
+ {
+ "id": "showKickFollows",
+ "label": "New Followers",
+ "description": "",
+ "type": "checkbox",
+ "defaultValue": false,
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "kickFollowAction",
+ "label": "",
+ "description": "Also run this Streamer.bot Action",
+ "type": "sb-actions",
+ "defaultValue": "",
+ "showIf": "showKickFollows",
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "showKickSubs",
+ "label": "New Subscribers",
+ "description": "",
+ "type": "checkbox",
+ "defaultValue": true,
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "kickSubAction",
+ "label": "",
+ "description": "Also run this Streamer.bot Action",
+ "type": "sb-actions",
+ "defaultValue": "",
+ "showIf": "showKickSubs",
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "showKickChannelPointRedemptions",
+ "label": "Channel Point Redemptions",
+ "description": "",
+ "type": "checkbox",
+ "defaultValue": true,
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "kickChannelPointRedemptionAction",
+ "label": "",
+ "description": "Also run this Streamer.bot Action",
+ "type": "sb-actions",
+ "defaultValue": "",
+ "showIf": "showKickChannelPointRedemptions",
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "showKickHosts",
+ "label": "Hosts",
+ "description": "",
+ "type": "checkbox",
+ "defaultValue": true,
+ "group": "Which Kick alerts do you want to see?"
+ },
+ {
+ "id": "kickHostAction",
+ "label": "",
+ "description": "Also run this Streamer.bot Action",
+ "type": "sb-actions",
+ "defaultValue": "",
+ "showIf": "showKickHosts",
+ "group": "Which Kick alerts do you want to see?"
+ },
{
"id": "showYouTubeSuperChats",
"label": "Super Chats",
diff --git a/multistream-alerts/style.css b/multistream-alerts/style.css
index 1b6172e..2b2a330 100644
--- a/multistream-alerts/style.css
+++ b/multistream-alerts/style.css
@@ -307,6 +307,10 @@ html {
background: linear-gradient(#FF6C60BF, #FF0707BF) !important;
}
+.kick {
+ background: linear-gradient(#53fc18BF, #005600BF) !important;
+}
+
.streamlabs {
background: linear-gradient(#73dabbbf, #397765bf) !important;
}