implement first interaction handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { config } from "config";
|
import { config } from "config";
|
||||||
import { Routes } from "discord.js";
|
import { Routes } from "discord.js";
|
||||||
import getCommands from "entitites/commands";
|
import getCommands from "entitites/commands";
|
||||||
|
import { logger } from "lib/common-logger";
|
||||||
import { discordRestClient } from "lib/rest-client";
|
import { discordRestClient } from "lib/rest-client";
|
||||||
|
|
||||||
export const publishCommands = async () => {
|
export const publishCommands = async () => {
|
||||||
@@ -11,8 +12,8 @@ export const publishCommands = async () => {
|
|||||||
body: getCommands(),
|
body: getCommands(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log("Successfully added commands");
|
logger.info("successfully added commands");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
logger.error("failed to add commands:", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import { logChannelService } from "features/log-channel/log-channel.service";
|
|||||||
import { logger } from "lib/common-logger";
|
import { logger } from "lib/common-logger";
|
||||||
|
|
||||||
export const handleShutdown = async () => {
|
export const handleShutdown = async () => {
|
||||||
logger.info("Bot is shutting down...");
|
logger.info("bot is shutting down...");
|
||||||
await logChannelService.sendLogMessage("Bot is shutting down...");
|
await logChannelService.sendLogMessage("bot is shutting down...");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const ConfigSchema = z.object({
|
|||||||
reactionRoles: z.object({
|
reactionRoles: z.object({
|
||||||
allowedMessageIds: z.array(z.string()),
|
allowedMessageIds: z.array(z.string()),
|
||||||
}),
|
}),
|
||||||
serverId: z.string(),
|
guildId: z.string(),
|
||||||
version: z.number(),
|
version: z.number(),
|
||||||
discord: z.object({
|
discord: z.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ export const config: z.output<typeof ConfigSchema> = {
|
|||||||
channelMapping: {
|
channelMapping: {
|
||||||
text: {
|
text: {
|
||||||
bot: "1317253291633279026",
|
bot: "1317253291633279026",
|
||||||
|
log: "1321972449365065728",
|
||||||
bump: "1330310075759329412",
|
bump: "1330310075759329412",
|
||||||
feedback: "1321626600185266176",
|
feedback: "1321626600185266176",
|
||||||
help: "1321860125127872605",
|
help: "1321860125127872605",
|
||||||
introduction: "1321473655523246171",
|
introduction: "1321473655523246171",
|
||||||
news: "1321953521435934741",
|
news: "1321953521435934741",
|
||||||
notification: "1321972449365065728",
|
|
||||||
"off-topic": "1316153372507639855",
|
"off-topic": "1316153372507639855",
|
||||||
rules: "1316153372507639849",
|
rules: "1316153372507639849",
|
||||||
testing: "1451310086864507112",
|
testing: "1451310086864507112",
|
||||||
@@ -40,7 +40,7 @@ export const config: z.output<typeof ConfigSchema> = {
|
|||||||
"1321491461111283722", // Example message ID for reaction roles
|
"1321491461111283722", // Example message ID for reaction roles
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
serverId: "1316153371899592774",
|
guildId: "1316153371899592774",
|
||||||
version: 1,
|
version: 1,
|
||||||
discord: {
|
discord: {
|
||||||
token: env.DISCORD_TOKEN,
|
token: env.DISCORD_TOKEN,
|
||||||
|
|||||||
@@ -6,117 +6,6 @@ import {
|
|||||||
import client from "lib/client";
|
import client from "lib/client";
|
||||||
|
|
||||||
export default class DiscordController extends EventEmitter {
|
export default class DiscordController extends EventEmitter {
|
||||||
private waterMeController: WaterMeController = waterMeController;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
let channelListeners = new Map();
|
|
||||||
|
|
||||||
client.on(
|
|
||||||
Events.VoiceStateUpdate,
|
|
||||||
async (oldState: VoiceState, newState: VoiceState) => {
|
|
||||||
// check if user joined a vc
|
|
||||||
if (
|
|
||||||
(!oldState.channelId && newState.channelId) ||
|
|
||||||
oldState.channelId !== newState.channelId
|
|
||||||
) {
|
|
||||||
// check if right vc
|
|
||||||
if (
|
|
||||||
newState.channelId === config.discord.vchannelIdForTwo ||
|
|
||||||
newState.channelId === config.discord.vchannelIdForThree ||
|
|
||||||
newState.channelId === config.discord.vchannelIdForFour ||
|
|
||||||
newState.channelId === config.discord.vchannelIdForGroup
|
|
||||||
) {
|
|
||||||
const channel = newState.channel;
|
|
||||||
if (!channel) {
|
|
||||||
console.error("channel not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const newChannel =
|
|
||||||
await this.dynamicVChannelService.createVChannel(
|
|
||||||
newState,
|
|
||||||
channel,
|
|
||||||
);
|
|
||||||
// move user in new channel
|
|
||||||
await newState.setChannel(newChannel);
|
|
||||||
// create specific listener for channel
|
|
||||||
const channelListener = async (
|
|
||||||
oldState: VoiceState,
|
|
||||||
newState: VoiceState,
|
|
||||||
) => {
|
|
||||||
channelListeners =
|
|
||||||
await this.dynamicVChannelService.deleteVChannel(
|
|
||||||
oldState,
|
|
||||||
newState,
|
|
||||||
newChannel,
|
|
||||||
channelListeners,
|
|
||||||
channelListener,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
// save listener in map
|
|
||||||
channelListeners.set(newChannel.id, channelListener);
|
|
||||||
// add listener
|
|
||||||
client.on(Events.VoiceStateUpdate, channelListener);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("error while duplicating channel", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`----------------\nversion ${config.discord.version}\n----------------`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setActivity(state: number) {
|
|
||||||
switch (state) {
|
|
||||||
case 0:
|
|
||||||
client.user?.setActivity(" ", { type: 0 });
|
|
||||||
console.log("set activity");
|
|
||||||
client.user?.setPresence({
|
|
||||||
status: "invisible",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
client.user?.setActivity("spielt sudoku", { type: 0 });
|
|
||||||
console.log("set activity");
|
|
||||||
client.user?.setPresence({
|
|
||||||
status: "online",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
|
||||||
if (interaction.isModalSubmit()) {
|
|
||||||
await this.handleModalSubmit(interaction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isChatInputCommand()) {
|
|
||||||
await this.handleChatInputCommand(interaction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (interaction.isButton()) {
|
|
||||||
await this.handleButton(interaction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleButton(interaction: ButtonInteraction<CacheType>) {
|
|
||||||
const { customId } = interaction;
|
|
||||||
console.log(interaction.customId);
|
|
||||||
|
|
||||||
if (customId.toLowerCase().includes("moreWater")) {
|
|
||||||
await this.waterMeService.handleInteraction(interaction);
|
|
||||||
}
|
|
||||||
if (customId.toLowerCase().includes("medication")) {
|
|
||||||
await this.medicationService.handleInteraction(interaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleChatInputCommand(
|
async handleChatInputCommand(
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { VoiceChannelsServiceInterface } from "@avocadi/bot-core/entities/channels/voice/voice-channels.service";
|
||||||
|
import { config } from "config";
|
||||||
|
import { ChannelType, type VoiceChannel } from "discord.js";
|
||||||
|
import client from "lib/client";
|
||||||
|
|
||||||
|
export class VoiceChannelsService
|
||||||
|
implements VoiceChannelsServiceInterface<VoiceChannel>
|
||||||
|
{
|
||||||
|
async getVoiceChannelById(channelId: string) {
|
||||||
|
const channel = await client.channels.fetch(channelId);
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
throw new Error(`Channel with id ${channelId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel.type !== ChannelType.GuildVoice) {
|
||||||
|
throw new Error(`Channel with id ${channelId} is not a voice channel`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createVoiceChannel(name: string) {
|
||||||
|
const guild = await client.guilds.fetch(config.guildId);
|
||||||
|
const channel = await guild.channels.create({
|
||||||
|
name,
|
||||||
|
type: ChannelType.GuildVoice,
|
||||||
|
});
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
async cloneVoiceChannel(
|
||||||
|
channel: VoiceChannel,
|
||||||
|
options?: { name?: string; position?: number },
|
||||||
|
) {
|
||||||
|
const clonedChannel = await channel.clone({
|
||||||
|
name: options?.name,
|
||||||
|
position: options?.position,
|
||||||
|
});
|
||||||
|
return clonedChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteVoiceChannel(channelId: string) {
|
||||||
|
const channel = await this.getVoiceChannelById(channelId);
|
||||||
|
|
||||||
|
await channel.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const voiceChannelsService = new VoiceChannelsService();
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { VoiceChannels } from "@avocadi/bot-core/entities/channels/channels.schema";
|
||||||
|
import { DynamicVoiceChannelsService } from "@avocadi/bot-core/features/dynamic-voice-channels/dynamic-voice-channels.service";
|
||||||
|
import { config } from "config";
|
||||||
|
import { voiceChannelsService } from "entitites/channels/voice/voice-channels.service";
|
||||||
|
|
||||||
|
export const dynamicVoiceChannelService = new DynamicVoiceChannelsService(
|
||||||
|
voiceChannelsService,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
channelId: config.channelMapping.voice["for-two"],
|
||||||
|
key: VoiceChannels.enum["for-two"],
|
||||||
|
size: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channelId: config.channelMapping.voice["for-three"],
|
||||||
|
key: VoiceChannels.enum["for-three"],
|
||||||
|
size: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channelId: config.channelMapping.voice["for-four"],
|
||||||
|
key: VoiceChannels.enum["for-four"],
|
||||||
|
size: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channelId: config.channelMapping.voice["for-group"],
|
||||||
|
key: VoiceChannels.enum["for-group"],
|
||||||
|
size: 99,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
@@ -3,7 +3,7 @@ import client from "lib/client";
|
|||||||
import { logger } from "lib/common-logger";
|
import { logger } from "lib/common-logger";
|
||||||
|
|
||||||
export class LogChannelService {
|
export class LogChannelService {
|
||||||
private logChannelId = config.channelMapping.text.bot;
|
private logChannelId = config.channelMapping.text.log;
|
||||||
|
|
||||||
async getLogChannel() {
|
async getLogChannel() {
|
||||||
const logChannel = await client.channels.fetch(this.logChannelId);
|
const logChannel = await client.channels.fetch(this.logChannelId);
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import type { WaterMeService } from "@avocadi/bot-core/features/water-me/water-m
|
|||||||
import {
|
import {
|
||||||
ActionRowBuilder,
|
ActionRowBuilder,
|
||||||
ButtonBuilder,
|
ButtonBuilder,
|
||||||
|
type ButtonInteraction,
|
||||||
ButtonStyle,
|
ButtonStyle,
|
||||||
type CacheType,
|
type CacheType,
|
||||||
|
type ChatInputCommandInteraction,
|
||||||
type Interaction,
|
type Interaction,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { waterMeService } from "./water-me.service";
|
import { waterMeService } from "./water-me.service";
|
||||||
@@ -15,7 +17,9 @@ class WaterMeController {
|
|||||||
this.waterMeService = waterMeService;
|
this.waterMeService = waterMeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
async handleInteraction(
|
||||||
|
interaction: ButtonInteraction | ChatInputCommandInteraction,
|
||||||
|
) {
|
||||||
const result = this.waterMeService.waterMe();
|
const result = this.waterMeService.waterMe();
|
||||||
|
|
||||||
const moreButton = new ButtonBuilder()
|
const moreButton = new ButtonBuilder()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { publishCommands } from "actions/publish-commands";
|
import { publishCommands } from "actions/publish-commands";
|
||||||
|
import "./listeners";
|
||||||
|
|
||||||
// Publish commands when the application starts
|
// Publish commands when the application starts
|
||||||
await publishCommands();
|
await publishCommands();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import "./ready.listener";
|
import "./ready.listener";
|
||||||
import "./guild-member-add.listener";
|
import "./stop.listener";
|
||||||
|
import "./guild-members/guild-members.listener";
|
||||||
import "./voice-state/voice-state.listener";
|
import "./voice-state/voice-state.listener";
|
||||||
import "./message-reaction-add.listener";
|
import "./interactions/interactions.listener";
|
||||||
import "./message-reaction-remove.listener";
|
import "./reactions/reactions.listener";
|
||||||
|
import "./messages/messages.listener";
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { ButtonInteraction } from "discord.js";
|
import type { ButtonInteraction } from "discord.js";
|
||||||
|
import { waterMeController } from "features/water-me/water-me.controller";
|
||||||
|
|
||||||
export const handleButtonInteraction = async (
|
export const handleButtonInteraction = async (
|
||||||
interaction: ButtonInteraction,
|
interaction: ButtonInteraction,
|
||||||
) => {};
|
) => {
|
||||||
|
waterMeController.handleInteraction(interaction);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { ChatInputCommandInteraction } from "discord.js";
|
import type { ChatInputCommandInteraction } from "discord.js";
|
||||||
|
import { waterMeController } from "features/water-me/water-me.controller";
|
||||||
|
|
||||||
export const handleChatInputCommandInteraction = async (
|
export const handleChatInputCommandInteraction = async (
|
||||||
interaction: ChatInputCommandInteraction,
|
interaction: ChatInputCommandInteraction,
|
||||||
) => {};
|
) => {
|
||||||
|
waterMeController.handleInteraction(interaction);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import { config } from "config";
|
||||||
import { messagesService } from "entitites/messages/messages.service";
|
import { messagesService } from "entitites/messages/messages.service";
|
||||||
import client from "lib/client";
|
import client from "lib/client";
|
||||||
|
|
||||||
client.on("messageCreate", async (message) => {
|
client.on("messageCreate", async (message) => {
|
||||||
|
if (
|
||||||
|
message.channel.id !== config.channelMapping.text.log &&
|
||||||
|
message.channel.id !== config.channelMapping.text.bot
|
||||||
|
) {
|
||||||
messagesService.logMessage(message);
|
messagesService.logMessage(message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
import { config } from "config";
|
import { config } from "config";
|
||||||
import { activityService } from "features/activity/activity.service";
|
import { activityService } from "features/activity/activity.service";
|
||||||
|
import { logChannelService } from "features/log-channel/log-channel.service";
|
||||||
import client from "lib/client";
|
import client from "lib/client";
|
||||||
import { logger } from "lib/common-logger";
|
import { logger } from "lib/common-logger";
|
||||||
|
|
||||||
client.once("ready", async () => {
|
client.once("clientReady", async () => {
|
||||||
const channels = client.channels;
|
await logChannelService.sendLogMessage("wieder online!!!");
|
||||||
const logChannel = channels.cache.get(config.channelMapping.text.bot);
|
|
||||||
|
|
||||||
if (logChannel?.isTextBased() && logChannel?.isSendable()) {
|
|
||||||
try {
|
|
||||||
logger.info("bot is online");
|
logger.info("bot is online");
|
||||||
await logChannel.send("wieder online!!!");
|
|
||||||
} catch (error) {
|
await activityService.set("competing");
|
||||||
logger.error("failed to send online message:", error);
|
logger.info("finished ready procedure");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error("log channel is not valid or sendable.");
|
|
||||||
}
|
|
||||||
await activityService.set("playing");
|
|
||||||
logger.info("ready");
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { VoiceState } from "discord.js";
|
||||||
|
import { dynamicVoiceChannelService } from "features/dynamic-voice-channel/dynamic-voice-channel.service";
|
||||||
|
import { logger } from "lib/common-logger";
|
||||||
|
|
||||||
|
export const handleDynamicVoiceChannelCreation = async (
|
||||||
|
oldState: VoiceState,
|
||||||
|
newState: VoiceState,
|
||||||
|
) => {
|
||||||
|
const userJoinedChannel = !oldState.channelId && newState.channelId;
|
||||||
|
const userSwitchedChannel = oldState.channelId !== newState.channelId;
|
||||||
|
|
||||||
|
if (userJoinedChannel || userSwitchedChannel) {
|
||||||
|
const channel = newState.channel;
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
logger.error("Channel not found for VoiceStateUpdate event");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New channel is created if user joined one of the specific channels (e.g. "Join to Create") or switched to one of them
|
||||||
|
const newChannel =
|
||||||
|
await dynamicVoiceChannelService.createDynamicVoiceChannel(channel.id);
|
||||||
|
|
||||||
|
if (newChannel) {
|
||||||
|
await newState.setChannel(newChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { VoiceState } from "discord.js";
|
||||||
|
import { voiceChannelsService } from "entitites/channels/voice/voice-channels.service";
|
||||||
|
import { dynamicVoiceChannelService } from "features/dynamic-voice-channel/dynamic-voice-channel.service";
|
||||||
|
|
||||||
|
export const handleDynamicVoiceChannelDeletion = async (
|
||||||
|
oldState: VoiceState,
|
||||||
|
newState: VoiceState,
|
||||||
|
) => {
|
||||||
|
// Check if user left a channel that was created by the bot and delete it if it's empty
|
||||||
|
if (oldState.channelId && !newState.channelId) {
|
||||||
|
const channelId = oldState.channelId;
|
||||||
|
|
||||||
|
if (dynamicVoiceChannelService.createdChannelIdsSet.has(channelId)) {
|
||||||
|
const channel = await voiceChannelsService.getVoiceChannelById(channelId);
|
||||||
|
|
||||||
|
if (channel.members.size === 0) {
|
||||||
|
await dynamicVoiceChannelService.deleteDynamicVoiceChannel(channelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import { Events } from "discord.js";
|
import { Events } from "discord.js";
|
||||||
import client from "lib/client";
|
import client from "lib/client";
|
||||||
|
import { handleDynamicVoiceChannelCreation } from "./handle-dynamic-voice-channel-creation";
|
||||||
|
import { handleDynamicVoiceChannelDeletion } from "./handle-dynamic-voice-channel-deletion";
|
||||||
|
|
||||||
client.on(Events.VoiceStateUpdate, async (oldState, newState) => {
|
client.on(Events.VoiceStateUpdate, async (oldState, newState) => {
|
||||||
// TODO: handle updates
|
await Promise.allSettled([
|
||||||
|
handleDynamicVoiceChannelCreation(oldState, newState),
|
||||||
|
handleDynamicVoiceChannelDeletion(oldState, newState),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,12 +33,15 @@
|
|||||||
"./db": "./dist/db/index.js",
|
"./db": "./dist/db/index.js",
|
||||||
"./db/schema": "./dist/db/schema.js",
|
"./db/schema": "./dist/db/schema.js",
|
||||||
"./entities/channels/channels.schema": "./dist/entities/channels/channels.schema.js",
|
"./entities/channels/channels.schema": "./dist/entities/channels/channels.schema.js",
|
||||||
|
"./entities/channels/voice/voice-channels.service": "./dist/entities/channels/voice/voice-channels.service.js",
|
||||||
"./entities/commands/commands.entity": "./dist/entities/commands/commands.entity.js",
|
"./entities/commands/commands.entity": "./dist/entities/commands/commands.entity.js",
|
||||||
"./entities/commands/commands.schema": "./dist/entities/commands/commands.schema.js",
|
"./entities/commands/commands.schema": "./dist/entities/commands/commands.schema.js",
|
||||||
"./entities/interactions/interactions.schema": "./dist/entities/interactions/interactions.schema.js",
|
"./entities/interactions/interactions.schema": "./dist/entities/interactions/interactions.schema.js",
|
||||||
"./entities/messages/messages.service": "./dist/entities/messages/messages.service.js",
|
"./entities/messages/messages.service": "./dist/entities/messages/messages.service.js",
|
||||||
"./entities/roles/roles.schema": "./dist/entities/roles/roles.schema.js",
|
"./entities/roles/roles.schema": "./dist/entities/roles/roles.schema.js",
|
||||||
"./entities/roles/roles.service": "./dist/entities/roles/roles.service.js",
|
"./entities/roles/roles.service": "./dist/entities/roles/roles.service.js",
|
||||||
|
"./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/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": "./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/text-based-feature/text-based-feature.schema": "./dist/features/text-based-feature/text-based-feature.schema.js",
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import config from "config";
|
|
||||||
import client from "lib/client";
|
|
||||||
|
|
||||||
export class GreetingService {
|
|
||||||
async greet() {
|
|
||||||
const channels = client.channels;
|
|
||||||
|
|
||||||
const channel = channels.cache.get(config.discord.channelIdBot);
|
|
||||||
|
|
||||||
if (channel?.isTextBased && channel?.isSendable()) {
|
|
||||||
await channel.send({ content: "HALLOOOO" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import config from "config";
|
|
||||||
import client from "lib/client";
|
|
||||||
import { getRandomInt } from "lib/utils";
|
|
||||||
import { } from "./dynamicVChannel.components.ts";
|
|
||||||
import {
|
|
||||||
Client,
|
|
||||||
EmbedBuilder,
|
|
||||||
type Message,
|
|
||||||
type CacheType,
|
|
||||||
type GuildMember,
|
|
||||||
type Interaction,
|
|
||||||
type OmitPartialGroupDMChannel,
|
|
||||||
type VoiceState,
|
|
||||||
type VoiceChannel,
|
|
||||||
type StageChannel,
|
|
||||||
Events,
|
|
||||||
type VoiceBasedChannel,
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
export class DynamicVChannelService {
|
|
||||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
|
||||||
// todo
|
|
||||||
}
|
|
||||||
|
|
||||||
async createVChannel(
|
|
||||||
newState: VoiceState,
|
|
||||||
channel: VoiceBasedChannel
|
|
||||||
): Promise<StageChannel | VoiceChannel> {
|
|
||||||
//console.log("createChannel()");
|
|
||||||
const newVChannel = await channel.clone({
|
|
||||||
name: `${channel.name.substring(2)}; ${newState.member?.displayName}`,
|
|
||||||
position: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newVChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteVChannel(
|
|
||||||
oldState: VoiceState,
|
|
||||||
newState: VoiceState,
|
|
||||||
newChannel: StageChannel | VoiceChannel,
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
channelListeners: Map<any, any>,
|
|
||||||
channelListener: (oldState: VoiceState, newState: VoiceState) => void
|
|
||||||
) {
|
|
||||||
//console.log("deleteChannel()");
|
|
||||||
if (
|
|
||||||
oldState.channelId === newChannel.id ||
|
|
||||||
newState.channelId === newChannel.id
|
|
||||||
) {
|
|
||||||
if (newChannel.members.size === 0) {
|
|
||||||
newChannel.delete().catch(console.error);
|
|
||||||
|
|
||||||
client.removeListener(Events.VoiceStateUpdate, channelListener);
|
|
||||||
channelListeners.delete(newChannel.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return channelListeners;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { ButtonBuilder, ButtonStyle } from "discord.js";
|
|
||||||
|
|
||||||
export const yesButton = new ButtonBuilder()
|
|
||||||
.setCustomId("yesMedication")
|
|
||||||
.setLabel("ja")
|
|
||||||
.setStyle(ButtonStyle.Primary);
|
|
||||||
export const noButton = new ButtonBuilder()
|
|
||||||
.setCustomId("noMedication")
|
|
||||||
.setLabel("noch nicht")
|
|
||||||
.setStyle(ButtonStyle.Secondary);
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
import { CronJob } from "cron";
|
|
||||||
import { getRandomInt } from "lib/utils";
|
|
||||||
import config from "config";
|
|
||||||
import client from "lib/client";
|
|
||||||
import {
|
|
||||||
ActionRowBuilder,
|
|
||||||
ButtonBuilder,
|
|
||||||
type ButtonInteraction,
|
|
||||||
ButtonStyle,
|
|
||||||
type ChatInputCommandInteraction,
|
|
||||||
type ModalSubmitInteraction,
|
|
||||||
type CacheType,
|
|
||||||
type Interaction,
|
|
||||||
} from "discord.js";
|
|
||||||
import { yesButton, noButton } from "./medication.components";
|
|
||||||
import { db } from "db";
|
|
||||||
import { usersTable } from "db/schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
export class MedicationService {
|
|
||||||
medication: string;
|
|
||||||
tookMedication: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.medication = "";
|
|
||||||
this.tookMedication = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async askMedication() {
|
|
||||||
const channels = client.channels;
|
|
||||||
|
|
||||||
const channel = channels.cache.get(config.discord.channelIdBot);
|
|
||||||
|
|
||||||
// funkt noch nicht, beides
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder().addComponents(yesButton);
|
|
||||||
row.addComponents(noButton);
|
|
||||||
|
|
||||||
if (
|
|
||||||
channel?.isTextBased &&
|
|
||||||
channel?.isSendable() &&
|
|
||||||
this.tookMedication === false
|
|
||||||
) {
|
|
||||||
await channel.send({
|
|
||||||
content: "hast du schon deine medis genommen? :)",
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
components: [row as any],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getMedication() {
|
|
||||||
// todo
|
|
||||||
}
|
|
||||||
|
|
||||||
setMedication() {
|
|
||||||
const reply = this.getReply();
|
|
||||||
console.log("medication");
|
|
||||||
|
|
||||||
// this.medication = input von user:in hier rein;
|
|
||||||
return {
|
|
||||||
reply,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getReply() {
|
|
||||||
return "medication reminder";
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleInteraction(interaction: Interaction<CacheType>) {
|
|
||||||
if (interaction.isModalSubmit()) {
|
|
||||||
await this.handleModalSubmit(interaction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isChatInputCommand()) {
|
|
||||||
await this.handleChatInputCommand(interaction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (interaction.isButton()) {
|
|
||||||
await this.handleButton(interaction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleModalSubmit(interaction: ModalSubmitInteraction<CacheType>) {
|
|
||||||
switch (interaction.customId) {
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleButton(interaction: ButtonInteraction<CacheType>) {
|
|
||||||
console.log("button interaction");
|
|
||||||
|
|
||||||
const discordId = Number.parseInt(interaction.user.id);
|
|
||||||
const name = interaction.user.displayName;
|
|
||||||
|
|
||||||
console.log(`userid: ${discordId}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const userId = await this.ensureUserExists(discordId, name);
|
|
||||||
console.log(`userid: ${userId}`);
|
|
||||||
|
|
||||||
const tookMedication = interaction.customId === "yesMedication";
|
|
||||||
|
|
||||||
await interaction.reply({
|
|
||||||
content: tookMedication
|
|
||||||
? "das hast du toll gemacht <3 mach weiter so :3"
|
|
||||||
: "das passiert mal... aber versuch sie heute noch zu nehmen, oki? :)",
|
|
||||||
});
|
|
||||||
await this.logMedication(userId, tookMedication);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("error ensuring user exists:", error);
|
|
||||||
await interaction.reply({
|
|
||||||
content:
|
|
||||||
"es gab einen fehler beim verarbeiten deiner anfrage :( versuch es bitte spaeter nochmal, oki? c:",
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleChatInputCommand(
|
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
|
||||||
) {
|
|
||||||
const result = this.setMedication();
|
|
||||||
|
|
||||||
const row = new ActionRowBuilder().addComponents(yesButton);
|
|
||||||
row.addComponents(noButton);
|
|
||||||
await interaction.reply({
|
|
||||||
content: result.reply,
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
components: [row as any],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**, um die Benutzerdaten in die Datenbank zu schreiben.
|
|
||||||
* @param discordId unique user id
|
|
||||||
* @param name name how the user wants to get called by avocadi
|
|
||||||
* @param tookMedication if user took medication
|
|
||||||
*/
|
|
||||||
async logMedication(id: number, tookMedication: boolean) {
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.update(usersTable)
|
|
||||||
.set({
|
|
||||||
took_medication_today: Number(tookMedication),
|
|
||||||
})
|
|
||||||
.where(eq(usersTable.id, id));
|
|
||||||
|
|
||||||
/* await db.insert(usersTable).values({
|
|
||||||
name: name,
|
|
||||||
discord_id: discordId,
|
|
||||||
took_medication_today: Number(tookMedication),
|
|
||||||
}); */
|
|
||||||
|
|
||||||
console.log(`user with id ${id} saved`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("error while saving in db:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNameByDiscordId(discordId: number): Promise<string | null> {
|
|
||||||
const result = await db
|
|
||||||
.select({
|
|
||||||
name: usersTable.name,
|
|
||||||
})
|
|
||||||
.from(usersTable)
|
|
||||||
.where(eq(usersTable.discord_id, discordId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (result.length > 0) {
|
|
||||||
console.log("user found");
|
|
||||||
return result[0].name;
|
|
||||||
}
|
|
||||||
console.log("name not found");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async findUserIdByDiscordId(discordId: number): Promise<number | null> {
|
|
||||||
try {
|
|
||||||
const result = await db
|
|
||||||
.select({
|
|
||||||
id: usersTable.id,
|
|
||||||
})
|
|
||||||
.from(usersTable)
|
|
||||||
.where(eq(usersTable.discord_id, discordId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (result.length > 0) {
|
|
||||||
console.log(`ID für Discord-ID ${discordId} gefunden: ${result[0].id}`);
|
|
||||||
return result[0].id;
|
|
||||||
}
|
|
||||||
console.log(`no id for discordId ${discordId} found`);
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`error while calling id for discordId ${discordId}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureUserExists(discordId: number, name: string): Promise<number> {
|
|
||||||
try {
|
|
||||||
const userId = await this.findUserIdByDiscordId(discordId);
|
|
||||||
|
|
||||||
if (userId !== null) {
|
|
||||||
console.log(`entry for discordID ${discordId} already exists`);
|
|
||||||
return userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`found no entry for discordID ${discordId}. creating new entry`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await db
|
|
||||||
.insert(usersTable)
|
|
||||||
.values({
|
|
||||||
name: name,
|
|
||||||
discord_id: discordId,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
.returning({
|
|
||||||
id: usersTable.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.length > 0) {
|
|
||||||
const newUserId = result[0].id;
|
|
||||||
console.log(
|
|
||||||
`new user with discordId ${discordId} and name ${name} created. id: ${newUserId}`,
|
|
||||||
);
|
|
||||||
return newUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check again if user is now existing
|
|
||||||
const newUserId = await this.findUserIdByDiscordId(discordId);
|
|
||||||
if (newUserId !== null) {
|
|
||||||
console.log(`user created in parallel. fetched id: ${newUserId}`);
|
|
||||||
return newUserId;
|
|
||||||
}
|
|
||||||
throw new Error(`creating a new user for discordId ${discordId} failed`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("error while creating or calling the user:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { CronJob } from "cron";
|
|
||||||
import { MedicationService } from "actions/medication/medication.service";
|
|
||||||
|
|
||||||
const medicationService = new MedicationService();
|
|
||||||
|
|
||||||
/*new CronJob(
|
|
||||||
"0 0 19 * * *", // cronTime
|
|
||||||
async () => {
|
|
||||||
console.log("askMedication()");
|
|
||||||
await medicationService.askMedication();
|
|
||||||
}, // onTick
|
|
||||||
null, // onComplete
|
|
||||||
true, // start
|
|
||||||
"Europe/Berlin", // timeZone
|
|
||||||
);*/
|
|
||||||
@@ -3,7 +3,7 @@ import z from "zod";
|
|||||||
export const TextChannelOptions = [
|
export const TextChannelOptions = [
|
||||||
"bump",
|
"bump",
|
||||||
"bot",
|
"bot",
|
||||||
"notification",
|
"log",
|
||||||
"testing",
|
"testing",
|
||||||
"news",
|
"news",
|
||||||
"rules",
|
"rules",
|
||||||
|
|||||||
14
core/src/entities/channels/voice/voice-channels.service.ts
Normal file
14
core/src/entities/channels/voice/voice-channels.service.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export interface VoiceChannelsServiceInterface<
|
||||||
|
C extends { name: string; id: string },
|
||||||
|
> {
|
||||||
|
getVoiceChannelById(channelId: string): Promise<C | null>;
|
||||||
|
|
||||||
|
createVoiceChannel(name: string): Promise<C>;
|
||||||
|
|
||||||
|
cloneVoiceChannel(
|
||||||
|
channel: C,
|
||||||
|
options?: { name?: string; position?: number },
|
||||||
|
): Promise<C>;
|
||||||
|
|
||||||
|
deleteVoiceChannel(channelId: string): Promise<void>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { VoiceChannels } from "entities/channels/channels.schema";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const DynamicVoiceChannelKeyOptions = [
|
||||||
|
VoiceChannels.enum["for-two"],
|
||||||
|
VoiceChannels.enum["for-three"],
|
||||||
|
VoiceChannels.enum["for-four"],
|
||||||
|
VoiceChannels.enum["for-group"],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const DynamicVoiceChannelKeys = z.enum(DynamicVoiceChannelKeyOptions);
|
||||||
|
|
||||||
|
export const DynamicVoiceChannel = z.object({
|
||||||
|
channelId: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
key: DynamicVoiceChannelKeys,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DynamicVoiceChannels = z.array(DynamicVoiceChannel);
|
||||||
|
export const DynamicVoiceChannelsMap = z.map(z.string(), DynamicVoiceChannel);
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { VoiceChannelsServiceInterface } from "entities/channels/voice/voice-channels.service";
|
||||||
|
import type z from "zod";
|
||||||
|
import type {
|
||||||
|
DynamicVoiceChannels,
|
||||||
|
DynamicVoiceChannelsMap,
|
||||||
|
} from "./dynamic-voice-channels.schema";
|
||||||
|
|
||||||
|
export class DynamicVoiceChannelsService<
|
||||||
|
C extends { name: string; id: string },
|
||||||
|
> {
|
||||||
|
private voiceChannelsService: VoiceChannelsServiceInterface<C>;
|
||||||
|
dynamicVoiceChannels: z.output<typeof DynamicVoiceChannels>;
|
||||||
|
validChannelIds = new Set<string>();
|
||||||
|
dynamicVoiceChannelsMap: z.output<typeof DynamicVoiceChannelsMap>;
|
||||||
|
createdChannelIdsSet = new Set<string>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
voiceChannelsService: VoiceChannelsServiceInterface<C>,
|
||||||
|
dynamicVoiceChannels: z.output<typeof DynamicVoiceChannels>,
|
||||||
|
) {
|
||||||
|
this.voiceChannelsService = voiceChannelsService;
|
||||||
|
this.dynamicVoiceChannels = dynamicVoiceChannels;
|
||||||
|
|
||||||
|
this.dynamicVoiceChannelsMap = new Map(
|
||||||
|
dynamicVoiceChannels.map((channel) => [channel.channelId, channel]),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.validChannelIds = new Set(
|
||||||
|
dynamicVoiceChannels.map((channel) => channel.channelId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDynamicVoiceChannel(id: string): Promise<C | void> {
|
||||||
|
if (this.validChannelIds.has(id)) {
|
||||||
|
// Channel is one of the dynamic voice channels, create a new one based on it
|
||||||
|
|
||||||
|
const channel = await this.voiceChannelsService.getVoiceChannelById(id);
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
throw new Error(`Channel with id ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChannel = await this.voiceChannelsService.cloneVoiceChannel(
|
||||||
|
channel,
|
||||||
|
{
|
||||||
|
name: channel.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.createdChannelIdsSet.add(newChannel.id);
|
||||||
|
|
||||||
|
return newChannel;
|
||||||
|
} else {
|
||||||
|
// Channel is not a dynamic voice channel, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDynamicVoiceChannel(channelId: string): Promise<void> {
|
||||||
|
this.createdChannelIdsSet.delete(channelId);
|
||||||
|
return this.voiceChannelsService.deleteVoiceChannel(channelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,5 @@ export type TextBasedFeatureHandleMessageSend = (
|
|||||||
|
|
||||||
export type TextBasedFeatureInput = {
|
export type TextBasedFeatureInput = {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
handleMessageSend: TextBasedFeatureHandleMessageSend;
|
messagesService: TextBasedFeatureHandleMessageSend;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user