Add events module with dashboard UI, scheduling, signups, and settings updates; extend env/readme.
This commit is contained in:
@@ -1,15 +1,63 @@
|
||||
import { CommandHandler } from '../services/commandHandler.js';
|
||||
import { AutoModService } from '../services/automodService.js';
|
||||
import { LoggingService } from '../services/loggingService.js';
|
||||
import { MusicService } from '../services/musicService.js';
|
||||
import { TicketService } from '../services/ticketService.js';
|
||||
import { LevelService } from '../services/levelService.js';
|
||||
import { CommandHandler } from '../services/commandHandler';
|
||||
import { AutoModService } from '../services/automodService';
|
||||
import { LoggingService, setLoggingAdmin } from '../services/loggingService';
|
||||
import { MusicService } from '../services/musicService';
|
||||
import { TicketService } from '../services/ticketService';
|
||||
import { LevelService } from '../services/levelService';
|
||||
import { Client } from 'discord.js';
|
||||
import { BotModuleService } from '../services/moduleService';
|
||||
import { DynamicVoiceService } from '../services/dynamicVoiceService';
|
||||
import { AdminService } from '../services/adminService';
|
||||
import { StatuspageService } from '../services/statuspageService';
|
||||
import { BirthdayService } from '../services/birthdayService';
|
||||
import { ReactionRoleService } from '../services/reactionRoleService';
|
||||
import { EventService } from '../services/eventService';
|
||||
|
||||
export const context = {
|
||||
client: null as Client | null,
|
||||
commandHandler: null as CommandHandler | null,
|
||||
automod: new AutoModService(true, true),
|
||||
logging: new LoggingService(),
|
||||
music: new MusicService(),
|
||||
tickets: new TicketService(),
|
||||
leveling: new LevelService()
|
||||
leveling: new LevelService(),
|
||||
dynamicVoice: new DynamicVoiceService(),
|
||||
modules: new BotModuleService(),
|
||||
admin: new AdminService(),
|
||||
statuspage: new StatuspageService(),
|
||||
birthdays: new BirthdayService(),
|
||||
reactionRoles: new ReactionRoleService(),
|
||||
events: new EventService()
|
||||
};
|
||||
|
||||
context.modules.setHooks({
|
||||
musicEnabled: {
|
||||
onDisable: async () => context.music.stopAll()
|
||||
},
|
||||
dynamicVoiceEnabled: {
|
||||
onDisable: async (guildId: string) => context.dynamicVoice.cleanupGuild(guildId, context.client)
|
||||
},
|
||||
statuspageEnabled: {
|
||||
onEnable: async (guildId: string) => {
|
||||
const cfg = await context.statuspage.getConfig(guildId);
|
||||
await context.statuspage.saveConfig(guildId, { ...cfg, enabled: true });
|
||||
},
|
||||
onDisable: async (guildId: string) => {
|
||||
const cfg = await context.statuspage.getConfig(guildId);
|
||||
await context.statuspage.saveConfig(guildId, { ...cfg, enabled: false });
|
||||
}
|
||||
},
|
||||
birthdayEnabled: {
|
||||
onEnable: async (guildId: string) => context.birthdays.invalidate(guildId),
|
||||
onDisable: async (guildId: string) => context.birthdays.invalidate(guildId)
|
||||
},
|
||||
reactionRolesEnabled: {
|
||||
onEnable: async (guildId: string) => context.reactionRoles.resyncGuild(guildId),
|
||||
onDisable: async (guildId: string) => context.reactionRoles.resyncGuild(guildId)
|
||||
},
|
||||
eventsEnabled: {
|
||||
onEnable: async (guildId: string) => context.events.tick().catch(() => undefined)
|
||||
}
|
||||
});
|
||||
|
||||
setLoggingAdmin(context.admin);
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from 'path';
|
||||
|
||||
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
|
||||
|
||||
const required = ['DISCORD_TOKEN', 'DISCORD_CLIENT_ID', 'DISCORD_GUILD_ID', 'DATABASE_URL'] as const;
|
||||
const required = ['DISCORD_TOKEN', 'DISCORD_CLIENT_ID', 'DISCORD_CLIENT_SECRET', 'DATABASE_URL'] as const;
|
||||
|
||||
required.forEach((key) => {
|
||||
if (!process.env[key]) {
|
||||
@@ -11,9 +11,16 @@ required.forEach((key) => {
|
||||
}
|
||||
});
|
||||
|
||||
const normalizeBasePath = (raw?: string) => {
|
||||
if (!raw) return '';
|
||||
const withSlash = raw.startsWith('/') ? raw : `/${raw}`;
|
||||
return withSlash.replace(/\/+$/, '');
|
||||
};
|
||||
|
||||
export const env = {
|
||||
token: process.env.DISCORD_TOKEN ?? '',
|
||||
clientId: process.env.DISCORD_CLIENT_ID ?? '',
|
||||
clientSecret: process.env.DISCORD_CLIENT_SECRET ?? '',
|
||||
guildId: process.env.DISCORD_GUILD_ID ?? '',
|
||||
guildIds: (process.env.DISCORD_GUILD_IDS ?? process.env.DISCORD_GUILD_ID ?? '')
|
||||
.split(',')
|
||||
@@ -21,5 +28,12 @@ export const env = {
|
||||
.filter(Boolean),
|
||||
databaseUrl: process.env.DATABASE_URL ?? '',
|
||||
port: Number(process.env.PORT ?? 3000),
|
||||
sessionSecret: process.env.SESSION_SECRET ?? 'papo_dev_secret'
|
||||
sessionSecret: process.env.SESSION_SECRET ?? 'papo_dev_secret',
|
||||
supportRoleId: process.env.SUPPORT_ROLE_ID ?? '',
|
||||
webBasePath: normalizeBasePath(process.env.WEB_BASE_PATH ?? '/ucp'),
|
||||
publicBaseUrl: process.env.DASHBOARD_BASE_URL ? process.env.DASHBOARD_BASE_URL.replace(/\/+$/, '') : undefined,
|
||||
ownerIds: (process.env.OWNER_IDS ?? process.env.OWNER_ID ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
};
|
||||
|
||||
@@ -1,8 +1,225 @@
|
||||
import { prisma } from '../database';
|
||||
|
||||
export interface GuildSettings {
|
||||
welcomeChannelId?: string;
|
||||
logChannelId?: string;
|
||||
automodEnabled?: boolean;
|
||||
automodConfig?: any;
|
||||
statuspageEnabled?: boolean;
|
||||
statuspageConfig?: any;
|
||||
welcomeConfig?: {
|
||||
enabled?: boolean;
|
||||
channelId?: string;
|
||||
embedTitle?: string;
|
||||
embedDescription?: string;
|
||||
embedColor?: string;
|
||||
embedFooter?: string;
|
||||
embedThumbnail?: string;
|
||||
embedImage?: string;
|
||||
embedThumbnailData?: string;
|
||||
embedImageData?: string;
|
||||
};
|
||||
loggingConfig?: {
|
||||
logChannelId?: string;
|
||||
categories?: {
|
||||
joinLeave?: boolean;
|
||||
messageEdit?: boolean;
|
||||
messageDelete?: boolean;
|
||||
automodActions?: boolean;
|
||||
ticketActions?: boolean;
|
||||
musicEvents?: boolean;
|
||||
};
|
||||
};
|
||||
levelingEnabled?: boolean;
|
||||
ticketsEnabled?: boolean;
|
||||
musicEnabled?: boolean;
|
||||
dynamicVoiceEnabled?: boolean;
|
||||
dynamicVoiceConfig?: {
|
||||
lobbyChannelId?: string;
|
||||
categoryId?: string;
|
||||
userLimit?: number;
|
||||
bitrate?: number;
|
||||
template?: string;
|
||||
};
|
||||
eventsEnabled?: boolean;
|
||||
supportLoginConfig?: {
|
||||
panelChannelId?: string;
|
||||
panelMessageId?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
loginLabel?: string;
|
||||
logoutLabel?: string;
|
||||
autoRefresh?: boolean;
|
||||
};
|
||||
birthdayEnabled?: boolean;
|
||||
birthdayConfig?: {
|
||||
enabled?: boolean;
|
||||
channelId?: string;
|
||||
sendHour?: number;
|
||||
messageTemplate?: string;
|
||||
};
|
||||
reactionRolesEnabled?: boolean;
|
||||
reactionRolesConfig?: any;
|
||||
supportRoleId?: string;
|
||||
welcomeEnabled?: boolean;
|
||||
}
|
||||
|
||||
export const settings = new Map<string, GuildSettings>();
|
||||
class SettingsStore {
|
||||
private cache = new Map<string, GuildSettings>();
|
||||
|
||||
private applyModuleDefaults(cfg: GuildSettings): GuildSettings {
|
||||
const normalized: GuildSettings = { ...cfg };
|
||||
(
|
||||
[
|
||||
'ticketsEnabled',
|
||||
'automodEnabled',
|
||||
'welcomeEnabled',
|
||||
'levelingEnabled',
|
||||
'musicEnabled',
|
||||
'dynamicVoiceEnabled',
|
||||
'statuspageEnabled',
|
||||
'birthdayEnabled',
|
||||
'reactionRolesEnabled',
|
||||
'eventsEnabled'
|
||||
] as const
|
||||
).forEach((key) => {
|
||||
if (normalized[key] === undefined) normalized[key] = true;
|
||||
});
|
||||
// keep welcomeConfig flag in sync when present
|
||||
if (normalized.welcomeConfig && normalized.welcomeConfig.enabled === undefined && normalized.welcomeEnabled !== undefined) {
|
||||
normalized.welcomeConfig = { ...normalized.welcomeConfig, enabled: normalized.welcomeEnabled };
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
const rows = await prisma.guildSettings.findMany();
|
||||
rows.forEach((row) => {
|
||||
const cfg = {
|
||||
welcomeChannelId: row.welcomeChannelId ?? undefined,
|
||||
logChannelId: row.logChannelId ?? undefined,
|
||||
automodEnabled: row.automodEnabled ?? undefined,
|
||||
automodConfig: (row as any).automodConfig ?? undefined,
|
||||
statuspageEnabled: (row as any).statuspageEnabled ?? (row as any).automodConfig?.statuspageEnabled ?? undefined,
|
||||
statuspageConfig: (row as any).statuspageConfig ?? (row as any).automodConfig?.statuspageConfig ?? undefined,
|
||||
welcomeEnabled: (row as any).welcomeEnabled ?? undefined,
|
||||
welcomeConfig: (row as any).automodConfig?.welcomeConfig ?? undefined,
|
||||
loggingConfig: (row as any).automodConfig?.loggingConfig ?? undefined,
|
||||
levelingEnabled: row.levelingEnabled ?? undefined,
|
||||
ticketsEnabled: row.ticketsEnabled ?? undefined,
|
||||
musicEnabled: (row as any).musicEnabled ?? undefined,
|
||||
dynamicVoiceEnabled: (row as any).dynamicVoiceEnabled ?? undefined,
|
||||
dynamicVoiceConfig: (row as any).dynamicVoiceConfig ?? undefined,
|
||||
eventsEnabled: (row as any).eventsEnabled ?? undefined,
|
||||
supportLoginConfig: (row as any).supportLoginConfig ?? undefined,
|
||||
birthdayEnabled: (row as any).birthdayEnabled ?? undefined,
|
||||
birthdayConfig: (row as any).birthdayConfig ?? undefined,
|
||||
reactionRolesEnabled: (row as any).reactionRolesEnabled ?? undefined,
|
||||
reactionRolesConfig: (row as any).reactionRolesConfig ?? undefined,
|
||||
supportRoleId: row.supportRoleId ?? undefined
|
||||
} satisfies GuildSettings;
|
||||
this.cache.set(row.guildId, this.applyModuleDefaults(cfg));
|
||||
});
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public get(guildId: string) {
|
||||
const cached = this.cache.get(guildId);
|
||||
return cached ? this.applyModuleDefaults(cached) : undefined;
|
||||
}
|
||||
|
||||
public all() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public async set(guildId: string, partial: GuildSettings) {
|
||||
if ((partial as any).welcomeEnabled !== undefined) {
|
||||
partial.welcomeConfig = { ...(partial.welcomeConfig ?? {}), enabled: (partial as any).welcomeEnabled };
|
||||
delete (partial as any).welcomeEnabled;
|
||||
}
|
||||
if (partial.birthdayEnabled !== undefined) {
|
||||
partial.birthdayConfig = { ...(partial.birthdayConfig ?? {}), enabled: partial.birthdayEnabled };
|
||||
} else if (partial.birthdayConfig?.enabled !== undefined) {
|
||||
partial.birthdayEnabled = partial.birthdayConfig.enabled;
|
||||
}
|
||||
if (partial.reactionRolesEnabled !== undefined) {
|
||||
partial.reactionRolesConfig = { ...(partial.reactionRolesConfig ?? {}), enabled: partial.reactionRolesEnabled };
|
||||
} else if (partial.reactionRolesConfig?.enabled !== undefined) {
|
||||
partial.reactionRolesEnabled = partial.reactionRolesConfig.enabled;
|
||||
}
|
||||
const merged: GuildSettings = this.applyModuleDefaults({ ...(this.cache.get(guildId) ?? {}), ...partial });
|
||||
const mergedAutomod = {
|
||||
...(merged.automodConfig ?? {}),
|
||||
...(partial.automodConfig ?? {}),
|
||||
loggingConfig: partial.loggingConfig ?? merged.loggingConfig ?? merged.automodConfig?.loggingConfig,
|
||||
welcomeConfig: partial.welcomeConfig ?? merged.welcomeConfig ?? merged.automodConfig?.welcomeConfig,
|
||||
statuspageEnabled: merged.statuspageEnabled,
|
||||
statuspageConfig: partial.statuspageConfig ?? merged.statuspageConfig ?? merged.automodConfig?.statuspageConfig
|
||||
};
|
||||
if (partial.dynamicVoiceConfig) {
|
||||
merged.dynamicVoiceConfig = { ...(merged.dynamicVoiceConfig ?? {}), ...partial.dynamicVoiceConfig };
|
||||
}
|
||||
if (partial.supportLoginConfig) {
|
||||
merged.supportLoginConfig = { ...(merged.supportLoginConfig ?? {}), ...partial.supportLoginConfig };
|
||||
}
|
||||
if (partial.birthdayConfig) {
|
||||
merged.birthdayConfig = { ...(merged.birthdayConfig ?? {}), ...partial.birthdayConfig };
|
||||
}
|
||||
if (partial.reactionRolesConfig) {
|
||||
merged.reactionRolesConfig = { ...(merged.reactionRolesConfig ?? {}), ...partial.reactionRolesConfig };
|
||||
}
|
||||
merged.automodConfig = { ...mergedAutomod, supportLoginConfig: merged.supportLoginConfig ?? mergedAutomod['supportLoginConfig'] };
|
||||
merged.statuspageEnabled = mergedAutomod.statuspageEnabled;
|
||||
merged.statuspageConfig = mergedAutomod.statuspageConfig;
|
||||
merged.loggingConfig = mergedAutomod.loggingConfig;
|
||||
merged.welcomeConfig = mergedAutomod.welcomeConfig;
|
||||
this.cache.set(guildId, merged);
|
||||
await prisma.guildSettings.upsert({
|
||||
where: { guildId },
|
||||
update: {
|
||||
welcomeChannelId: merged.welcomeChannelId ?? null,
|
||||
logChannelId: merged.logChannelId ?? null,
|
||||
automodEnabled: merged.automodEnabled ?? null,
|
||||
automodConfig: merged.automodConfig ?? null,
|
||||
levelingEnabled: merged.levelingEnabled ?? null,
|
||||
ticketsEnabled: merged.ticketsEnabled ?? null,
|
||||
musicEnabled: merged.musicEnabled ?? null,
|
||||
statuspageEnabled: merged.statuspageEnabled ?? null,
|
||||
statuspageConfig: merged.statuspageConfig ?? null,
|
||||
dynamicVoiceEnabled: merged.dynamicVoiceEnabled ?? null,
|
||||
dynamicVoiceConfig: merged.dynamicVoiceConfig ?? null,
|
||||
eventsEnabled: (merged as any).eventsEnabled ?? null,
|
||||
birthdayEnabled: merged.birthdayEnabled ?? null,
|
||||
birthdayConfig: merged.birthdayConfig ?? null,
|
||||
reactionRolesEnabled: merged.reactionRolesEnabled ?? null,
|
||||
reactionRolesConfig: merged.reactionRolesConfig ?? null,
|
||||
supportRoleId: merged.supportRoleId ?? null
|
||||
},
|
||||
create: {
|
||||
guildId,
|
||||
welcomeChannelId: merged.welcomeChannelId ?? null,
|
||||
logChannelId: merged.logChannelId ?? null,
|
||||
automodEnabled: merged.automodEnabled ?? null,
|
||||
automodConfig: merged.automodConfig ?? null,
|
||||
levelingEnabled: merged.levelingEnabled ?? null,
|
||||
ticketsEnabled: merged.ticketsEnabled ?? null,
|
||||
musicEnabled: merged.musicEnabled ?? null,
|
||||
statuspageEnabled: merged.statuspageEnabled ?? null,
|
||||
statuspageConfig: merged.statuspageConfig ?? null,
|
||||
dynamicVoiceEnabled: merged.dynamicVoiceEnabled ?? null,
|
||||
dynamicVoiceConfig: merged.dynamicVoiceConfig ?? null,
|
||||
eventsEnabled: (merged as any).eventsEnabled ?? null,
|
||||
birthdayEnabled: merged.birthdayEnabled ?? null,
|
||||
birthdayConfig: merged.birthdayConfig ?? null,
|
||||
reactionRolesEnabled: merged.reactionRolesEnabled ?? null,
|
||||
reactionRolesConfig: merged.reactionRolesConfig ?? null,
|
||||
supportRoleId: merged.supportRoleId ?? null
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
|
||||
export const settingsStore = new SettingsStore();
|
||||
// Backwards compatibility for existing imports (read-only)
|
||||
export const settings = settingsStore.all();
|
||||
|
||||
Reference in New Issue
Block a user