|
139 | 139 | var maxTrackedMessages = 40; |
140 | 140 | var pastMessages = []; |
141 | 141 |
|
| 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 | + |
142 | 272 | function escapeHtml(unsafe){ |
143 | 273 | try { |
144 | 274 | if (settings.textonlymode){ // we can escape things later, as needed instead I guess. |
|
338 | 468 | if (cachedUserProfiles.size >= maxCachedProfiles) { |
339 | 469 | const firstKey = cachedUserProfiles.keys().next().value; |
340 | 470 | cachedUserProfiles.delete(firstKey); |
| 471 | + debouncedSaveCachedProfiles(); // Save after eviction |
341 | 472 | } |
342 | 473 |
|
343 | 474 | // Add placeholder immediately to prevent duplicate requests |
|
348 | 479 | if (data && data.profilepic){ |
349 | 480 | // Update cache with actual profile pic |
350 | 481 | cachedUserProfiles.set(username, data.profilepic); |
| 482 | + debouncedSaveCachedProfiles(); // Save after adding new profile |
351 | 483 | return data.profilepic; |
352 | 484 | } |
353 | 485 | }); |
|
476 | 608 | } |
477 | 609 | } |
478 | 610 |
|
| 611 | + var member = false; |
| 612 | + var mod = false; |
479 | 613 | 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=>{ |
480 | 614 | try { |
481 | 615 | if (badge && badge.nodeName == "IMG"){ |
482 | 616 | var tmp = {}; |
483 | 617 | tmp.src = badge.src; |
484 | 618 | tmp.type = "img"; |
| 619 | + if (badge.src.includes("subscriber")){ |
| 620 | + member = badge.getAttribute("alt") || "Subscriber"; |
| 621 | + } |
485 | 622 | chatbadges.push(tmp); |
486 | 623 | } else if (badge && badge.nodeName.toLowerCase() == "svg"){ |
487 | 624 | var tmp = {}; |
488 | 625 | tmp.html = badge.outerHTML; |
489 | 626 | 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 | + } |
490 | 630 | chatbadges.push(tmp); |
491 | 631 | } |
492 | 632 | } catch(e){ } |
|
529 | 669 | return; |
530 | 670 | } |
531 | 671 |
|
| 672 | + if (member){ |
| 673 | + data.membership = membership; |
| 674 | + } |
| 675 | + if (mod){ |
| 676 | + data.mod = true; |
| 677 | + } |
| 678 | + |
532 | 679 | if (kickUsername){ |
533 | 680 | data.sourceName = kickUsername; |
534 | 681 | } |
|
696 | 843 | } |
697 | 844 | } |
698 | 845 |
|
699 | | - |
| 846 | + var member = false; |
| 847 | + var mod = false; |
700 | 848 | ele.querySelectorAll("div > div > div > div > div > div[data-state] img[src], div > div > div > div > div > div[data-state] svg").forEach(badge=>{ |
701 | 849 | try { |
702 | 850 | if (badge && badge.nodeName == "IMG"){ |
703 | 851 | var tmp = {}; |
704 | 852 | tmp.src = badge.src; |
705 | 853 | tmp.type = "img"; |
| 854 | + if (badge.src.includes("subscriber")){ |
| 855 | + member = badge.getAttribute("alt") || "Subscriber"; |
| 856 | + } |
706 | 857 | chatbadges.push(tmp); |
707 | 858 | } else if (badge && badge.nodeName.toLowerCase() == "svg"){ |
708 | 859 | var tmp = {}; |
709 | 860 | tmp.html = badge.outerHTML; |
710 | 861 | 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 | + } |
711 | 865 | chatbadges.push(tmp); |
712 | 866 | } |
713 | 867 | } catch(e){ } |
|
742 | 896 | data.chatmessage = chatmessage; |
743 | 897 | data.chatimg = chatimg; |
744 | 898 | data.hasDonation = hasDonation; |
745 | | - data.membership = ""; |
| 899 | + if (member){ |
| 900 | + data.membership = member; |
| 901 | + } |
| 902 | + if (mod){ |
| 903 | + data.mod = true; |
| 904 | + } |
746 | 905 | data.textonly = settings.textonlymode || false; |
747 | 906 | data.type = "kick"; |
748 | 907 |
|
| 908 | + |
| 909 | + |
749 | 910 | if (!chatmessage && !hasDonation){ |
750 | 911 | return; |
751 | 912 | } |
|
761 | 922 | //if (brandedImageURL){ |
762 | 923 | // data.sourceImg = brandedImageURL; |
763 | 924 | //} |
| 925 | + if (mod){ |
| 926 | + console.log(data); |
| 927 | + } |
764 | 928 |
|
765 | 929 | try { |
766 | 930 | chrome.runtime.sendMessage(chrome.runtime.id, { "message": data }, (e)=>{ |
|
0 commit comments