From 72b1316d1a1fbd4bedfa31a7c5e31f5da2489ae7 Mon Sep 17 00:00:00 2001 From: "arasseo." <34034590+galpt@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:49:11 +0700 Subject: [PATCH] Revert "add new Accelerator plugin" This reverts commit 16c92129f02d556a07f5398beec9d942b65cdf7a. --- src/plugins/accelerator/index.ts | 237 -------- src/plugins/accelerator/modules/fastcache.ts | 351 ------------ .../accelerator/modules/imagepreloader.ts | 255 --------- .../accelerator/modules/intersection.ts | 173 ------ .../accelerator/modules/messageAccessory.tsx | 86 --- src/plugins/accelerator/modules/preloader.ts | 214 -------- src/plugins/accelerator/modules/stats.ts | 513 ------------------ src/plugins/accelerator/styles.css | 171 ------ src/utils/constants.ts | 4 - 9 files changed, 2004 deletions(-) delete mode 100644 src/plugins/accelerator/index.ts delete mode 100644 src/plugins/accelerator/modules/fastcache.ts delete mode 100644 src/plugins/accelerator/modules/imagepreloader.ts delete mode 100644 src/plugins/accelerator/modules/intersection.ts delete mode 100644 src/plugins/accelerator/modules/messageAccessory.tsx delete mode 100644 src/plugins/accelerator/modules/preloader.ts delete mode 100644 src/plugins/accelerator/modules/stats.ts delete mode 100644 src/plugins/accelerator/styles.css diff --git a/src/plugins/accelerator/index.ts b/src/plugins/accelerator/index.ts deleted file mode 100644 index 65b79bd1..00000000 --- a/src/plugins/accelerator/index.ts +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { definePluginSettings } from "@api/Settings"; -import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories"; -import { Devs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; -import definePlugin, { OptionType } from "@utils/types"; - -import { channelPreloader } from "./modules/preloader"; -import { fastCache } from "./modules/fastcache"; -import { intersectionOptimizer } from "./modules/intersection"; -import { imagePreloader } from "./modules/imagepreloader"; -import { CacheIndicatorAccessory } from "./modules/messageAccessory"; -import { statsTracker, FloatingStats } from "./modules/stats"; - -const logger = new Logger("Accelerator"); - -const settings = definePluginSettings({ - enablePreloading: { - type: OptionType.BOOLEAN, - description: "Preload adjacent channels and recent DMs for instant switching", - default: true, - restartNeeded: true - }, - - enableFastCache: { - type: OptionType.BOOLEAN, - description: "Thread-safe high-performance message and user caching", - default: true, - restartNeeded: true - }, - - enableImagePreloading: { - type: OptionType.BOOLEAN, - description: "Intelligent image preloading for smoother scrolling", - default: true, - restartNeeded: true - }, - - enableViewportOptimization: { - type: OptionType.BOOLEAN, - description: "Use intersection observers for efficient rendering", - default: true, - restartNeeded: true - }, - - showStatsWindow: { - type: OptionType.BOOLEAN, - description: "Show floating performance statistics window", - default: true, - restartNeeded: true - }, - - showCacheIndicators: { - type: OptionType.BOOLEAN, - description: "Show green dots on messages served instantly from cache", - default: true, - restartNeeded: true - }, - - preloadDistance: { - type: OptionType.SLIDER, - description: "How many adjacent channels to preload", - default: 3, - markers: [1, 2, 3, 4, 5], - stickToMarkers: true, - restartNeeded: true - }, - - cacheSize: { - type: OptionType.SLIDER, - description: "Cache size in MB (uses thread-safe buckets)", - default: 256, - markers: [128, 256, 384, 512, 640, 768, 896, 1024], - stickToMarkers: true, - restartNeeded: true - }, - - maxImagePreload: { - type: OptionType.SLIDER, - description: "Maximum images to preload ahead", - default: 10, - markers: [5, 10, 20, 50], - stickToMarkers: true, - restartNeeded: true - } -}); - -export default definePlugin({ - name: "Accelerator", - description: "High-performance Discord optimization using thread-safe caching, intelligent preloading, and zero CSS interference", - authors: [Devs.galpt], - tags: ["performance", "optimization", "preload", "cache", "thread-safe"], - dependencies: ["MessageAccessoriesAPI"], - - settings, - - // Minimal patches - only for tracking performance, no CSS modifications - patches: [ - { - find: "CONNECTION_OPEN:", - replacement: { - match: /(CONNECTION_OPEN:function\(\w+\)\{)/, - replace: "$1/* Accelerator: Performance tracking */" - } - } - ], - - flux: { - // Channel switching with immediate preloading - CHANNEL_SELECT({ channelId, guildId }) { - if (channelId) { - statsTracker.trackChannelSwitchStart(channelId); - - if (settings.store.enablePreloading) { - channelPreloader.preloadAdjacent(guildId, channelId, settings.store.preloadDistance); - } - - if (settings.store.enableImagePreloading) { - imagePreloader.preloadChannelImages(channelId, settings.store.maxImagePreload); - } - } - }, - - // Track message loading and fast cache integration - LOAD_MESSAGES_SUCCESS({ channelId, messages }) { - statsTracker.trackChannelSwitchEnd(channelId); - - // Always track messages loaded for statistics - if (messages?.length) { - statsTracker.incrementMessagesLoaded(messages.length); - } - - // Add to fast cache if enabled - if (settings.store.enableFastCache && messages?.length) { - fastCache.addMessageBatch(channelId, messages); - } - }, - - // Cache new messages atomically and track them - MESSAGE_CREATE({ message }) { - if (message) { - // Add to fast cache if enabled and track cached count - if (settings.store.enableFastCache) { - fastCache.addMessage(message.channel_id, message); - statsTracker.incrementMessagesCached(1); - } - } - }, - - // Track cache performance - LOAD_MESSAGES_START({ channelId }) { - if (settings.store.enableFastCache) { - const cached = fastCache.getMessages(channelId); - if (cached.length > 0) { - statsTracker.incrementCacheHit(); - logger.debug(`Found ${cached.length} cached messages for channel ${channelId}`); - } else { - statsTracker.incrementCacheMiss(); - } - } - }, - - // User data caching for profile optimization - USER_UPDATE({ user }) { - if (settings.store.enableFastCache && user) { - fastCache.addUser(user.id, user); - } - } - }, - - async start() { - // Initialize performance tracking - statsTracker.init(); - - // Initialize thread-safe cache system first - if (settings.store.enableFastCache) { - await fastCache.init(settings.store.cacheSize * 1024 * 1024); // Convert MB to bytes - } - - // Initialize pure JavaScript optimizations - if (settings.store.enableViewportOptimization) { - intersectionOptimizer.init(); - } - - if (settings.store.enablePreloading) { - channelPreloader.init(settings.store.preloadDistance); - } - - if (settings.store.enableImagePreloading) { - imagePreloader.init(settings.store.maxImagePreload); - } - - // Initialize message accessories for cache indicators - if (settings.store.enableFastCache && settings.store.showCacheIndicators) { - addMessageAccessory("accelerator-cache-indicator", props => - CacheIndicatorAccessory({ message: props.message }) - ); - } - - // Show stats window (this is the ONLY UI/CSS component) - if (settings.store.showStatsWindow) { - FloatingStats.show(); - } - - // Set up periodic cache stats sync - if (settings.store.enableFastCache) { - setInterval(() => { - const totalCached = fastCache.getTotalMessagesCached(); - if (totalCached !== statsTracker.getStats().messagesCached) { - statsTracker.updateCacheStats({ - totalMessagesCached: totalCached - }); - } - }, 5000); // Sync every 5 seconds - } - }, - - stop() { - // Clean shutdown of all systems - channelPreloader.cleanup(); - fastCache.cleanup(); - intersectionOptimizer.cleanup(); - imagePreloader.cleanup(); - statsTracker.cleanup(); - FloatingStats.hide(); - - // Remove message accessories - if (settings.store.enableFastCache && settings.store.showCacheIndicators) { - removeMessageAccessory("accelerator-cache-indicator"); - } - } -}); \ No newline at end of file diff --git a/src/plugins/accelerator/modules/fastcache.ts b/src/plugins/accelerator/modules/fastcache.ts deleted file mode 100644 index 50512c80..00000000 --- a/src/plugins/accelerator/modules/fastcache.ts +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; -import { statsTracker } from "./stats"; - -const logger = new Logger("Accelerator:FastCache"); - -// Inspired by VictoriaMetrics/fastcache - thread-safe cache with buckets -// Each bucket has its own lock (simulated with async operations) to reduce contention - -interface CacheEntry { - key: string; - value: any; - timestamp: number; - size: number; -} - -interface Bucket { - entries: Map; - totalSize: number; - lastCleanup: number; -} - -class FastCache { - private buckets: Bucket[] = []; - private bucketCount = 256; // Power of 2 for fast modulo - private maxCacheSize = 256 * 1024 * 1024; // 256 MB default - private maxEntryAge = 30 * 60 * 1000; // 30 minutes - private cleanupInterval: any | null = null; - private stats = { - hits: 0, - misses: 0, - evictions: 0, - totalEntries: 0 - }; - - async init(maxSizeBytes: number): Promise { - this.maxCacheSize = maxSizeBytes; - this.buckets = []; - - // Initialize buckets (like chunks in fastcache) - for (let i = 0; i < this.bucketCount; i++) { - this.buckets.push({ - entries: new Map(), - totalSize: 0, - lastCleanup: Date.now() - }); - } - - // Start background cleanup (like GC in fastcache) - this.cleanupInterval = setInterval(() => { - this.performCleanup(); - }, 60 * 1000); // Every minute - - logger.info(`FastCache initialized with ${this.bucketCount} buckets, max size: ${Math.round(maxSizeBytes / 1024 / 1024)}MB`); - } - - // Hash function to distribute keys across buckets - private hash(key: string): number { - let hash = 0; - for (let i = 0; i < key.length; i++) { - const char = key.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32-bit integer - } - return Math.abs(hash) % this.bucketCount; - } - - // Estimate size of an object (simplified) - private estimateSize(obj: any): number { - if (obj === null || obj === undefined) return 8; - if (typeof obj === "string") return obj.length * 2; // UTF-16 - if (typeof obj === "number") return 8; - if (typeof obj === "boolean") return 4; - if (Array.isArray(obj)) { - return 24 + obj.reduce((acc, item) => acc + this.estimateSize(item), 0); - } - if (typeof obj === "object") { - return 24 + Object.keys(obj).reduce((acc, key) => { - return acc + this.estimateSize(key) + this.estimateSize(obj[key]); - }, 0); - } - return 24; // Default object overhead - } - - // Thread-safe set operation - set(key: string, value: any): void { - const bucketIndex = this.hash(key); - const bucket = this.buckets[bucketIndex]; - const size = this.estimateSize(value); - const now = Date.now(); - - // Remove old entry if exists - const existing = bucket.entries.get(key); - if (existing) { - bucket.totalSize -= existing.size; - this.stats.totalEntries--; - } - - // Add new entry - const entry: CacheEntry = { - key, - value, - timestamp: now, - size - }; - - bucket.entries.set(key, entry); - bucket.totalSize += size; - this.stats.totalEntries++; - - // Evict if bucket is too large - this.evictFromBucketIfNeeded(bucket); - } - - // Thread-safe get operation - get(key: string): any | null { - const bucketIndex = this.hash(key); - const bucket = this.buckets[bucketIndex]; - const entry = bucket.entries.get(key); - - if (!entry) { - this.stats.misses++; - return null; - } - - // Check if entry is still fresh - const now = Date.now(); - if (now - entry.timestamp > this.maxEntryAge) { - bucket.entries.delete(key); - bucket.totalSize -= entry.size; - this.stats.totalEntries--; - this.stats.evictions++; - this.stats.misses++; - return null; - } - - this.stats.hits++; - return entry.value; - } - - // Message-specific operations - addMessage(channelId: string, message: any): void { - if (!message?.id) return; - const key = `msg:${channelId}:${message.id}`; - this.set(key, message); - } - - addMessageBatch(channelId: string, messages: any[]): void { - for (const message of messages) { - this.addMessage(channelId, message); - } - } - - getMessage(channelId: string, messageId: string): any | null { - const key = `msg:${channelId}:${messageId}`; - return this.get(key); - } - - getMessages(channelId: string): any[] { - const messages: any[] = []; - const prefix = `msg:${channelId}:`; - - // Search across all buckets for this channel's messages - for (const bucket of this.buckets) { - for (const [key, entry] of bucket.entries) { - if (key.startsWith(prefix)) { - const now = Date.now(); - if (now - entry.timestamp <= this.maxEntryAge) { - messages.push(entry.value); - } - } - } - } - - return messages.sort((a, b) => - new Date(a.timestamp || 0).getTime() - new Date(b.timestamp || 0).getTime() - ); - } - - getMessageCount(channelId: string): number { - const prefix = `msg:${channelId}:`; - let count = 0; - - for (const bucket of this.buckets) { - for (const [key, entry] of bucket.entries) { - if (key.startsWith(prefix)) { - const now = Date.now(); - if (now - entry.timestamp <= this.maxEntryAge) { - count++; - } - } - } - } - - return count; - } - - getTotalMessagesCached(): number { - let count = 0; - for (const bucket of this.buckets) { - for (const [key, entry] of bucket.entries) { - if (key.startsWith('msg:')) { - const now = Date.now(); - if (now - entry.timestamp <= this.maxEntryAge) { - count++; - } - } - } - } - return count; - } - - // User-specific operations - addUser(userId: string, user: any): void { - const key = `user:${userId}`; - this.set(key, user); - } - - getUser(userId: string): any | null { - const key = `user:${userId}`; - return this.get(key); - } - - // Channel data operations - addChannelData(channelId: string, data: any): void { - const key = `channel:${channelId}`; - this.set(key, data); - } - - getChannelData(channelId: string): any | null { - const key = `channel:${channelId}`; - return this.get(key); - } - - // Eviction policy (LRU-like but simplified for performance) - private evictFromBucketIfNeeded(bucket: Bucket): void { - const maxBucketSize = this.maxCacheSize / this.bucketCount; - - while (bucket.totalSize > maxBucketSize && bucket.entries.size > 0) { - // Find oldest entry - let oldestKey = ""; - let oldestTime = Date.now(); - - for (const [key, entry] of bucket.entries) { - if (entry.timestamp < oldestTime) { - oldestTime = entry.timestamp; - oldestKey = key; - } - } - - if (oldestKey) { - const entry = bucket.entries.get(oldestKey); - if (entry) { - bucket.entries.delete(oldestKey); - bucket.totalSize -= entry.size; - this.stats.totalEntries--; - this.stats.evictions++; - } - } else { - break; // Safety break - } - } - } - - // Background cleanup (like fastcache's background GC) - private performCleanup(): void { - const now = Date.now(); - let totalCleaned = 0; - - for (const bucket of this.buckets) { - // Only clean buckets that haven't been cleaned recently - if (now - bucket.lastCleanup < 30 * 1000) continue; // 30 seconds - - const keysToDelete: string[] = []; - - for (const [key, entry] of bucket.entries) { - if (now - entry.timestamp > this.maxEntryAge) { - keysToDelete.push(key); - } - } - - for (const key of keysToDelete) { - const entry = bucket.entries.get(key); - if (entry) { - bucket.entries.delete(key); - bucket.totalSize -= entry.size; - this.stats.totalEntries--; - totalCleaned++; - } - } - - bucket.lastCleanup = now; - } - - if (totalCleaned > 0) { - logger.debug(`Cleanup removed ${totalCleaned} expired entries`); - } - - // Update stats tracker - statsTracker.updateCacheStats({ - hits: this.stats.hits, - misses: this.stats.misses, - evictions: this.stats.evictions, - totalEntries: this.stats.totalEntries, - totalSize: this.getTotalSize() - }); - } - - // Get cache statistics - getStats() { - return { - ...this.stats, - totalSize: this.getTotalSize(), - bucketCount: this.bucketCount, - avgBucketSize: this.getTotalSize() / this.bucketCount - }; - } - - private getTotalSize(): number { - return this.buckets.reduce((total, bucket) => total + bucket.totalSize, 0); - } - - cleanup(): void { - if (this.cleanupInterval) { - clearInterval(this.cleanupInterval); - this.cleanupInterval = null; - } - - // Clear all buckets - for (const bucket of this.buckets) { - bucket.entries.clear(); - bucket.totalSize = 0; - } - - this.stats = { - hits: 0, - misses: 0, - evictions: 0, - totalEntries: 0 - }; - - logger.debug("FastCache cleanup completed"); - } -} - -export const fastCache = new FastCache(); \ No newline at end of file diff --git a/src/plugins/accelerator/modules/imagepreloader.ts b/src/plugins/accelerator/modules/imagepreloader.ts deleted file mode 100644 index 6c58bf94..00000000 --- a/src/plugins/accelerator/modules/imagepreloader.ts +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; -import { MessageStore } from "@webpack/common"; - -const logger = new Logger("Accelerator:ImagePreloader"); - -class ImagePreloader { - private preloadedImages = new Set(); - private preloadQueue: string[] = []; - private isPreloading = false; - private maxPreload = 10; - private intersectionObserver: IntersectionObserver | null = null; - - init(maxPreload: number): void { - this.maxPreload = maxPreload; - this.setupIntersectionObserver(); - logger.info("Image preloader initialized"); - } - - // Set up intersection observer for intelligent preloading - private setupIntersectionObserver(): void { - this.intersectionObserver = new IntersectionObserver( - (entries) => { - for (const entry of entries) { - if (entry.isIntersecting) { - this.preloadNearbyImages(entry.target); - } - } - }, - { - rootMargin: "100px 0px 100px 0px", // Preload when 100px away - threshold: 0.1 - } - ); - - // Observe existing images - this.observeExistingImages(); - - // Use MutationObserver to watch for new images - const mutationObserver = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === "childList") { - for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE) { - this.observeImagesInElement(node as Element); - } - } - } - } - }); - - mutationObserver.observe(document.body, { - childList: true, - subtree: true - }); - } - - private observeExistingImages(): void { - const images = document.querySelectorAll('img, [style*="background-image"]'); - for (const img of images) { - this.intersectionObserver?.observe(img); - } - } - - private observeImagesInElement(element: Element): void { - // Observe the element itself if it's an image - if (element.tagName === "IMG" || - (element as HTMLElement).style.backgroundImage) { - this.intersectionObserver?.observe(element); - } - - // Observe child images - const images = element.querySelectorAll('img, [style*="background-image"]'); - for (const img of images) { - this.intersectionObserver?.observe(img); - } - } - - private preloadNearbyImages(target: Element): void { - // Find adjacent images to preload - const container = target.closest('[class*="message"], [class*="content"]'); - if (!container) return; - - const nearbyImages = this.findNearbyImageUrls(container); - this.addToPreloadQueue(nearbyImages); - } - - private findNearbyImageUrls(container: Element): string[] { - const urls: string[] = []; - - // Find images in current and adjacent messages - const messageElements = container.parentElement?.querySelectorAll('[class*="message"]') || []; - const currentIndex = Array.from(messageElements).indexOf(container as Element); - - // Check current message and a few before/after - const start = Math.max(0, currentIndex - 2); - const end = Math.min(messageElements.length, currentIndex + 3); - - for (let i = start; i < end; i++) { - const messageEl = messageElements[i]; - - // Find image URLs in this message - const images = messageEl.querySelectorAll('img'); - for (const img of images) { - if (img.src && !this.preloadedImages.has(img.src)) { - urls.push(img.src); - } - } - - // Find background images - const elementsWithBg = messageEl.querySelectorAll('[style*="background-image"]'); - for (const el of elementsWithBg) { - const bgImage = (el as HTMLElement).style.backgroundImage; - const urlMatch = bgImage.match(/url\(['"]?(.*?)['"]?\)/); - if (urlMatch && urlMatch[1] && !this.preloadedImages.has(urlMatch[1])) { - urls.push(urlMatch[1]); - } - } - } - - return urls.slice(0, this.maxPreload); - } - - preloadChannelImages(channelId: string, maxImages: number): void { - try { - // Get messages from Discord's message store - const messages = MessageStore.getMessages(channelId); - if (!messages?._array) return; - - const imageUrls: string[] = []; - - // Extract image URLs from recent messages - for (const message of messages._array.slice(-20)) { // Last 20 messages - if (imageUrls.length >= maxImages) break; - - // Check attachments - if (message.attachments) { - for (const attachment of message.attachments) { - if (attachment.content_type?.startsWith('image/') && - attachment.url && - !this.preloadedImages.has(attachment.url)) { - imageUrls.push(attachment.url); - } - } - } - - // Check embeds - if (message.embeds) { - for (const embed of message.embeds) { - if (embed.image?.url && !this.preloadedImages.has(embed.image.url)) { - imageUrls.push(embed.image.url); - } - if (embed.thumbnail?.url && !this.preloadedImages.has(embed.thumbnail.url)) { - imageUrls.push(embed.thumbnail.url); - } - } - } - } - - this.addToPreloadQueue(imageUrls.slice(0, maxImages)); - } catch (error) { - logger.warn("Failed to preload channel images:", error); - } - } - - private addToPreloadQueue(urls: string[]): void { - for (const url of urls) { - if (!this.preloadedImages.has(url) && !this.preloadQueue.includes(url)) { - this.preloadQueue.push(url); - } - } - - if (!this.isPreloading) { - this.processPreloadQueue(); - } - } - - private async processPreloadQueue(): Promise { - if (this.isPreloading || this.preloadQueue.length === 0) return; - - this.isPreloading = true; - - while (this.preloadQueue.length > 0 && this.preloadedImages.size < this.maxPreload * 2) { - const url = this.preloadQueue.shift(); - if (!url || this.preloadedImages.has(url)) continue; - - try { - await this.preloadImage(url); - this.preloadedImages.add(url); - } catch (error) { - logger.debug(`Failed to preload image: ${url}`, error); - } - - // Add small delay to avoid blocking the main thread - await new Promise(resolve => setTimeout(resolve, 10)); - } - - this.isPreloading = false; - } - - private preloadImage(url: string): Promise { - return new Promise((resolve, reject) => { - const img = new Image(); - - const cleanup = () => { - img.onload = null; - img.onerror = null; - img.onabort = null; - }; - - img.onload = () => { - cleanup(); - resolve(); - }; - - img.onerror = () => { - cleanup(); - reject(new Error(`Failed to load ${url}`)); - }; - - img.onabort = () => { - cleanup(); - reject(new Error(`Aborted loading ${url}`)); - }; - - // Set timeout to avoid hanging - setTimeout(() => { - cleanup(); - reject(new Error(`Timeout loading ${url}`)); - }, 5000); - - img.src = url; - }); - } - - cleanup(): void { - if (this.intersectionObserver) { - this.intersectionObserver.disconnect(); - this.intersectionObserver = null; - } - - this.preloadedImages.clear(); - this.preloadQueue.length = 0; - this.isPreloading = false; - - logger.debug("Image preloader cleanup completed"); - } -} - -export const imagePreloader = new ImagePreloader(); \ No newline at end of file diff --git a/src/plugins/accelerator/modules/intersection.ts b/src/plugins/accelerator/modules/intersection.ts deleted file mode 100644 index c55f3275..00000000 --- a/src/plugins/accelerator/modules/intersection.ts +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; -import { statsTracker } from "./stats"; - -const logger = new Logger("Accelerator:IntersectionOptimizer"); - -class IntersectionOptimizer { - private imageObserver: IntersectionObserver | null = null; - private observedElements = new Set(); - private imagePreloadQueue = new Set(); - private isEnabled = false; - - init() { - this.setupImageObserver(); - this.isEnabled = true; - logger.info("Intersection optimizer initialized (scroll-safe mode)"); - } - - private setupImageObserver() { - // Only handle image lazy loading - no layout modifications to avoid scroll issues - this.imageObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - const img = entry.target as HTMLImageElement; - this.optimizeImageLoading(img); - this.imageObserver?.unobserve(img); - } - }); - }, { - rootMargin: '1000px 0px', // Preload images 1000px before they enter viewport - threshold: 0.01 - }); - - this.observeExistingImages(); - this.setupImageMutationObserver(); - } - - private observeExistingImages() { - if (!this.imageObserver) return; - - // Only observe Discord CDN images to avoid interfering with other content - const images = document.querySelectorAll('img[src*="cdn.discordapp.com"], img[src*="media.discordapp.net"]'); - images.forEach(img => { - if (!this.observedElements.has(img)) { - this.imageObserver!.observe(img); - this.observedElements.add(img); - } - }); - } - - private setupImageMutationObserver() { - if (!this.imageObserver) return; - - const mutationObserver = new MutationObserver(mutations => { - if (!this.isEnabled) return; - - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node instanceof HTMLElement) { - // Check for new Discord images only - const images = node.matches('img') ? [node] : - Array.from(node.querySelectorAll('img[src*="cdn.discordapp.com"], img[src*="media.discordapp.net"]')); - - images.forEach(img => { - if (!this.observedElements.has(img) && this.imageObserver) { - this.imageObserver.observe(img); - this.observedElements.add(img); - } - }); - } - }); - }); - }); - - mutationObserver.observe(document.body, { - childList: true, - subtree: true - }); - } - - private optimizeImageLoading(img: HTMLImageElement) { - try { - // Only apply safe image optimizations that don't affect layout - if (!img.loading) { - img.loading = 'lazy'; - } - if (!img.decoding) { - img.decoding = 'async'; - } - - // Preload higher quality version if available - const src = img.src; - if (src && (src.includes('cdn.discordapp.com') || src.includes('media.discordapp.net'))) { - this.preloadHigherQuality(src); - } - - logger.debug(`Optimized image loading: ${img.src}`); - statsTracker.incrementImagesOptimized(); - } catch (error) { - logger.error("Failed to optimize image loading:", error); - } - } - - private preloadHigherQuality(src: string) { - if (this.imagePreloadQueue.has(src)) return; - - this.imagePreloadQueue.add(src); - - try { - // Convert to higher quality if it's a Discord CDN image with quality params - let highQualitySrc = src; - - if (src.includes('?')) { - const url = new URL(src); - - // Remove width/height constraints for better quality - url.searchParams.delete('width'); - url.searchParams.delete('height'); - - // Set higher quality if quality param exists - if (url.searchParams.has('quality')) { - url.searchParams.set('quality', '100'); - } - - // Remove format constraints to get original format - if (url.searchParams.has('format')) { - url.searchParams.delete('format'); - } - - highQualitySrc = url.toString(); - } - - // Preload the higher quality version - if (highQualitySrc !== src) { - const preloadImg = new Image(); - preloadImg.onload = () => { - this.imagePreloadQueue.delete(src); - logger.debug(`Preloaded high quality image: ${highQualitySrc}`); - }; - preloadImg.onerror = () => { - this.imagePreloadQueue.delete(src); - }; - preloadImg.src = highQualitySrc; - } else { - this.imagePreloadQueue.delete(src); - } - } catch (error) { - this.imagePreloadQueue.delete(src); - logger.error("Failed to preload higher quality image:", error); - } - } - - cleanup() { - this.isEnabled = false; - - if (this.imageObserver) { - this.imageObserver.disconnect(); - this.imageObserver = null; - } - - this.observedElements.clear(); - this.imagePreloadQueue.clear(); - - logger.debug("Intersection optimizer cleanup completed"); - } -} - -export const intersectionOptimizer = new IntersectionOptimizer(); \ No newline at end of file diff --git a/src/plugins/accelerator/modules/messageAccessory.tsx b/src/plugins/accelerator/modules/messageAccessory.tsx deleted file mode 100644 index f4202a56..00000000 --- a/src/plugins/accelerator/modules/messageAccessory.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; -import { React } from "@webpack/common"; - -import { fastCache } from "./fastcache"; -import { statsTracker } from "./stats"; - -const logger = new Logger("Accelerator:MessageAccessory"); - -interface MessageAccessoryProps { - message: any; -} - -let processedMessages = new Set(); - -export function CacheIndicatorAccessory({ message }: MessageAccessoryProps) { - const [showIndicator, setShowIndicator] = React.useState(false); - const indicatorRef = React.useRef(null); - - React.useEffect(() => { - if (!message?.id || !message?.channel_id) return; - - const messageKey = `${message.channel_id}:${message.id}`; - - // Avoid processing the same message multiple times - if (processedMessages.has(messageKey)) return; - processedMessages.add(messageKey); - - // Add slight delay to ensure cache has been checked - setTimeout(() => { - const cachedMessage = fastCache.getMessage(message.channel_id, message.id); - - if (cachedMessage) { - setShowIndicator(true); - statsTracker.incrementMessagesServedFromCache(1); - logger.debug(`Message ${message.id} served from cache`); - } - }, 100); - }, [message?.id, message?.channel_id]); - - // Ensure proper positioning after render - React.useEffect(() => { - if (showIndicator && indicatorRef.current) { - const indicator = indicatorRef.current; - const messageElement = indicator.closest('[class*="message"]') || - indicator.closest('[id^="chat-messages-"]') || - indicator.closest('[class*="messageListItem-"]'); - - if (messageElement) { - const messageRect = messageElement.getBoundingClientRect(); - // Adjust position to be perfectly centered in the message highlight area - indicator.style.top = "50%"; - indicator.style.right = "12px"; - } - } - }, [showIndicator]); - - if (!showIndicator) return null; - - return ( -
- ); -} \ No newline at end of file diff --git a/src/plugins/accelerator/modules/preloader.ts b/src/plugins/accelerator/modules/preloader.ts deleted file mode 100644 index 41bce7e5..00000000 --- a/src/plugins/accelerator/modules/preloader.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { FluxDispatcher, ChannelStore, GuildChannelStore, ChannelActionCreators, MessageActions } from "@webpack/common"; -import { Logger } from "@utils/Logger"; - -const logger = new Logger("Accelerator:Preloader"); - -interface PreloadedChannel { - channelId: string; - timestamp: number; - messages: boolean; -} - -class ChannelPreloader { - private preloadedChannels = new Map(); - private preloadQueue = new Set(); - private maxPreloadAge = 5 * 60 * 1000; // 5 minutes - private preloadDistance = 3; - private isScrolling = false; - private scrollTimeout: number | null = null; - private lastScrollTime = 0; - - init(distance: number) { - this.preloadDistance = distance; - this.setupScrollDetection(); - logger.info("Channel preloader initialized (scroll-aware)"); - } - - private setupScrollDetection() { - // Detect when user is actively scrolling to avoid interference - const handleScroll = () => { - this.isScrolling = true; - this.lastScrollTime = Date.now(); - - if (this.scrollTimeout) { - clearTimeout(this.scrollTimeout); - } - - // Consider scrolling finished after 150ms of no scroll events - this.scrollTimeout = setTimeout(() => { - this.isScrolling = false; - }, 150); - }; - - // Listen to scroll events on potential scroll containers - document.addEventListener('scroll', handleScroll, { passive: true, capture: true }); - document.addEventListener('wheel', handleScroll, { passive: true, capture: true }); - } - - async preloadAdjacent(guildId: string | null, currentChannelId: string, distance: number) { - // Don't start new preloads while user is actively scrolling - if (this.isScrolling || (Date.now() - this.lastScrollTime) < 500) { - logger.debug("Skipping preload during scroll activity"); - return; - } - - try { - const adjacentChannels = this.getAdjacentChannels(guildId, currentChannelId, distance); - - // More conservative preloading to avoid interfering with scroll - const batchSize = 1; // Reduced from 2 to 1 - for (let i = 0; i < adjacentChannels.length; i += batchSize) { - // Check if user started scrolling during preload - if (this.isScrolling) { - logger.debug("Stopping preload due to scroll activity"); - break; - } - - const batch = adjacentChannels.slice(i, i + batchSize); - await Promise.all(batch.map(channelId => this.preloadChannel(channelId))); - - // Longer delay between batches to be less aggressive - if (i + batchSize < adjacentChannels.length) { - await new Promise(resolve => setTimeout(resolve, 200)); - } - } - - // Cleanup old preloaded channels - this.cleanup(); - } catch (error) { - logger.error("Failed to preload adjacent channels:", error); - } - } - - private getAdjacentChannels(guildId: string | null, currentChannelId: string, distance: number): string[] { - const channels: string[] = []; - - try { - if (guildId) { - // Guild channels - const guildChannels = GuildChannelStore.getChannels(guildId); - const selectableChannels = guildChannels.SELECTABLE || []; - - const currentIndex = selectableChannels.findIndex(ch => ch.channel.id === currentChannelId); - if (currentIndex !== -1) { - // Get channels before and after current - for (let i = 1; i <= distance; i++) { - const beforeIndex = currentIndex - i; - const afterIndex = currentIndex + i; - - if (beforeIndex >= 0) { - channels.push(selectableChannels[beforeIndex].channel.id); - } - if (afterIndex < selectableChannels.length) { - channels.push(selectableChannels[afterIndex].channel.id); - } - } - } - } else { - // DM channels - preload recent conversations - const recentChannels = this.getRecentDMChannels(currentChannelId, distance); - channels.push(...recentChannels); - } - } catch (error) { - logger.error("Failed to get adjacent channels:", error); - } - - return channels.filter(id => id !== currentChannelId); - } - - private getRecentDMChannels(excludeChannelId: string, count: number): string[] { - // Get recent DM channels from Discord's internal stores - try { - const privateChannels = ChannelStore.getSortedPrivateChannels(); - return privateChannels - .filter(channel => channel.id !== excludeChannelId) - .slice(0, count) - .map(channel => channel.id); - } catch (error) { - logger.error("Failed to get recent DM channels:", error); - return []; - } - } - - private async preloadChannel(channelId: string) { - if (this.preloadQueue.has(channelId)) return; - if (this.isScrolling) return; // Don't start new preloads during scroll - - const existingPreload = this.preloadedChannels.get(channelId); - const now = Date.now(); - - // Skip if recently preloaded - if (existingPreload && (now - existingPreload.timestamp) < this.maxPreloadAge) { - return; - } - - this.preloadQueue.add(channelId); - - try { - const channel = ChannelStore.getChannel(channelId); - if (!channel) return; - - // Check again if user started scrolling - if (this.isScrolling) { - this.preloadQueue.delete(channelId); - return; - } - - // Use Discord's internal preload system - but be more conservative - if (channel.guild_id) { - await ChannelActionCreators.preload(channel.guild_id, channelId); - } - - // Only preload messages for non-DM channels, and with smaller batch size - if (channel.type !== 1 && channel.type !== 3 && !this.isScrolling) { - await MessageActions.fetchMessages({ - channelId, - limit: 25 // Reduced from 50 to 25 - }); - } - - this.preloadedChannels.set(channelId, { - channelId, - timestamp: now, - messages: true - }); - - logger.debug(`Preloaded channel: ${channelId}`); - } catch (error) { - logger.error(`Failed to preload channel ${channelId}:`, error); - } finally { - this.preloadQueue.delete(channelId); - } - } - - cleanup() { - const now = Date.now(); - const toRemove: string[] = []; - - for (const [channelId, preload] of this.preloadedChannels) { - if ((now - preload.timestamp) > this.maxPreloadAge) { - toRemove.push(channelId); - } - } - - toRemove.forEach(channelId => this.preloadedChannels.delete(channelId)); - - if (toRemove.length > 0) { - logger.debug(`Cleaned up ${toRemove.length} old preloaded channels`); - } - - // Cleanup scroll detection - if (this.scrollTimeout) { - clearTimeout(this.scrollTimeout); - this.scrollTimeout = null; - } - } -} - -export const channelPreloader = new ChannelPreloader(); \ No newline at end of file diff --git a/src/plugins/accelerator/modules/stats.ts b/src/plugins/accelerator/modules/stats.ts deleted file mode 100644 index 0467ad27..00000000 --- a/src/plugins/accelerator/modules/stats.ts +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; - -const logger = new Logger("Accelerator:Stats"); - -interface AcceleratorStats { - channelSwitches: number; - messagesLoaded: number; - messagesCached: number; - messagesServedFromCache: number; - imagesOptimized: number; - imagesCached: number; - cacheHits: number; - cacheMisses: number; - averageLoadTime: number; - startTime: number; - lastUpdate: number; -} - -interface ChannelSwitchTracking { - channelId: string; - startTime: number; -} - -class StatsTracker { - private stats: AcceleratorStats; - private loadTimes: number[] = []; - private maxLoadTimeHistory = 50; - private currentChannelSwitch: ChannelSwitchTracking | null = null; - private imageLoadTimes = new Map(); // Track when image requests start - private processedImages = new Set(); // Track which images we've seen before - - constructor() { - this.stats = this.getInitialStats(); - } - - private getInitialStats(): AcceleratorStats { - return { - channelSwitches: 0, - messagesLoaded: 0, - messagesCached: 0, - messagesServedFromCache: 0, - imagesOptimized: 0, - imagesCached: 0, - cacheHits: 0, - cacheMisses: 0, - averageLoadTime: 0, - startTime: Date.now(), - lastUpdate: Date.now() - }; - } - - init() { - this.stats = this.getInitialStats(); - this.setupImageCacheTracking(); - logger.info("Stats tracker initialized - tracking real Discord performance"); - } - - // Track real Discord channel switching performance like messageFetchTimer - trackChannelSwitchStart(channelId: string): void { - this.currentChannelSwitch = { - channelId, - startTime: performance.now() - }; - logger.debug(`Channel switch started: ${channelId}`); - } - - trackChannelSwitchEnd(channelId: string): void { - if (!this.currentChannelSwitch || this.currentChannelSwitch.channelId !== channelId) { - logger.debug(`Channel switch end without matching start: ${channelId}`); - return; - } - - const loadTime = performance.now() - this.currentChannelSwitch.startTime; - this.recordLoadTime(loadTime); - this.incrementChannelSwitch(); - this.currentChannelSwitch = null; - - logger.debug(`Channel switch completed: ${channelId} in ${loadTime.toFixed(1)}ms`); - } - - // Set up real image cache tracking by hooking into image loading - private setupImageCacheTracking() { - const statsTracker = this; - - // Use MutationObserver to track all image elements added to DOM - const imageObserver = new MutationObserver((mutations) => { - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as Element; - - // Check if it's an image or contains images - const images = element.tagName === 'IMG' - ? [element as HTMLImageElement] - : Array.from(element.querySelectorAll('img')); - - images.forEach(img => { - this.trackImageElement(img); - }); - } - }); - }); - }); - - // Start observing - imageObserver.observe(document.body, { - childList: true, - subtree: true - }); - - // Track existing images - document.querySelectorAll('img').forEach(img => { - this.trackImageElement(img); - }); - } - - private trackImageElement(img: HTMLImageElement) { - const src = img.src || img.getAttribute('data-src') || ''; - - // Only track Discord CDN images - if (!src || (!src.includes('cdn.discordapp.com') && !src.includes('media.discordapp.net'))) { - return; - } - - const startTime = performance.now(); - this.imageLoadTimes.set(src, startTime); - - // Check if we've seen this image before (cache scenario) - const wasProcessed = this.processedImages.has(src); - - const onLoad = () => { - const endTime = performance.now(); - const loadTime = endTime - (this.imageLoadTimes.get(src) || endTime); - - if (wasProcessed) { - // Image was processed before, likely from cache - this.incrementImagesCached(); - if (loadTime < 50) { // Very fast load likely means cache hit - this.incrementCacheHit(); - } - } else { - // First time seeing this image - this.incrementImagesOptimized(); - this.processedImages.add(src); - if (loadTime >= 50) { // Slower load likely means cache miss - this.incrementCacheMiss(); - } - } - - this.imageLoadTimes.delete(src); - logger.debug(`Image loaded: ${src} in ${loadTime.toFixed(1)}ms (cached: ${wasProcessed})`); - - // Clean up listeners - img.removeEventListener('load', onLoad); - img.removeEventListener('error', onError); - }; - - const onError = () => { - this.imageLoadTimes.delete(src); - img.removeEventListener('load', onLoad); - img.removeEventListener('error', onError); - }; - - // If image is already loaded, track it immediately - if (img.complete && img.naturalWidth > 0) { - setTimeout(onLoad, 0); - } else { - img.addEventListener('load', onLoad); - img.addEventListener('error', onError); - } - } - - incrementChannelSwitch() { - this.stats.channelSwitches++; - this.updateTimestamp(); - logger.debug(`Channel switch tracked: ${this.stats.channelSwitches}`); - } - - incrementMessagesLoaded(count: number) { - this.stats.messagesLoaded += count; - this.updateTimestamp(); - logger.debug(`Messages loaded: +${count} (total: ${this.stats.messagesLoaded})`); - } - - incrementMessagesCached(count: number) { - this.stats.messagesCached += count; - this.updateTimestamp(); - logger.debug(`Messages cached: +${count} (total: ${this.stats.messagesCached})`); - } - - incrementMessagesServedFromCache(count: number) { - this.stats.messagesServedFromCache += count; - this.updateTimestamp(); - logger.debug(`Messages served from cache: +${count} (total: ${this.stats.messagesServedFromCache})`); - } - - incrementImagesOptimized() { - this.stats.imagesOptimized++; - this.updateTimestamp(); - logger.debug(`Images optimized: ${this.stats.imagesOptimized}`); - } - - incrementImagesCached() { - this.stats.imagesCached++; - this.updateTimestamp(); - logger.debug(`Images cached: ${this.stats.imagesCached}`); - } - - - - incrementCacheHit() { - this.stats.cacheHits++; - this.updateTimestamp(); - logger.debug(`Cache hit: ${this.stats.cacheHits}`); - } - - incrementCacheMiss() { - this.stats.cacheMisses++; - this.updateTimestamp(); - logger.debug(`Cache miss: ${this.stats.cacheMisses}`); - } - - updateCacheStats(cacheStats: any): void { - if (cacheStats.hits !== undefined) this.stats.cacheHits = cacheStats.hits; - if (cacheStats.misses !== undefined) this.stats.cacheMisses = cacheStats.misses; - if (cacheStats.totalMessagesCached !== undefined) this.stats.messagesCached = cacheStats.totalMessagesCached; - this.updateTimestamp(); - logger.debug(`Cache stats updated:`, cacheStats); - } - - recordLoadTime(time: number) { - // Only record realistic load times (filter out obviously wrong values) - if (time > 0 && time < 30000) { // Between 0 and 30 seconds - this.loadTimes.push(time); - if (this.loadTimes.length > this.maxLoadTimeHistory) { - this.loadTimes.shift(); - } - - this.stats.averageLoadTime = this.loadTimes.reduce((a, b) => a + b, 0) / this.loadTimes.length; - this.updateTimestamp(); - logger.debug(`Load time recorded: ${time.toFixed(1)}ms (avg: ${this.stats.averageLoadTime.toFixed(1)}ms)`); - } - } - - private updateTimestamp() { - this.stats.lastUpdate = Date.now(); - } - - getStats(): AcceleratorStats { - return { ...this.stats }; - } - - getFormattedUptime(): string { - const uptime = Date.now() - this.stats.startTime; - const minutes = Math.floor(uptime / 60000); - const seconds = Math.floor((uptime % 60000) / 1000); - return `${minutes}m ${seconds}s`; - } - - getCacheHitRate(): number { - const total = this.stats.cacheHits + this.stats.cacheMisses; - return total > 0 ? (this.stats.cacheHits / total) * 100 : 0; - } - - cleanup() { - // Reset tracking state - this.currentChannelSwitch = null; - this.imageLoadTimes.clear(); - this.processedImages.clear(); - this.stats = this.getInitialStats(); - this.loadTimes = []; - logger.debug("Stats tracker cleanup completed"); - } -} - -export const statsTracker = new StatsTracker(); - -// Floating Stats Window using vanilla DOM -export class FloatingStats { - private static container: HTMLDivElement | null = null; - private static isVisible = false; - private static updateInterval: number | null = null; - private static isDragging = false; - private static dragOffset = { x: 0, y: 0 }; - private static position = { x: window.innerWidth - 320, y: 20 }; - private static isMinimized = false; - - static show() { - if (this.isVisible) return; - - this.createContainer(); - this.setupEventListeners(); - this.startUpdating(); - this.isVisible = true; - - logger.info("Floating stats window shown"); - } - - static hide() { - if (!this.isVisible) return; - - if (this.container) { - document.body.removeChild(this.container); - this.container = null; - } - - if (this.updateInterval) { - clearInterval(this.updateInterval); - this.updateInterval = null; - } - - this.isVisible = false; - logger.info("Floating stats window hidden"); - } - - static toggle() { - if (this.isVisible) { - this.hide(); - } else { - this.show(); - } - } - - private static createContainer() { - this.container = document.createElement("div"); - this.container.id = "accelerator-stats-container"; - - this.updateStyles(); - this.updateContent(); - - document.body.appendChild(this.container); - } - - private static updateStyles() { - if (!this.container) return; - - Object.assign(this.container.style, { - position: "fixed", - top: `${this.position.y}px`, - left: `${this.position.x}px`, - width: "300px", - minHeight: this.isMinimized ? "40px" : "auto", - maxHeight: this.isMinimized ? "40px" : "500px", - backgroundColor: "var(--background-secondary)", - border: "1px solid var(--background-secondary-alt)", - borderRadius: "8px", - padding: "12px", - fontSize: "12px", - fontFamily: "var(--font-primary)", - color: "var(--text-normal)", - zIndex: "9999", - userSelect: "none", - boxShadow: "0 8px 24px rgba(0, 0, 0, 0.15)", - backdropFilter: "blur(10px)", - overflow: "hidden", - transition: "all 0.2s ease" - }); - } - - private static updateContent() { - if (!this.container) return; - - const stats = statsTracker.getStats(); - const uptime = statsTracker.getFormattedUptime(); - const cacheRate = statsTracker.getCacheHitRate(); - - const formatNumber = (num: number): string => { - if (num >= 1000) return (num / 1000).toFixed(1) + "k"; - return num.toString(); - }; - - const formatLoadTime = (time: number): string => { - return time < 1000 ? `${Math.round(time)}ms` : `${(time / 1000).toFixed(1)}s`; - }; - - const formatCacheRate = (rate: number): string => { - return rate.toFixed(1) + "%"; - }; - - const headerHTML = ` -
- 🚀 Accelerator Stats -
- - -
-
- `; - - const contentHTML = this.isMinimized ? '' : ` -
-
-
Channel Switches
-
${formatNumber(stats.channelSwitches)}
-
-
-
Channel Load Time
-
${formatLoadTime(stats.averageLoadTime)}
-
-
-
Messages from Discord
-
${formatNumber(stats.messagesLoaded)}
-
-
-
Messages from Cache
-
${formatNumber(stats.messagesServedFromCache)}
-
-
-
Messages Stored
-
${formatNumber(stats.messagesCached)}
-
-
-
Message Cache Rate
-
${formatCacheRate(cacheRate)}
-
-
-
Images Preloaded
-
${formatNumber(stats.imagesOptimized)}
-
-
-
Images from Cache
-
${formatNumber(stats.imagesCached)}
-
-
-
Uptime
-
${uptime}
-
-
- `; - - this.container.innerHTML = headerHTML + contentHTML; - this.setupToggleButton(); - } - - private static setupToggleButton() { - const minimizeBtn = document.getElementById("accelerator-minimize"); - const closeBtn = document.getElementById("accelerator-close"); - - if (minimizeBtn) { - minimizeBtn.onclick = (e) => { - e.stopPropagation(); - this.isMinimized = !this.isMinimized; - this.updateStyles(); - this.updateContent(); - }; - } - - if (closeBtn) { - closeBtn.onclick = (e) => { - e.stopPropagation(); - this.hide(); - }; - } - } - - private static setupEventListeners() { - if (!this.container) return; - - // Dragging functionality - this.container.onmousedown = (e) => { - if ((e.target as HTMLElement).tagName === "BUTTON") return; - - this.isDragging = true; - this.dragOffset.x = e.clientX - this.position.x; - this.dragOffset.y = e.clientY - this.position.y; - - if (this.container) { - this.container.style.cursor = "grabbing"; - this.container.style.opacity = "0.8"; - } - }; - - document.onmousemove = (e) => { - if (!this.isDragging || !this.container) return; - - this.position.x = e.clientX - this.dragOffset.x; - this.position.y = e.clientY - this.dragOffset.y; - - // Keep within screen bounds - this.position.x = Math.max(0, Math.min(window.innerWidth - 300, this.position.x)); - this.position.y = Math.max(0, Math.min(window.innerHeight - 100, this.position.y)); - - this.container.style.left = `${this.position.x}px`; - this.container.style.top = `${this.position.y}px`; - }; - - document.onmouseup = () => { - if (!this.isDragging) return; - - this.isDragging = false; - if (this.container) { - this.container.style.cursor = "default"; - this.container.style.opacity = "1"; - } - }; - } - - private static startUpdating() { - if (this.updateInterval) clearInterval(this.updateInterval); - - this.updateInterval = setInterval(() => { - if (this.isVisible && this.container) { - this.updateContent(); - } - }, 1000) as any; - } -} \ No newline at end of file diff --git a/src/plugins/accelerator/styles.css b/src/plugins/accelerator/styles.css deleted file mode 100644 index a16d3f2b..00000000 --- a/src/plugins/accelerator/styles.css +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Accelerator Stats Window - The ONLY CSS component - * This contains ONLY styles for the floating stats window - * NO performance CSS that could interfere with Discord's layout - */ - -.accelerator-stats-window { - position: fixed; - z-index: 10000; - background: rgba(32, 34, 37, 0.95); - border: 1px solid #40444b; - border-radius: 8px; - backdrop-filter: blur(10px); - font-family: 'gg sans', 'Noto Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 12px; - color: #dcddde; - min-width: 280px; - max-width: 400px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); - user-select: none; - cursor: move; -} - -.accelerator-stats-header { - padding: 12px 16px 8px; - border-bottom: 1px solid #40444b; - font-weight: 600; - display: flex; - justify-content: space-between; - align-items: center; - background: linear-gradient(90deg, #5865f2, #7289da); - color: white; - border-radius: 7px 7px 0 0; - cursor: move; -} - -.accelerator-stats-content { - padding: 12px 16px; - max-height: 400px; - overflow-y: auto; -} - -.accelerator-stats-content::-webkit-scrollbar { - width: 6px; -} - -.accelerator-stats-content::-webkit-scrollbar-track { - background: transparent; -} - -.accelerator-stats-content::-webkit-scrollbar-thumb { - background: #40444b; - border-radius: 3px; -} - -.accelerator-stats-section { - margin-bottom: 12px; -} - -.accelerator-stats-section h4 { - margin: 0 0 6px 0; - font-size: 11px; - font-weight: 600; - color: #b9bbbe; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.accelerator-stats-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; -} - -.accelerator-stats-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 4px 0; -} - -.accelerator-stats-label { - color: #b9bbbe; - font-size: 11px; -} - -.accelerator-stats-value { - color: #dcddde; - font-weight: 500; - font-size: 12px; -} - -.accelerator-stats-value.positive { - color: #3ba55c; -} - -.accelerator-stats-value.neutral { - color: #faa61a; -} - -.accelerator-stats-toggle { - background: none; - border: none; - color: #dcddde; - cursor: pointer; - font-size: 14px; - padding: 4px; - border-radius: 4px; - transition: background-color 0.2s; -} - -.accelerator-stats-toggle:hover { - background: rgba(255, 255, 255, 0.1); -} - -.accelerator-stats-window.minimized .accelerator-stats-content { - display: none; -} - -.accelerator-stats-window.minimized { - min-width: auto; -} - -/* Cache Indicator - Shows when a message was served from fastcache */ -.accelerator-cache-indicator { - position: absolute; - top: 4px; - right: 4px; - width: 8px; - height: 8px; - background: #3ba55c; - border-radius: 50%; - z-index: 10; - box-shadow: 0 0 0 1px rgba(32, 34, 37, 0.9), 0 1px 3px rgba(0, 0, 0, 0.3); - animation: accelerator-cache-pulse 2s ease-out; - cursor: help; -} - -@keyframes acceleratorCachePulse { - 0% { - transform: translateY(-50%) scale(1); - opacity: 1; - box-shadow: 0 0 0 2px rgba(32, 34, 37, 0.95), 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 0 0 rgba(59, 165, 92, 1); - } - - 50% { - transform: translateY(-50%) scale(1.4); - opacity: 0.6; - box-shadow: 0 0 0 2px rgba(32, 34, 37, 0.95), 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 0 12px rgba(59, 165, 92, 0.6); - } - - 100% { - transform: translateY(-50%) scale(1); - opacity: 1; - box-shadow: 0 0 0 2px rgba(32, 34, 37, 0.95), 0 2px 8px rgba(0, 0, 0, 0.5), 0 0 0 0 rgba(59, 165, 92, 1); - } -} - -/* Ensure the message container has relative positioning for the indicator */ -[class*="message-"], -[id^="chat-messages-"], -[data-list-item-id^="chat-messages"] { - position: relative; -} - -/* Additional support for Discord's message structure */ -[class*="messageListItem-"], -[class*="groupStart-"], -[class*="wrapper-"] { - position: relative; -} \ No newline at end of file diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2b8a7b35..d06e173b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -610,10 +610,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Cootshk", id: 921605971577548820n }, - galpt: { - name: "galpt", - id: 631418827841863712n - }, } satisfies Record); export const EquicordDevs = Object.freeze({