UNPKG

discord-starboard-plus

Version:

Discord Starboard Plus: A clean, maintainable starboard system for Discord.js bots. Features per-guild configuration, TypeScript support. Highlight your community's favorite messages with customizable starboards.

237 lines 8.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StarboardManager = void 0; const ConfigValidator_1 = require("../utils/ConfigValidator"); const Logger_1 = require("../utils/Logger"); const GuildConfigManager_1 = require("./GuildConfigManager"); const ValidationService_1 = require("../services/ValidationService"); const MessageSearchService_1 = require("../services/MessageSearchService"); const EmbedBuilderService_1 = require("../services/EmbedBuilderService"); const ReactionAddHandler_1 = require("../handlers/ReactionAddHandler"); const ReactionRemoveHandler_1 = require("../handlers/ReactionRemoveHandler"); const MessageDeleteHandler_1 = require("../handlers/MessageDeleteHandler"); /** * Main orchestrator for the Starboard system. * * Implements clean architecture principles: * - Dependency injection for all services * - Proper event listener management (no memory leaks) * - Per-guild configuration support */ class StarboardManager { client; logger; guildConfig; validation; messageSearch; embedBuilder; handlers; /** * Bound event handlers stored for proper cleanup. */ boundHandlers; isDestroyed = false; /** * Create a new StarboardManager instance. * * @param client - Discord.js Client instance * @param options - Starboard configuration options * @throws Error if client is missing or options are invalid */ constructor(client, options) { if (!client) { throw new Error('Discord client is required'); } // Validate and normalize options const normalizedOptions = ConfigValidator_1.ConfigValidator.validateAndNormalize(options); this.client = client; // Initialize services this.logger = new Logger_1.Logger({ enabled: normalizedOptions.logActions, prefix: 'Starboard' }); this.guildConfig = new GuildConfigManager_1.GuildConfigManager(normalizedOptions, this.logger); this.validation = new ValidationService_1.ValidationService(this.logger); this.messageSearch = new MessageSearchService_1.MessageSearchService(this.logger); this.embedBuilder = new EmbedBuilderService_1.EmbedBuilderService(); // Initialize handlers this.handlers = this.createHandlers(); // Create bound handlers for proper cleanup // Note: Discord.js event handlers receive partial types that we need to handle this.boundHandlers = { reactionAdd: (r, u) => this.handleReactionAdd(r, u), reactionRemove: (r, u) => this.handleReactionRemove(r, u), messageDelete: (m) => this.handleMessageDelete(m) }; // Attach event listeners this.attachListeners(); this.logger.success('Starboard system initialized'); } /** * Create handler instances with injected dependencies. */ createHandlers() { const dependencies = { client: this.client, logger: this.logger, guildConfig: this.guildConfig, validation: this.validation, messageSearch: this.messageSearch, embedBuilder: this.embedBuilder }; return { reactionAdd: new ReactionAddHandler_1.ReactionAddHandler(dependencies), reactionRemove: new ReactionRemoveHandler_1.ReactionRemoveHandler(dependencies), messageDelete: new MessageDeleteHandler_1.MessageDeleteHandler(dependencies) }; } /** * Attach event listeners to the client. */ attachListeners() { this.logger.info('Initializing event listeners...'); this.client.on('messageReactionAdd', this.boundHandlers.reactionAdd); this.client.on('messageReactionRemove', this.boundHandlers.reactionRemove); this.client.on('messageDelete', this.boundHandlers.messageDelete); this.logger.success('Event listeners attached'); } /** * Wrapper for reaction add handler. * Handles partial reactions by fetching the full reaction if needed. */ async handleReactionAdd(reaction, user) { if (this.isDestroyed) return; const fullReaction = reaction.partial ? await reaction.fetch().catch(() => null) : reaction; if (!fullReaction) return; const fullUser = user.partial ? await user.fetch().catch(() => null) : user; if (!fullUser) return; return this.handlers.reactionAdd.handle(fullReaction, fullUser); } /** * Wrapper for reaction remove handler. * Handles partial reactions by fetching the full reaction if needed. */ async handleReactionRemove(reaction, user) { if (this.isDestroyed) return; // Fetch partial reaction if needed const fullReaction = reaction.partial ? await reaction.fetch().catch(() => null) : reaction; if (!fullReaction) return; // Fetch partial user if needed const fullUser = user.partial ? await user.fetch().catch(() => null) : user; if (!fullUser) return; return this.handlers.reactionRemove.handle(fullReaction, fullUser); } /** * Wrapper for message delete handler. * Handles partial messages - note: deleted messages may not be fetchable. */ async handleMessageDelete(message) { if (this.isDestroyed) return; // For deleted messages, we may not be able to fetch the full message // But we still have the message ID which is what we need return this.handlers.messageDelete.handle(message); } /** * Clean up event listeners and destroy the instance. * FIX: This method properly removes event listeners, fixing the memory leak. */ destroy() { if (this.isDestroyed) { this.logger.warn('StarboardManager already destroyed'); return; } this.client.off('messageReactionAdd', this.boundHandlers.reactionAdd); this.client.off('messageReactionRemove', this.boundHandlers.reactionRemove); this.client.off('messageDelete', this.boundHandlers.messageDelete); this.isDestroyed = true; this.logger.info('Starboard system destroyed'); } /** * Check if the manager has been destroyed. */ isActive() { return !this.isDestroyed; } // ========================================================================= // Public API for per-guild configuration // ========================================================================= /** * Set configuration for a specific guild. * * @param guildId - The guild ID * @param options - Partial options to merge with defaults */ setGuildConfig(guildId, options) { this.guildConfig.setGuildConfig(guildId, options); } /** * Get configuration for a specific guild. * * @param guildId - The guild ID * @returns The guild's configuration (or defaults if not set) */ getGuildConfig(guildId) { return this.guildConfig.getGuildConfig(guildId); } /** * Check if a guild has custom configuration. * * @param guildId - The guild ID */ hasGuildConfig(guildId) { return this.guildConfig.hasGuildConfig(guildId); } /** * Remove custom configuration for a guild. * * @param guildId - The guild ID * @returns true if config was removed, false if it didn't exist */ removeGuildConfig(guildId) { return this.guildConfig.removeGuildConfig(guildId); } /** * Get the default options used for guilds without custom config. */ getDefaultOptions() { return this.guildConfig.getDefaultOptions(); } /** * Update the default options. * Affects all guilds without custom configuration. * * @param options - Partial options to merge with current defaults */ updateDefaultOptions(options) { this.guildConfig.setDefaultOptions(options); } /** * Get list of guilds with custom configuration. */ getConfiguredGuilds() { return this.guildConfig.getConfiguredGuilds(); } /** * Get number of guilds with custom configuration. */ getConfiguredGuildCount() { return this.guildConfig.getConfiguredGuildCount(); } } exports.StarboardManager = StarboardManager; //# sourceMappingURL=StarboardManager.js.map