From 3a190bb61db991050af21411e31e036d6104b5bd Mon Sep 17 00:00:00 2001 From: Matteo Juen Date: Thu, 24 Oct 2024 20:42:17 +0200 Subject: [PATCH] feat: new configure command to configure a textchannel for all cron tasks --- src/modules/command/command.module.ts | 11 ++- src/modules/command/command.service.ts | 3 + src/modules/command/commands/server-config.ts | 57 ++++++++++++++ src/modules/cron-tasks/cron.service.ts | 46 ++++++----- src/modules/cron-tasks/task.module.ts | 3 +- .../tasks/birthday-shoutout.task.ts | 65 +++++++++------- src/modules/event/services/clientReady.ts | 2 +- .../dto/create-or-update-server-config.dto.ts | 21 +++++ .../config/module/server-config.module.ts | 22 ++++++ .../config/service/server-config.service.ts | 76 +++++++++++++++++++ 10 files changed, 257 insertions(+), 49 deletions(-) create mode 100644 src/modules/command/commands/server-config.ts create mode 100644 src/modules/models/config/dto/create-or-update-server-config.dto.ts create mode 100644 src/modules/models/config/module/server-config.module.ts create mode 100644 src/modules/models/config/service/server-config.service.ts diff --git a/src/modules/command/command.module.ts b/src/modules/command/command.module.ts index 0ae4977..bab0dc2 100644 --- a/src/modules/command/command.module.ts +++ b/src/modules/command/command.module.ts @@ -18,6 +18,8 @@ import { SomeoneOnceSaidModule } from '../models/someone-once-said/module/someon import { PollModule } from '../models/poll/module/poll.module'; import { VersionModule } from '../models/version/module/version.module'; import { BirthdayEntryModule } from '../models/birthday/module/birthday-entry.module'; +import ConfigureServerChannelCommand from './commands/server-config'; +import { ServerConfigModule } from '../models/config/module/server-config.module'; @Module({ providers: [ @@ -34,10 +36,17 @@ import { BirthdayEntryModule } from '../models/birthday/module/birthday-entry.mo PollCommand, VersionCommand, AddBirthdayEntryCommand, + ConfigureServerChannelCommand, DeactivateBirthdayEntryShoutoutCommand, ActivateBirthdayEntryShoutoutCommand, ], - imports: [SomeoneOnceSaidModule, PollModule, VersionModule, BirthdayEntryModule], + imports: [ + SomeoneOnceSaidModule, + PollModule, + VersionModule, + BirthdayEntryModule, + ServerConfigModule, + ], exports: [CommandService], }) export class CommandModule {} diff --git a/src/modules/command/command.service.ts b/src/modules/command/command.service.ts index 05e756b..2c98b60 100644 --- a/src/modules/command/command.service.ts +++ b/src/modules/command/command.service.ts @@ -14,6 +14,7 @@ import { BirthdayEntry } from '../../schemas/birthday-entry.schema'; import AddBirthdayEntryCommand from './commands/add-birthday-entry'; import DeactivateBirthdayEntryShoutoutCommand from './commands/deactivate-birthday-shoutout'; import ActivateBirthdayEntryShoutoutCommand from './commands/activate-birthday-shoutout'; +import ConfigureServerChannelCommand from './commands/server-config'; @Injectable() export class CommandService { @@ -34,6 +35,7 @@ export class CommandService { addBirthdayEntryModule: AddBirthdayEntryCommand, deactivateBirthdayEntryShoutoutModule: DeactivateBirthdayEntryShoutoutCommand, activateBirthdayEntryShoutoutModule: ActivateBirthdayEntryShoutoutCommand, + configureServerChannelIdModule: ConfigureServerChannelCommand, ) { const commands: ACommand[] = [ //pingpongModule, @@ -50,6 +52,7 @@ export class CommandService { addBirthdayEntryModule, deactivateBirthdayEntryShoutoutModule, activateBirthdayEntryShoutoutModule, + configureServerChannelIdModule, ]; commands.forEach((command) => { if (command.data.name && !!command.execute) { diff --git a/src/modules/command/commands/server-config.ts b/src/modules/command/commands/server-config.ts new file mode 100644 index 0000000..03167c1 --- /dev/null +++ b/src/modules/command/commands/server-config.ts @@ -0,0 +1,57 @@ +import { + CacheType, + CommandInteraction, + EmbedBuilder, + SlashCommandBuilder, +} from 'discord.js'; +import { ACommand } from '../command.abstract'; +import { Inject } from '@nestjs/common'; +import { ServerConfigService } from '../../models/config/service/server-config.service'; +import { CreateOrUpdateServerConfigDto } from '../../models/config/dto/create-or-update-server-config.dto'; + +export default class ConfigureServerChannelCommand extends ACommand { + constructor( + @Inject(ServerConfigService) + private readonly configService: ServerConfigService, + ) { + super(); + } + data = new SlashCommandBuilder() + .setName('configure') + .setDescription( + 'set the channel id where the bot will send all scheduled messages', + ) + .addStringOption((option) => + option.setName('channelid').setDescription('the Id of the text-channel'), + ); + + // @Role(CommandAccessLevel.member) + async execute(arg: CommandInteraction): Promise { + const channelId = arg.options.get('channelid'); + if (!channelId) { + await arg.reply({ + content: 'you need to provide a valid channel Id! 🤓', + ephemeral: true, + }); + return false; + } + await arg.deferReply(); + const channelIdValue = channelId.value as unknown as string; + const instance: CreateOrUpdateServerConfigDto = { + channelId: channelIdValue, + serverId: arg.guildId, + }; + const created = + await this.configService.createOrUpdateServerConfig(instance); + const quoteEmbed = new EmbedBuilder() + .setTitle('Your scheduled messages textchannel was configured! 🤓') + .setDescription('🤓') + .setFooter({ + text: 'scheduled messages activated', + }) + .setTimestamp(new Date()); + await arg.editReply({ embeds: [quoteEmbed] }); + return true; + } +} + diff --git a/src/modules/cron-tasks/cron.service.ts b/src/modules/cron-tasks/cron.service.ts index 02e3d54..dc7f7ea 100644 --- a/src/modules/cron-tasks/cron.service.ts +++ b/src/modules/cron-tasks/cron.service.ts @@ -1,4 +1,4 @@ -import { Client, TextChannel } from 'discord.js'; +import { TextChannel } from 'discord.js'; import BirthdayShoutoutTask from './tasks/birthday-shoutout.task'; import { Inject, Injectable } from '@nestjs/common'; import WakeUpTask from './tasks/wake-up.task'; @@ -6,6 +6,8 @@ import * as cron from 'node-cron'; import { BirthdayEntryService } from '../models/birthday/service/birthday-entry.service'; import { TaskEntry } from './interfaces/task-entry.interface'; import { DiscordService } from '../discord/discord.service'; +import { ServerConfigService } from '../models/config/service/server-config.service'; +import { ServerConfigDocument } from '../../schemas/server-config.schema'; @Injectable() export class CronService { @@ -17,6 +19,8 @@ export class CronService { private readonly birthdayService: BirthdayEntryService, @Inject(DiscordService) private readonly discordService: DiscordService, + @Inject(ServerConfigService) + private readonly serverConfigService: ServerConfigService, ) { if (CronService.instance) { throw new Error(`ERROR: An instance has already been created.`); @@ -29,28 +33,32 @@ export class CronService { return CronService.instance; } - public init() { - this.tasks = [ - { + public async init() { + const servers: ServerConfigDocument[] = + await this.serverConfigService.getAll(); + let birthdayTasks = []; + servers.forEach((server) => { + const birthdayChannel = this.discordService.client.channels.cache.find( + (channel) => channel.id === server.channelId, + ) as TextChannel; + birthdayTasks.push({ name: 'birthday-shoutout', schedule: '0 10 * * *', - task: new BirthdayShoutoutTask( - this.discordService.client.channels.cache.find( - (channel) => channel.id === '447554141724737548', - ) as TextChannel, - this.birthdayService, - ), - }, - { + task: new BirthdayShoutoutTask(birthdayChannel, this.birthdayService), + }); + }); + let wakeUpTasks = []; + servers.forEach((server) => { + const wakeUpChannel = this.discordService.client.channels.cache.find( + (channel) => channel.id === server.channelId, + ) as TextChannel; + wakeUpTasks.push({ name: 'first-of-the-month', schedule: '0 12 1 * *', - task: new WakeUpTask( - this.discordService.client.channels.cache.find( - (channel) => channel.id === '447554141724737548', - ) as TextChannel, - ), - }, - ]; + task: new WakeUpTask(wakeUpChannel), + }); + }); + this.tasks = [...birthdayTasks, ...wakeUpTasks]; this.registerTasks(); } diff --git a/src/modules/cron-tasks/task.module.ts b/src/modules/cron-tasks/task.module.ts index 98edce4..dd515ce 100644 --- a/src/modules/cron-tasks/task.module.ts +++ b/src/modules/cron-tasks/task.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { BirthdayEntryModule } from '../models/birthday/module/birthday-entry.module'; import { CronService } from './cron.service'; import { DiscordModule } from '../discord/discord.module'; +import { ServerConfigModule } from '../models/config/module/server-config.module'; @Module({ - imports: [DiscordModule, BirthdayEntryModule], + imports: [DiscordModule, BirthdayEntryModule, ServerConfigModule], providers: [CronService], exports: [CronService], }) diff --git a/src/modules/cron-tasks/tasks/birthday-shoutout.task.ts b/src/modules/cron-tasks/tasks/birthday-shoutout.task.ts index bc9a222..ed56fc8 100644 --- a/src/modules/cron-tasks/tasks/birthday-shoutout.task.ts +++ b/src/modules/cron-tasks/tasks/birthday-shoutout.task.ts @@ -1,35 +1,46 @@ -import { EmbedBuilder, TextChannel } from 'discord.js' -import { ITask } from './interfaces/task.interface' -import { BirthdayEntryService } from '../../models/birthday/service/birthday-entry.service' +import { EmbedBuilder, TextChannel } from 'discord.js'; +import { ITask } from './interfaces/task.interface'; +import { BirthdayEntryService } from '../../models/birthday/service/birthday-entry.service'; export default class BirthdayShoutoutTask implements ITask { - private channel: TextChannel + private channel: TextChannel; - constructor(channel: TextChannel, private readonly birthdayService: BirthdayEntryService) { - this.channel = channel - } - - async execute(): Promise { - const birthDayEntries = await this.birthdayService.getEntryForToday() + constructor( + channel: TextChannel, + private readonly birthdayService: BirthdayEntryService, + ) { + this.channel = channel; + } - if (!birthDayEntries || birthDayEntries.length < 1) { - return - } + async execute(): Promise { + const birthDayEntries = await this.birthdayService.getEntryForToday(); - birthDayEntries.forEach((entry) => { - const creator = this.channel.members.find((member) => member.user.username === entry.username || member.user.displayName === entry.secName) + if (!birthDayEntries || birthDayEntries.length < 1) { + return; + } - const embed = new EmbedBuilder() - .setTitle(`🚨 Birthday Alert!! 🚨`) - .setColor('Random') - .setDescription(`${entry.username ?? entry.secName} is turning **${new Date().getFullYear() - entry.birthDate.getFullYear()}** years old today! 🎉🎂🎈`) - .setFooter({ - text: creator?.user.username ?? 'bingus', - iconURL: creator?.displayAvatarURL() ?? undefined, - }) - .setTimestamp(new Date()) + birthDayEntries.forEach((entry) => { + const creator = this.channel.members.find( + (member) => + member.user.username === entry.username || + member.user.displayName === entry.secName, + ); - this.channel.send({ embeds: [embed] }) + const embed = new EmbedBuilder() + .setTitle(`🚨 Birthday Alert!! 🚨`) + .setColor('Random') + .setDescription( + `${entry.username ?? entry.secName} is turning **${ + new Date().getFullYear() - entry.birthDate.getFullYear() + }** years old today! 🎉🎂🎈`, + ) + .setFooter({ + text: creator?.user.username ?? 'bingus', + iconURL: creator?.displayAvatarURL() ?? undefined, }) - } -} \ No newline at end of file + .setTimestamp(new Date()); + + this.channel.send({ embeds: [embed] }); + }); + } +} diff --git a/src/modules/event/services/clientReady.ts b/src/modules/event/services/clientReady.ts index f79a88a..8894ef2 100644 --- a/src/modules/event/services/clientReady.ts +++ b/src/modules/event/services/clientReady.ts @@ -16,6 +16,6 @@ export class ClientReady extends AEvent { console.log('Successfully connected to Discord'); console.log(`logged in as ${args[0].user.username}`); - this.cronService.init(); + await this.cronService.init(); } } diff --git a/src/modules/models/config/dto/create-or-update-server-config.dto.ts b/src/modules/models/config/dto/create-or-update-server-config.dto.ts new file mode 100644 index 0000000..35ac0e8 --- /dev/null +++ b/src/modules/models/config/dto/create-or-update-server-config.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; + +export class CreateOrUpdateServerConfigDto { + @ApiProperty({ + example: '239123321', + description: 'The discord serverId', + type: String, + }) + @IsNotEmpty() + serverId: string; + + @ApiProperty({ + example: '321123312123', + description: 'The discord channelId for cron tasks', + type: String, + }) + @IsNotEmpty() + channelId: string; +} + diff --git a/src/modules/models/config/module/server-config.module.ts b/src/modules/models/config/module/server-config.module.ts new file mode 100644 index 0000000..0d20ecb --- /dev/null +++ b/src/modules/models/config/module/server-config.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { ServerConfigService } from '../service/server-config.service'; +import { + ServerConfig, + ServerConfigSchema, +} from '../../../../schemas/server-config.schema'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { + name: ServerConfig.name, + schema: ServerConfigSchema, + }, + ]), + ], + controllers: [], + providers: [ServerConfigService], + exports: [ServerConfigService], +}) +export class ServerConfigModule {} diff --git a/src/modules/models/config/service/server-config.service.ts b/src/modules/models/config/service/server-config.service.ts new file mode 100644 index 0000000..768f926 --- /dev/null +++ b/src/modules/models/config/service/server-config.service.ts @@ -0,0 +1,76 @@ +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { CreateOrUpdateServerConfigDto } from '../dto/create-or-update-server-config.dto'; +import { ServerConfigDocument } from '../../../../schemas/server-config.schema'; + +export class ServerConfigService { + constructor( + @InjectModel('ServerConfig') + private readonly serverConfig: Model, + ) {} + async createOrUpdateServerConfig( + serverConfigDto: CreateOrUpdateServerConfigDto, + ) { + const entry = await this.serverConfig.findOne({ + channelId: serverConfigDto.channelId, + serverId: serverConfigDto.serverId, + }); + if (entry) { + return await this.updateEntry(serverConfigDto); + } else { + return await this.create(serverConfigDto); + } + } + + async create( + birthdayEntryDto: CreateOrUpdateServerConfigDto, + ): Promise { + try { + return await this.serverConfig.create({ + channelId: birthdayEntryDto.channelId, + serverId: birthdayEntryDto.serverId, + }); + } catch (e) { + return null; + } + } + + async updateEntry( + dto: CreateOrUpdateServerConfigDto, + ): Promise { + try { + return await this.serverConfig.findOneAndUpdate( + { serverId: dto.serverId }, + { + channelId: dto.channelId, + }, + { new: true }, + ); + } catch (e) { + return null; + } + } + + async getEntryForServer(serverId: string): Promise { + try { + const entry = await this.serverConfig.findOne({ + serverId: serverId, + }); + if (!entry) { + return null; + } + + return entry; + } catch (e) { + return null; + } + } + + async getAll(): Promise { + try { + return await this.serverConfig.find(); + } catch (e) { + return null; + } + } +}