gaming-hub/server/src/core/plugin.ts

64 lines
1.9 KiB
TypeScript
Raw Normal View History

import { Client } from 'discord.js';
import express from 'express';
export interface Plugin {
name: string;
version: string;
description: string;
/** Called once when plugin is loaded */
init(ctx: PluginContext): Promise<void>;
/** Called when Discord client is ready */
onReady?(ctx: PluginContext): Promise<void>;
/** Called to register Express routes */
registerRoutes?(app: express.Application, ctx: PluginContext): void;
/** Called to build SSE snapshot data for new clients */
getSnapshot?(ctx: PluginContext): Record<string, any>;
/** Called on graceful shutdown */
destroy?(ctx: PluginContext): Promise<void>;
}
export interface PluginContext {
client: Client;
dataDir: string;
adminPwd: string;
allowedGuildIds: string[];
}
const loadedPlugins: Plugin[] = [];
export function registerPlugin(plugin: Plugin): void {
loadedPlugins.push(plugin);
console.log(`[Plugin] Registered: ${plugin.name} v${plugin.version}`);
}
export function getPlugins(): Plugin[] {
return [...loadedPlugins];
}
// ── Voice claim system ──
// Only one plugin can use voice per guild. When a new plugin claims voice,
// the previous claimant's cleanup callback is invoked automatically.
type VoiceClaimCleanup = () => void;
const voiceClaims = new Map<string, { plugin: string; cleanup: VoiceClaimCleanup }>();
export function claimVoice(guildId: string, pluginName: string, cleanup: VoiceClaimCleanup): void {
const existing = voiceClaims.get(guildId);
if (existing && existing.plugin !== pluginName) {
console.log(`[Voice] ${pluginName} claims guild ${guildId}, releasing ${existing.plugin}`);
existing.cleanup();
}
voiceClaims.set(guildId, { plugin: pluginName, cleanup });
}
export function releaseVoice(guildId: string, pluginName: string): void {
const claim = voiceClaims.get(guildId);
if (claim?.plugin === pluginName) {
voiceClaims.delete(guildId);
}
}