Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/api/routes/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ import { Router, type Request } from 'express';
import { messageService } from '../../discord/services/index.js';
import { SendMessageSchema, EditMessageSchema, GetMessagesSchema } from '../../types/api.types.js';

/** Route params for message endpoints (merged from parent router) */
interface MessageParams {
channelId?: string;
messageId?: string;
emoji?: string;
}

const router = Router({ mergeParams: true });

// Helper to get merged params
/**
* Helper to get merged params with type safety.
* Express mergeParams merges parent route params, so channelId comes from the parent router.
*/
function getParams(req: Request): { channelId: string; messageId?: string; emoji?: string } {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const params = req.params as any;
// Params are merged from parent router via mergeParams: true
const params = req.params as MessageParams;
return { channelId: params.channelId ?? '', messageId: params.messageId, emoji: params.emoji };
}

Expand Down
27 changes: 22 additions & 5 deletions src/discord/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Server as SocketIOServer } from 'socket.io';
import type { User, GuildScheduledEvent } from 'discord.js';
import { discordClient } from '../client.js';
import {
serializeMessage,
Expand Down Expand Up @@ -267,7 +268,8 @@ export function registerDiscordEvents(): void {

discordClient.on('userUpdate', (oldUser, newUser) => {
// oldUser may be partial, so we only serialize if not partial
const oldSerialized = oldUser.partial ? null : serializeUser(oldUser as any);
// Type assertion is safe here because we've checked partial === false
const oldSerialized = oldUser.partial ? null : serializeUser(oldUser as User);
broadcastEvent({
event: 'userUpdate',
guildId: null,
Expand Down Expand Up @@ -605,25 +607,40 @@ export function registerDiscordEvents(): void {

discordClient.on('guildScheduledEventUpdate', (oldEvent, newEvent) => {
// oldEvent may be partial
// Type assertions are safe here because we've checked partial === false
const oldSerialized = oldEvent && !oldEvent.partial
? serializeScheduledEvent(oldEvent as any)
? serializeScheduledEvent(oldEvent as GuildScheduledEvent)
: null;
broadcastEvent({
event: 'guildScheduledEventUpdate',
guildId: newEvent.guildId,
data: {
old: oldSerialized,
new: serializeScheduledEvent(newEvent as any),
new: serializeScheduledEvent(newEvent as GuildScheduledEvent),
},
});
});

discordClient.on('guildScheduledEventDelete', (event) => {
// event may be partial, but we still want to broadcast the deletion
// For partial events, only broadcast minimal deletion info
// Full serialization would fail as many properties are undefined
if (event.partial) {
broadcastEvent({
event: 'guildScheduledEventDelete',
guildId: event.guildId,
data: {
id: event.id,
guildId: event.guildId,
partial: true,
},
});
return;
}

broadcastEvent({
event: 'guildScheduledEventDelete',
guildId: event.guildId,
data: serializeScheduledEvent(event as any),
data: serializeScheduledEvent(event as GuildScheduledEvent),
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { loginDiscord, waitForReady, discordClient } from './discord/client.js';
import { registerDiscordEvents } from './discord/events/index.js';
import { createApiServer, startApiServer } from './api/server.js';
import { pluginManager } from './plugins/manager.js';
import { shutdownRateLimiter } from './api/middleware/rateLimit.js';
import { config } from './config/index.js';

async function main(): Promise<void> {
Expand Down Expand Up @@ -44,6 +45,9 @@ async function main(): Promise<void> {
async function shutdown(): Promise<void> {
console.log('\n🛑 Shutting down...');

// Clean up rate limiter intervals
shutdownRateLimiter();

// Unload plugins gracefully
if (pluginManager.count > 0) {
console.log('🔌 Unloading plugins...');
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ export class PluginManager {
normalizedArgs = args.map(wrapHandler);
}

pluginSubRouter!.use(...normalizedArgs as Parameters<typeof pluginSubRouter.use>);
// pluginSubRouter is guaranteed non-null here since we just created it
const router = pluginSubRouter as Router;
router.use(...normalizedArgs as Parameters<typeof router.use>);
},
};

Expand Down
5 changes: 5 additions & 0 deletions src/plugins/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ export function createEventHelpers(eventBus: PluginEventBus) {
};
}

/**
* Type for the event helpers object returned by createEventHelpers
*/
export type EventHelpers = ReturnType<typeof createEventHelpers>;

/**
* Plugin definition options for definePlugin helper
*/
Expand Down
9 changes: 8 additions & 1 deletion src/types/events.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,14 @@ export interface GuildScheduledEventUpdateEvent {
export interface GuildScheduledEventDeleteEvent {
event: 'guildScheduledEventDelete';
guildId: string;
data: SerializedScheduledEvent;
data: SerializedScheduledEvent | {
/** Event ID (always available even for partial events) */
id: string;
/** Guild ID (always available even for partial events) */
guildId: string;
/** Indicates this is a partial event with limited data */
partial: true;
};
}

export interface GuildScheduledEventUserAddEvent {
Expand Down
10 changes: 2 additions & 8 deletions src/types/plugin.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
SocketData,
} from './events.types.js';
import type { PluginEventBus, EventSubscription } from '../plugins/event-bus.js';
import type { PluginRouter, PluginLogger } from '../plugins/sdk.js';
import type { PluginRouter, PluginLogger, EventHelpers } from '../plugins/sdk.js';

/**
* The context passed to plugins on load.
Expand Down Expand Up @@ -92,13 +92,7 @@ export interface HoloPlugin {
* ```
*/
events?: (
helpers: {
onDiscord: <T = unknown>(event: string, handler: (data: T) => void | Promise<void>) => EventSubscription;
onCustom: <T = Record<string, unknown>>(event: string, handler: (data: T) => void | Promise<void>) => EventSubscription;
emit: <T extends Record<string, unknown>>(event: string, data: T) => void;
onPluginLoaded: (handler: (data: { name: string; version: string }) => void) => EventSubscription;
onPluginUnloaded: (handler: (data: { name: string }) => void) => EventSubscription;
},
helpers: EventHelpers,
ctx: PluginContext
) => EventSubscription[];

Expand Down