diff --git a/adapters/discord/src/actions/shutdown.ts b/adapters/discord/src/actions/shutdown.ts index 5995b19..aa8f80b 100644 --- a/adapters/discord/src/actions/shutdown.ts +++ b/adapters/discord/src/actions/shutdown.ts @@ -3,5 +3,5 @@ import { logger } from "lib/common-logger"; export const handleShutdown = async () => { logger.info("bot is shutting down..."); - await logChannelService.sendLogMessage("bot is shutting down..."); + await logChannelService.sendLogMessage("ich geh schlafen..."); }; diff --git a/adapters/discord/src/entitites/messages/messages.service.ts b/adapters/discord/src/entitites/messages/messages.service.ts index 8944cbc..02c6f87 100644 --- a/adapters/discord/src/entitites/messages/messages.service.ts +++ b/adapters/discord/src/entitites/messages/messages.service.ts @@ -1,9 +1,11 @@ import type { MessagesServiceInterface } from "@avocadi/bot-core/entities/messages/messages.service"; import { createLogger } from "@avocadi/bot-core/lib/logger"; import { + type Channel, ChannelType, type DMChannel, type Message, + type MessagePayload, type PartialDMChannel, type User, } from "discord.js"; @@ -11,11 +13,11 @@ import { logChannelService } from "features/log-channel/log-channel.service"; import client from "lib/client"; export class MessagesService - implements MessagesServiceInterface + implements MessagesServiceInterface { private logger = createLogger("MessagesService"); - async sendToUser(userInput: User, message: string): Promise { + async sendToUser(userInput: User, message: MessagePayload): Promise { const user = await client.users.fetch(userInput.id); if (user) { @@ -47,6 +49,23 @@ export class MessagesService await logChannelService.sendLogMessage(logMessage); } + + async sendToChannel( + channel: Channel, + createMessageInput: MessagePayload, + ): Promise { + const fetchedChannel = await client.channels.fetch(channel.id); + + if (fetchedChannel?.isTextBased() && fetchedChannel.isSendable()) { + await fetchedChannel.send(createMessageInput); + } else { + this.logger.error( + `Channel with ID ${channel.id} not found or is not text-based.`, + ); + + throw new Error("Channel not found or is not text-based."); + } + } } export const messagesService = new MessagesService(); diff --git a/adapters/discord/src/features/water-me/water-me.controller.ts b/adapters/discord/src/features/water-me/water-me.controller.ts index 65b378d..8ea28ae 100644 --- a/adapters/discord/src/features/water-me/water-me.controller.ts +++ b/adapters/discord/src/features/water-me/water-me.controller.ts @@ -4,9 +4,7 @@ import { ButtonBuilder, type ButtonInteraction, ButtonStyle, - type CacheType, type ChatInputCommandInteraction, - type Interaction, } from "discord.js"; import { waterMeService } from "./water-me.service"; diff --git a/adapters/discord/src/features/water-me/water-me.service.ts b/adapters/discord/src/features/water-me/water-me.service.ts index cdaf70f..047fedb 100644 --- a/adapters/discord/src/features/water-me/water-me.service.ts +++ b/adapters/discord/src/features/water-me/water-me.service.ts @@ -1,8 +1,4 @@ import { WaterMeService } from "@avocadi/bot-core/features/water-me/water-me.service"; +import { messagesService } from "entitites/messages/messages.service"; -export const waterMeService = new WaterMeService({ - channelId: "123", - handleMessageSend: async (message, channelId) => { - console.log(`Sending message to channel ${channelId}: ${message}`); - }, -}); +export const waterMeService = new WaterMeService(messagesService); diff --git a/adapters/fluxer/src/actions/shutdown.ts b/adapters/fluxer/src/actions/shutdown.ts new file mode 100644 index 0000000..aa8f80b --- /dev/null +++ b/adapters/fluxer/src/actions/shutdown.ts @@ -0,0 +1,7 @@ +import { logChannelService } from "features/log-channel/log-channel.service"; +import { logger } from "lib/common-logger"; + +export const handleShutdown = async () => { + logger.info("bot is shutting down..."); + await logChannelService.sendLogMessage("ich geh schlafen..."); +}; diff --git a/adapters/fluxer/src/config/config.schema.ts b/adapters/fluxer/src/config/config.schema.ts index f5d79f1..efad3b2 100644 --- a/adapters/fluxer/src/config/config.schema.ts +++ b/adapters/fluxer/src/config/config.schema.ts @@ -13,6 +13,7 @@ export const ConfigSchema = z.object({ roleMapping: z.record(Roles, z.string()), serverId: z.string(), version: z.number(), + commandPrefix: z.string().default("!"), fluxer: z.object({ token: z.string(), applicationId: z.string(), diff --git a/adapters/fluxer/src/config/index.ts b/adapters/fluxer/src/config/index.ts index 01a9a04..8b2c185 100644 --- a/adapters/fluxer/src/config/index.ts +++ b/adapters/fluxer/src/config/index.ts @@ -1,8 +1,8 @@ import type z from "zod"; -import type { ConfigSchema } from "./config.schema"; +import { ConfigSchema } from "./config.schema"; import env from "./env"; -export const config: z.output = { +const configInput: z.input = { channelMapping: { text: { bot: "1473270893617315899", @@ -42,3 +42,5 @@ export const config: z.output = { applicationId: env.FLUXER_APPLICATION_ID, }, }; + +export const config = ConfigSchema.parse(configInput); diff --git a/adapters/fluxer/src/entities/messages/messages.service.ts b/adapters/fluxer/src/entities/messages/messages.service.ts new file mode 100644 index 0000000..de6428d --- /dev/null +++ b/adapters/fluxer/src/entities/messages/messages.service.ts @@ -0,0 +1,56 @@ +import type { MessagesServiceInterface } from "@avocadi/bot-core/entities/messages/messages.service"; +import { createLogger } from "@avocadi/bot-core/lib/logger"; +import type { + Channel, + Message, + MessageSendOptions, + User, +} from "@fluxerjs/core"; + +import { logChannelService } from "features/log-channel/log-channel.service"; +import client from "lib/client"; + +export class MessagesService + implements + MessagesServiceInterface +{ + private logger = createLogger("MessagesService"); + + async sendToUser( + userInput: User, + createMessageInput: MessageSendOptions, + ): Promise { + const user = await client.users.fetch(userInput.id); + + if (user) { + await user.send(createMessageInput); + } else { + this.logger.error(`User with ID ${userInput.id} not found.`); + } + } + + async sendToChannel( + channel: Channel, + createMessageInput: MessageSendOptions, + ): Promise { + const fetchedChannel = await client.channels.fetch(channel.id); + + if (fetchedChannel?.isSendable()) { + await fetchedChannel.send(createMessageInput); + } else { + this.logger.error( + `Channel with ID ${channel.id} not found or is not text-based.`, + ); + + throw new Error("Channel not found or is not text-based."); + } + } + + async logMessage(message: Message): Promise { + const logMessage = `<@${message.author.id}> sent a message:\n"${message.content}"`; + + await logChannelService.sendLogMessage(logMessage); + } +} + +export const messagesService = new MessagesService(); diff --git a/adapters/fluxer/src/features/log-channel/log-channel.service.ts b/adapters/fluxer/src/features/log-channel/log-channel.service.ts index 75b62e2..cb5bc31 100644 --- a/adapters/fluxer/src/features/log-channel/log-channel.service.ts +++ b/adapters/fluxer/src/features/log-channel/log-channel.service.ts @@ -1,18 +1,22 @@ +import { createLogger } from "@avocadi/bot-core/lib/logger"; import { config } from "config"; import client from "lib/client"; -import { logger } from "lib/common-logger"; export class LogChannelService { + private logger = createLogger("LogChannelService"); + private logChannelId = config.channelMapping.text.log; async getLogChannel() { + this.logger.debug(`fetching log channel with ID: ${this.logChannelId}`); + const logChannel = await client.channels.fetch(this.logChannelId); if (logChannel.isSendable()) { return logChannel; } else { - logger.fatal("Log channel not found or is not text-based."); - throw new Error("Log channel not found or is not text-based"); + this.logger.fatal("log channel not found or is not text-based."); + throw new Error("log channel not found or is not text-based"); } } diff --git a/adapters/fluxer/src/features/water-me/water-me.service.ts b/adapters/fluxer/src/features/water-me/water-me.service.ts new file mode 100644 index 0000000..983efe3 --- /dev/null +++ b/adapters/fluxer/src/features/water-me/water-me.service.ts @@ -0,0 +1,4 @@ +import { WaterMeService } from "@avocadi/bot-core/features/water-me/water-me.service"; +import { messagesService } from "entities/messages/messages.service"; + +export const waterMeService = new WaterMeService(messagesService); diff --git a/adapters/fluxer/src/fluxer.controller.ts b/adapters/fluxer/src/fluxer.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/adapters/fluxer/src/listeners/index.ts b/adapters/fluxer/src/listeners/index.ts index 269347d..ce85720 100644 --- a/adapters/fluxer/src/listeners/index.ts +++ b/adapters/fluxer/src/listeners/index.ts @@ -1,2 +1,3 @@ import "./ready.listener"; import "./stop.listener"; +import "./messages/messages.listener"; diff --git a/adapters/fluxer/src/listeners/messages/messages.listener.ts b/adapters/fluxer/src/listeners/messages/messages.listener.ts new file mode 100644 index 0000000..c6fe2ae --- /dev/null +++ b/adapters/fluxer/src/listeners/messages/messages.listener.ts @@ -0,0 +1,37 @@ +import { CommandKeys } from "@avocadi/bot-core/entities/commands/commands.schema"; +import { Events, type Message } from "@fluxerjs/core"; +import { config } from "config"; +import { messagesService } from "entities/messages/messages.service"; +import client from "lib/client"; +import { logger } from "lib/common-logger"; + +client.on(Events.MessageCreate, async (message: Message) => { + if ( + message.channel?.id !== config.channelMapping.text.log && + message.channel?.id !== config.channelMapping.text.bot + ) { + await messagesService.logMessage(message); + } + + if (message.content?.startsWith(config.commandPrefix)) { + await messagesService.logMessage(message); + logger.info( + `Command received: ${message.content} from user ${message.author.id}`, + ); + + const command = message.content + .slice(config.commandPrefix.length) + .trim() + .split(" ")[0]; + + logger.info(`Parsed command: ${command}`); + + const result = CommandKeys.safeParse(command); + + if (result.success) { + logger.info(`Command ${command} is valid.`); + } else { + logger.warn(`Command ${command} is not recognized.`); + } + } +}); diff --git a/adapters/fluxer/src/listeners/stop.listener.ts b/adapters/fluxer/src/listeners/stop.listener.ts index 851a76a..52ffc7a 100644 --- a/adapters/fluxer/src/listeners/stop.listener.ts +++ b/adapters/fluxer/src/listeners/stop.listener.ts @@ -1,3 +1,15 @@ -import { Events } from "@fluxerjs/core"; -import client from "lib/client"; -import { logger } from "lib/common-logger"; +import { handleShutdown } from "actions/shutdown"; + +process.on("exit", async () => { + await handleShutdown(); +}); + +process.on("SIGINT", async () => { + await handleShutdown(); + process.exit(0); +}); + +process.on("SIGTERM", async () => { + await handleShutdown(); + process.exit(0); +}); diff --git a/core/package.json b/core/package.json index 031bee6..744d578 100644 --- a/core/package.json +++ b/core/package.json @@ -43,9 +43,8 @@ "./features/dynamic-voice-channels/dynamic-voice-channels.schema": "./dist/features/dynamic-voice-channels/dynamic-voice-channels.schema.js", "./features/dynamic-voice-channels/dynamic-voice-channels.service": "./dist/features/dynamic-voice-channels/dynamic-voice-channels.service.js", "./features/greeting/greeting.service": "./dist/features/greeting/greeting.service.js", - "./features/text-based-feature/text-based-feature": "./dist/features/text-based-feature/text-based-feature.js", - "./features/text-based-feature/text-based-feature.schema": "./dist/features/text-based-feature/text-based-feature.schema.js", "./features/water-me/water-me.service": "./dist/features/water-me/water-me.service.js", + "./lib/common": "./dist/lib/common.js", "./lib/logger": "./dist/lib/logger.js", "./lib/utils": "./dist/lib/utils.js", "./lib/utils.test": "./dist/lib/utils.test.js", diff --git a/core/src/entities/messages/messages.service.ts b/core/src/entities/messages/messages.service.ts index 21f640c..4333dab 100644 --- a/core/src/entities/messages/messages.service.ts +++ b/core/src/entities/messages/messages.service.ts @@ -1,5 +1,19 @@ -export interface MessagesServiceInterface { - sendToUser(user: U, message: string): Promise; +import type { + BaseChannel, + BaseCreateMessage, + BaseMessage, + BaseUser, +} from "lib/common"; + +export interface MessagesServiceInterface< + U extends BaseUser = BaseUser, + M extends BaseMessage = BaseMessage, + C extends BaseChannel = BaseChannel, + CM extends BaseCreateMessage = BaseCreateMessage, +> { + sendToUser(user: U, createMessageInput: CM): Promise; + + sendToChannel(channel: C, createMessageInput: CM): Promise; logMessage(message: M): Promise; } diff --git a/core/src/features/text-based-feature/text-based-feature.schema.ts b/core/src/features/text-based-feature/text-based-feature.schema.ts deleted file mode 100644 index 526754f..0000000 --- a/core/src/features/text-based-feature/text-based-feature.schema.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type TextBasedFeatureHandleMessageSend = ( - message: string, - channelId: string, -) => void | Promise; - -export type TextBasedFeatureInput = { - channelId: string; - messagesService: TextBasedFeatureHandleMessageSend; -}; diff --git a/core/src/features/text-based-feature/text-based-feature.ts b/core/src/features/text-based-feature/text-based-feature.ts deleted file mode 100644 index a3e1f92..0000000 --- a/core/src/features/text-based-feature/text-based-feature.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { - TextBasedFeatureHandleMessageSend, - TextBasedFeatureInput, -} from "./text-based-feature.schema"; - -export class TextBasedFeature { - channelId: string; - handleMessageSend: TextBasedFeatureHandleMessageSend; - - constructor(input: TextBasedFeatureInput) { - this.channelId = input.channelId; - this.handleMessageSend = input.handleMessageSend; - } - - async sendMessage(input: { content: string }) { - this.handleMessageSend(input.content, this.channelId); - } -} diff --git a/core/src/features/water-me/water-me.service.ts b/core/src/features/water-me/water-me.service.ts index 8276ede..f822902 100644 --- a/core/src/features/water-me/water-me.service.ts +++ b/core/src/features/water-me/water-me.service.ts @@ -1,19 +1,19 @@ -import { TextBasedFeature } from "features/text-based-feature/text-based-feature"; -import type { TextBasedFeatureInput } from "features/text-based-feature/text-based-feature.schema"; +import type { MessagesServiceInterface } from "entities/messages/messages.service"; import { createLogger } from "lib/logger"; import { getRandomInt } from "lib/utils"; -export class WaterMeService extends TextBasedFeature { +export class WaterMeService { waterLevel: number; private logger = createLogger("WaterMeService"); private thirsty = 3 as const; private enough = 10 as const; + messagesService: MessagesServiceInterface; - constructor(input: TextBasedFeatureInput) { - super(input); + constructor(messagesService: MessagesServiceInterface) { this.waterLevel = 0; + this.messagesService = messagesService; } getReply() { @@ -47,7 +47,10 @@ export class WaterMeService extends TextBasedFeature { async notifyIfThirsty() { if (this.waterLevel <= this.thirsty) { - await this.sendMessage({ content: "ich brauche wasser :(" }); + await this.messagesService.sendToChannel( + { id: "channelId" }, + { content: "ich brauche wasser :(" }, + ); } } diff --git a/core/src/lib/common.ts b/core/src/lib/common.ts new file mode 100644 index 0000000..3edea44 --- /dev/null +++ b/core/src/lib/common.ts @@ -0,0 +1,20 @@ +export type BaseMessage = { + content: string; +}; + +export type BaseChannel = { + id: string; +}; + +export type BaseUser = { + id: string; +}; + +export type BaseCreateMessage = + | { + content?: string; + embeds?: unknown[] | null; + attachments?: unknown[] | null; + files?: unknown[] | null; + } + | string;