Skip to content

Commit 22a2b08

Browse files
rootroot
authored andcommitted
kick improvements
1 parent f7882f9 commit 22a2b08

5 files changed

Lines changed: 185 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ chatmessage | string | Chat message
644644
chatimg | string | URL or DataBlob (under ~55KB) of the user's avatar image
645645
type | lower-case string | the pre-qualified name of the source, eg: `twitch`, also used as the source png image
646646
sourceImg | string | an alternative URL to the source image; relative or absolute
647+
sourceName | string | the channel's name or the username of the host for the channel
647648
textonly | boolean | Whether the chat message is only plain text; or does it contain HTML, etc.
648649
hasDonation | string | The donation amount with its units. eg: "3 roses" or "$50 USD".
649650
chatbadges | array | An array of URLs/Objects. If an object, it may define itself as an img/svg and other attributes

actions/EventFlowEditor.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class EventFlowEditor {
2424
{ id: 'messageEquals', name: 'Message Equals' },
2525
{ id: 'messageRegex', name: 'Message Regex' },
2626
{ id: 'fromSource', name: 'From Source' },
27+
{ id: 'fromChannelName', name: 'From Channel Name' },
2728
{ id: 'fromUser', name: 'From User' },
2829
{ id: 'userRole', name: 'User Role' },
2930
{ id: 'hasDonation', name: 'Has Donation' },
@@ -687,6 +688,7 @@ class EventFlowEditor {
687688
case 'messageEquals': return `Text: "${(node.config.text || '').substring(0,15)}${(node.config.text || '').length > 15 ? '...' : ''}"`;
688689
case 'messageRegex': return `Pattern: "${(node.config.pattern || '').substring(0,15)}${(node.config.pattern || '').length > 15 ? '...' : ''}"`;
689690
case 'fromSource': return `Source: ${node.config.source || 'Any'}`;
691+
case 'fromChannelName': return `Channel: ${node.config.channelName || 'Any'}`;
690692
case 'fromUser': return `User: ${node.config.username || 'Any'}`;
691693
case 'userRole': return `Role: ${node.config.role || 'Any'}`;
692694
case 'hasDonation': return 'Has donation';
@@ -958,6 +960,7 @@ class EventFlowEditor {
958960
case 'messageEquals': node.config = { text: 'hello' }; break;
959961
case 'messageRegex': node.config = { pattern: 'pattern', flags: 'i' }; break;
960962
case 'fromSource': node.config = { source: '*' }; break;
963+
case 'fromChannelName': node.config = { channelName: '' }; break;
961964
case 'fromUser': node.config = { username: 'user' }; break;
962965
case 'userRole': node.config = { role: 'mod' }; break;
963966
case 'hasDonation': node.config = {}; break;
@@ -1183,6 +1186,10 @@ class EventFlowEditor {
11831186
${['twitch', 'youtube', 'facebook', 'kick', 'tiktok', 'instagram', 'discord', 'slack', 'other'].map(s => `<option value="${s}" ${node.config.source === s ? 'selected' : ''}>${s.charAt(0).toUpperCase() + s.slice(1)}</option>`).join('')}
11841187
</select></div>`;
11851188
break;
1189+
case 'fromChannelName':
1190+
html += `<div class="property-group"><label class="property-label">Channel Name</label><input type="text" class="property-input" id="prop-channelName" value="${node.config.channelName || ''}" placeholder="Enter channel name"></div>
1191+
<div class="property-help">Match messages from a specific channel name or host username</div>`;
1192+
break;
11861193
case 'fromUser':
11871194
html += `<div class="property-group"><label class="property-label">Username</label><input type="text" class="property-input" id="prop-username" value="${node.config.username || ''}"></div>`;
11881195
break;

actions/EventFlowSystem.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,16 @@ class EventFlowSystem {
561561
console.log(`[RELAY DEBUG - fromSource Trigger] Config Source: "${config.source}", Message Type: "${message.type}", Match: ${match}`);
562562
return match;
563563

564+
case 'fromChannelName':
565+
if (!config.channelName || config.channelName.trim() === '') {
566+
match = true; // Match any channel if no name specified
567+
} else {
568+
const channelName = (message.sourceName || '').toLowerCase();
569+
match = channelName === config.channelName.toLowerCase();
570+
}
571+
console.log(`[RELAY DEBUG - fromChannelName Trigger] Config Channel: "${config.channelName}", Message Channel: "${message.sourceName}", Match: ${match}`);
572+
return match;
573+
564574
case 'fromUser':
565575
const identifier = (message.userid || message.chatname || '').toLowerCase();
566576
match = config && typeof config.username === 'string' && identifier === config.username.toLowerCase();

api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ chatmessage | string | Chat message
399399
chatimg | string | URL or DataBlob (under ~55KB) of the user's avatar image
400400
type | lower-case string | the pre-qualified name of the source, eg: `twitch`, also used as the source png image
401401
sourceImg | string | an alternative URL to the source image; relative or absolute
402+
sourceName | string | the channel's name or the username of the host for the channel
402403
textonly | boolean | Whether the chat message is only plain text; or does it contain HTML, etc.
403404
hasDonation | string | The donation amount with its units. eg: "3 roses" or "$50 USD".
404405
chatbadges | array | An array of URLs/Objects. If an object, it may define itself as an img/svg and other attributes

sources/kick.js

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,136 @@
139139
var maxTrackedMessages = 40;
140140
var pastMessages = [];
141141

142+
// Persistent cache configuration
143+
const CACHE_KEY = 'kick_user_profiles_cache';
144+
const CACHE_EXPIRY_DAYS = 30;
145+
const CACHE_EXPIRY_MS = CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000;
146+
147+
// Load cached profiles from localStorage on startup
148+
function loadCachedProfiles() {
149+
try {
150+
const stored = localStorage.getItem(CACHE_KEY);
151+
if (stored) {
152+
const data = JSON.parse(stored);
153+
const now = Date.now();
154+
155+
// Filter out expired entries and convert back to Map
156+
Object.entries(data).forEach(([username, entry]) => {
157+
if (entry.timestamp && (now - entry.timestamp) < CACHE_EXPIRY_MS) {
158+
cachedUserProfiles.set(username, entry.profilePic);
159+
}
160+
});
161+
162+
console.log(`[Social Stream] Loaded ${cachedUserProfiles.size} cached user profiles from localStorage`);
163+
}
164+
} catch (e) {
165+
console.error('[Social Stream] Error loading cached profiles:', e);
166+
}
167+
}
168+
169+
// Save cached profiles to localStorage
170+
function saveCachedProfiles() {
171+
try {
172+
const data = {};
173+
const now = Date.now();
174+
175+
// Convert Map to object with timestamps
176+
cachedUserProfiles.forEach((profilePic, username) => {
177+
data[username] = {
178+
profilePic: profilePic,
179+
timestamp: now
180+
};
181+
});
182+
183+
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
184+
} catch (e) {
185+
console.error('[Social Stream] Error saving cached profiles:', e);
186+
// If storage is full, clear old cache and try again
187+
if (e.name === 'QuotaExceededError') {
188+
localStorage.removeItem(CACHE_KEY);
189+
try {
190+
localStorage.setItem(CACHE_KEY, JSON.stringify({}));
191+
} catch (e2) {
192+
console.error('[Social Stream] Failed to clear cache:', e2);
193+
}
194+
}
195+
}
196+
}
197+
198+
// Debounce saving to localStorage to avoid excessive writes
199+
let saveTimeout = null;
200+
let lastSaveTime = Date.now();
201+
const DEBOUNCE_DELAY = 5000; // 5 seconds of inactivity
202+
const MAX_SAVE_INTERVAL = 5 * 60 * 1000; // 5 minutes max between saves
203+
204+
function debouncedSaveCachedProfiles() {
205+
const now = Date.now();
206+
const timeSinceLastSave = now - lastSaveTime;
207+
208+
// Clear existing timeout
209+
if (saveTimeout) {
210+
clearTimeout(saveTimeout);
211+
}
212+
213+
// If it's been more than 5 minutes, save immediately
214+
if (timeSinceLastSave >= MAX_SAVE_INTERVAL) {
215+
saveCachedProfiles();
216+
lastSaveTime = now;
217+
} else {
218+
// Otherwise, save after 5 seconds of inactivity
219+
saveTimeout = setTimeout(() => {
220+
saveCachedProfiles();
221+
lastSaveTime = Date.now();
222+
}, DEBOUNCE_DELAY);
223+
}
224+
}
225+
226+
// Load cached profiles on startup
227+
loadCachedProfiles();
228+
229+
// Periodic cleanup of expired cache entries
230+
function cleanupExpiredCache() {
231+
try {
232+
const stored = localStorage.getItem(CACHE_KEY);
233+
if (stored) {
234+
const data = JSON.parse(stored);
235+
const now = Date.now();
236+
let hasExpired = false;
237+
238+
// Remove expired entries
239+
Object.entries(data).forEach(([username, entry]) => {
240+
if (!entry.timestamp || (now - entry.timestamp) >= CACHE_EXPIRY_MS) {
241+
delete data[username];
242+
hasExpired = true;
243+
// Also remove from memory cache if present
244+
cachedUserProfiles.delete(username);
245+
}
246+
});
247+
248+
// Save cleaned data if any entries were removed
249+
if (hasExpired) {
250+
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
251+
console.log('[Social Stream] Cleaned up expired cache entries');
252+
}
253+
}
254+
} catch (e) {
255+
console.error('[Social Stream] Error cleaning up cache:', e);
256+
}
257+
}
258+
259+
// Run cleanup on startup and periodically (every hour)
260+
cleanupExpiredCache();
261+
setInterval(cleanupExpiredCache, 60 * 60 * 1000);
262+
263+
// Also ensure periodic saves every 5 minutes in case of continuous activity
264+
setInterval(() => {
265+
const now = Date.now();
266+
if (now - lastSaveTime >= MAX_SAVE_INTERVAL) {
267+
saveCachedProfiles();
268+
lastSaveTime = now;
269+
}
270+
}, 60 * 1000); // Check every minute
271+
142272
function escapeHtml(unsafe){
143273
try {
144274
if (settings.textonlymode){ // we can escape things later, as needed instead I guess.
@@ -338,6 +468,7 @@
338468
if (cachedUserProfiles.size >= maxCachedProfiles) {
339469
const firstKey = cachedUserProfiles.keys().next().value;
340470
cachedUserProfiles.delete(firstKey);
471+
debouncedSaveCachedProfiles(); // Save after eviction
341472
}
342473

343474
// Add placeholder immediately to prevent duplicate requests
@@ -348,6 +479,7 @@
348479
if (data && data.profilepic){
349480
// Update cache with actual profile pic
350481
cachedUserProfiles.set(username, data.profilepic);
482+
debouncedSaveCachedProfiles(); // Save after adding new profile
351483
return data.profilepic;
352484
}
353485
});
@@ -476,17 +608,25 @@
476608
}
477609
}
478610

611+
var member = false;
612+
var mod = false;
479613
ele.querySelector(".chat-message-identity").querySelectorAll(".badge-tooltip img[src], .badge-tooltip svg, .base-badge img[src], .base-badge svg, .badge img[src], .badge svg").forEach(badge=>{
480614
try {
481615
if (badge && badge.nodeName == "IMG"){
482616
var tmp = {};
483617
tmp.src = badge.src;
484618
tmp.type = "img";
619+
if (badge.src.includes("subscriber")){
620+
member = badge.getAttribute("alt") || "Subscriber";
621+
}
485622
chatbadges.push(tmp);
486623
} else if (badge && badge.nodeName.toLowerCase() == "svg"){
487624
var tmp = {};
488625
tmp.html = badge.outerHTML;
489626
tmp.type = "svg";
627+
if (badge.querySelector('[d="M23.5 2.5v3h-3v3h-3v3h-3v3h-3v-3h-6v6h3v3h-3v3h-3v6h6v-3h3v-3h3v3h6v-6h-3v-3h3v-3h3v-3h3v-3h3v-6h-6Z"]')){
628+
mod = true;
629+
}
490630
chatbadges.push(tmp);
491631
}
492632
} catch(e){ }
@@ -529,6 +669,13 @@
529669
return;
530670
}
531671

672+
if (member){
673+
data.membership = membership;
674+
}
675+
if (mod){
676+
data.mod = true;
677+
}
678+
532679
if (kickUsername){
533680
data.sourceName = kickUsername;
534681
}
@@ -696,18 +843,25 @@
696843
}
697844
}
698845

699-
846+
var member = false;
847+
var mod = false;
700848
ele.querySelectorAll("div > div > div > div > div > div[data-state] img[src], div > div > div > div > div > div[data-state] svg").forEach(badge=>{
701849
try {
702850
if (badge && badge.nodeName == "IMG"){
703851
var tmp = {};
704852
tmp.src = badge.src;
705853
tmp.type = "img";
854+
if (badge.src.includes("subscriber")){
855+
member = badge.getAttribute("alt") || "Subscriber";
856+
}
706857
chatbadges.push(tmp);
707858
} else if (badge && badge.nodeName.toLowerCase() == "svg"){
708859
var tmp = {};
709860
tmp.html = badge.outerHTML;
710861
tmp.type = "svg";
862+
if (badge.querySelector('[d="M23.5 2.5v3h-3v3h-3v3h-3v3h-3v-3h-6v6h3v3h-3v3h-3v6h6v-3h3v-3h3v3h6v-6h-3v-3h3v-3h3v-3h3v-3h3v-6h-6Z"]')){
863+
mod = true;
864+
}
711865
chatbadges.push(tmp);
712866
}
713867
} catch(e){ }
@@ -742,10 +896,17 @@
742896
data.chatmessage = chatmessage;
743897
data.chatimg = chatimg;
744898
data.hasDonation = hasDonation;
745-
data.membership = "";
899+
if (member){
900+
data.membership = member;
901+
}
902+
if (mod){
903+
data.mod = true;
904+
}
746905
data.textonly = settings.textonlymode || false;
747906
data.type = "kick";
748907

908+
909+
749910
if (!chatmessage && !hasDonation){
750911
return;
751912
}
@@ -761,6 +922,9 @@
761922
//if (brandedImageURL){
762923
// data.sourceImg = brandedImageURL;
763924
//}
925+
if (mod){
926+
console.log(data);
927+
}
764928

765929
try {
766930
chrome.runtime.sendMessage(chrome.runtime.id, { "message": data }, (e)=>{

0 commit comments

Comments
 (0)