import { GatewayIntentBits, type GatewayDispatchEvents } from '@discordjs/core'; import { WebSocketManager } from '@discordjs/ws'; import { EventEmitter } from 'node:events'; import { REST } from '@discordjs/rest'; type AnyEvent = { t: string; d: any; s: number | null; op: number; shard?: number }; export interface GatewayFilters { eventTypes?: string[]; // e.g., ["MESSAGE_CREATE","MESSAGE_UPDATE"] guildIds?: string[]; channelIds?: string[]; } export class GatewayManager extends EventEmitter { #wsm: WebSocketManager; #queue: AnyEvent[] = []; #filters: GatewayFilters = {}; #max = 1000; constructor(token: string, intents: number){ super(); const rest = new REST({ version: '10' }).setToken(token); this.#wsm = new WebSocketManager({ token, intents, shardCount: 1, rest }); this.#wsm.on('ready', () => { console.error(`Gateway connected (intents: ${intents})`); }); this.#wsm.on('error', (error: any) => { console.error(`Gateway error:`, error); }); // Use 'dispatch' event name (WebSocketShardEvents.Dispatch = "dispatch") // Payload structure: { data: GatewayDispatchPayload; shardId: number; } this.#wsm.on('dispatch', (payload: { data: any; shardId: number }) => { try { // The dispatch payload has the actual gateway event in payload.data // payload.data has: t (type), d (data), s (sequence), op (opcode) const ev: AnyEvent = { t: payload.data.t, d: payload.data.d, s: payload.data.s, op: payload.data.op, shard: payload.shardId }; if (!this.#passes(ev)) return; this.#queue.push(ev); // Fix memory leak: remove excess events from the beginning of the queue if (this.#queue.length > this.#max) { this.#queue.splice(0, this.#queue.length - this.#max); } this.emit('event', ev); } catch (error) { console.error('Gateway event processing error:', error); this.emit('error', error); } }); } start(){ this.#wsm.connect(); } stop(){ this.#wsm.destroy(); this.#queue = []; this.removeAllListeners(); } setFilters(f: GatewayFilters){ this.#filters = f; } getEvents(max = 50){ return this.#queue.splice(0, Math.min(max, this.#queue.length)); } info(){ // WebSocketManager doesn't expose a status property directly // We'll use a simple connected flag for now return { connected: true }; } #passes(ev: AnyEvent): boolean { const f = this.#filters; if (f.eventTypes && f.eventTypes.length && !f.eventTypes.includes(ev.t)) return false; // Filter by guild/channel if IDs are present in payload (best-effort) const guildId = ev?.d?.guild_id ?? ev?.d?.guild?.id ?? null; if (f.guildIds && f.guildIds.length && guildId && !f.guildIds.includes(guildId)) return false; const channelId = ev?.d?.channel_id ?? ev?.d?.id ?? null; if (f.channelIds && f.channelIds.length && channelId && !f.channelIds.includes(channelId)) return false; return true; } }