diff --git a/src/actions/dm/dm.service.ts b/src/actions/dm/dm.service.ts index 6ea6c22..1994769 100644 --- a/src/actions/dm/dm.service.ts +++ b/src/actions/dm/dm.service.ts @@ -17,20 +17,31 @@ export class DmService { // todo } + async reminderPrivate(member: GuildMember) { + console.log("reminder"); + try { + const content = `hey, kleine erinnerung :)\nbitte stelle dich auf <#${config.discord.channelIdIntroduction}> vor, damit du alle kanaele ansehen und nutzen kannst! <3` + await client.users.send(member, content); + } catch (error) { + const channels = client.channels; + const channel = channels.cache.get(config.discord.channelIdNotification); + if (channel?.isTextBased() && channel?.isSendable()) { + await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`); + } + console.error("error while sending a welcome msg:", error); + } + } + async welcomePrivate(member: GuildMember) { console.log("welcome private"); try { await client.users.send(member, dmWelcomeContent); } catch (error) { - console.error("error while sending a welcome msg:", error); - } - } - - async welcomePrivateTest() { - console.log("welcomePrivateTest()"); - try { - await client.users.send(config.discord.myId, dmWelcomeContent); - } catch (error) { + const channels = client.channels; + const channel = channels.cache.get(config.discord.channelIdNotification); + if (channel?.isTextBased() && channel?.isSendable()) { + await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`); + } console.error("error while sending a welcome msg:", error); } } @@ -66,6 +77,11 @@ export class DmService { try { await client.users.send(member, dmAcceptedContent); } catch (error) { + const channels = client.channels; + const channel = channels.cache.get(config.discord.channelIdNotification); + if (channel?.isTextBased() && channel?.isSendable()) { + await channel.send(`konnte keine private nachricht an <@${member.user.id}> senden`); + } console.error("error while sending a accept msg:", error); } } diff --git a/src/actions/dynamicChannel/dynamicChannel.service.ts b/src/actions/dynamicChannel/dynamicChannel.service.ts index 0912250..3ffb3dd 100644 --- a/src/actions/dynamicChannel/dynamicChannel.service.ts +++ b/src/actions/dynamicChannel/dynamicChannel.service.ts @@ -10,9 +10,9 @@ import { type GuildMember, type Interaction, type OmitPartialGroupDMChannel, - VoiceState, - VoiceChannel, - StageChannel, + type VoiceState, + type VoiceChannel, + type StageChannel, Events, type VoiceBasedChannel, } from "discord.js"; @@ -22,26 +22,36 @@ export class DynamicChannelService { // todo } - async createChannel(oldState: VoiceState, newState: VoiceState, channel: VoiceBasedChannel): Promise { - console.log("createChannel()"); - + async createChannel( + oldState: VoiceState, + newState: VoiceState, + channel: VoiceBasedChannel, + ): Promise { + //console.log("createChannel()"); const newChannel = await channel.clone({ - name: channel.name + " " + newState.member?.displayName, - position: channel.position + name: `${channel.name.substring(2)}; ${newState.member?.displayName}`, + position: 100, }); return newChannel; } - async deleteChannel(oldState: VoiceState, newState: VoiceState, newChannel: StageChannel | VoiceChannel, channelListeners: Map, channelListener: (oldState: VoiceState, newState: VoiceState) => void) { - - console.log("deleteChannel()"); - - if (oldState.channelId === newChannel.id || newState.channelId === newChannel.id) { + async deleteChannel( + oldState: VoiceState, + newState: VoiceState, + newChannel: StageChannel | VoiceChannel, + // biome-ignore lint/suspicious/noExplicitAny: + channelListeners: Map, + 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); + newChannel.delete().catch(console.error); client.removeListener(Events.VoiceStateUpdate, channelListener); channelListeners.delete(newChannel.id); diff --git a/src/actions/greeting/greeting.service.ts b/src/actions/greeting/greeting.service.ts index 34fd885..4308532 100644 --- a/src/actions/greeting/greeting.service.ts +++ b/src/actions/greeting/greeting.service.ts @@ -13,6 +13,8 @@ import { type CacheType, type GuildMember, type Interaction, + GuildMemberRoleManager, + type APIInteractionGuildMember, } from "discord.js"; import { DmService } from "actions/dm/dm.service.ts"; import { Commands, type CommandsType } from "commands/index.ts"; @@ -35,7 +37,6 @@ export class GreetingService { } async handleChatInputCommand(interaction: ChatInputCommandInteraction) { - console.log("accept"); const commandName = interaction.commandName as CommandsType; switch (commandName) { case Commands.Enum.accept: @@ -44,6 +45,9 @@ export class GreetingService { case Commands.Enum.welcome: await this.welcomeCommand(interaction); return; + case Commands.Enum.reminder: + await this.reminderCommand(interaction); + return; default: break; } @@ -78,10 +82,9 @@ export class GreetingService { //console.log(input); // permission check - const userIdCommand = interaction.user.id; - if (userIdCommand !== config.discord.myId) { + if (await this.checkPermission(interaction.member) !== true) { await interaction.reply({ - content: "you have no permission for that command", + content: "du hast keine rechte fuer diesen befehl", ephemeral: true, }); return; @@ -100,14 +103,19 @@ export class GreetingService { return; } - const username = (await guild.members.fetch(userId)).user.username; - console.log(username); - // get member from id const member = await guild.members.fetch(userId); + const username = member.user.username; + console.log(username); - const role = config.discord.roleStudy; + if (await this.checkRole(member) === true) { + await interaction.reply({ + content: `${member.user.username} hat die rolle *lernende:r* schon!`, + ephemeral: true, + }); + return; + } - await member.roles.add(role); + await member.roles.add(config.discord.roleStudy); await interaction.reply({ content: `die rolle *lernende:r* wurde erfolgreich an ${member.user.username} vergeben`, @@ -180,25 +188,77 @@ export class GreetingService { return greetContent[getRandomInt(0, greetContent.length - 1)]; } - async welcomeCommand( - interaction: ChatInputCommandInteraction - ) { - console.log("accept user"); + async reminderCommand( + interaction: ChatInputCommandInteraction) { + console.log("remind user"); // get the string option const input = interaction.options.getString("input") || ""; // return the value //console.log(input); - // permission check - const userIdCommand = interaction.user.id; - if (userIdCommand !== config.discord.myId) { + try { + // permission check + if (await this.checkPermission(interaction.member) !== true) { + await interaction.reply({ + content: "du hast keine rechte fuer diesen befehl", + ephemeral: true, + }); + return; + } + + // get user id from mentioning + const userId = input.replace(/[<@!>]/g, ""); + console.log(userId.toString()); + const guild = interaction.guild; + if (!guild) { + await interaction.reply({ + content: "command can only be used on a server", + ephemeral: true, + }); + return; + } + + const member = await guild.members.fetch(userId); + const username = member.user.username; + console.log(username); + + if (await this.checkRole(member) === true) { + await interaction.reply({ + content: `${member.user.username} hat die rolle *lernende:r* schon!`, + ephemeral: true, + }); + return; + } + + await this.dmService.reminderPrivate(member); + //await member.roles.add(config.discord.roleStudy); + await interaction.reply({ - content: "you have no permission for that command", + content: `${member.user.username} wurde erfolgrich remindet`, + ephemeral: true, + }); + + this.dmService.acceptDm(member); + } catch (error) { + console.error("error while reminding:", error); + await interaction.reply({ + content: + "Es gab einen Fehler beim reminden. Stelle sicher, dass du einen gültigen User erwähnt hast.", ephemeral: true, }); - return; } + } + + async welcomeCommand( + interaction: ChatInputCommandInteraction + ) { + console.log("welcome user"); + + // get the string option + const input = interaction.options.getString("input") || ""; + // return the value + //console.log(input); try { // get user id from mentioning @@ -213,14 +273,27 @@ export class GreetingService { return; } - const username = (await guild.members.fetch(userId)).user.username; - console.log(username); + if (await this.checkPermission(interaction.member) !== true) { + await interaction.reply({ + content: "du hast keine rechte fuer diesen befehl", + ephemeral: true, + }); + return; + } + // get member from id const member = await guild.members.fetch(userId); - + const username = member.user.username; + console.log(username); const welcomeContent = getWelcomeContent(member); + if (await this.checkRole(member) === true) { + await interaction.reply({ + content: `${member.user.username} wurde schon begruesst!`, + ephemeral: true, + }); + } try { const channels = client.channels; @@ -240,6 +313,7 @@ export class GreetingService { content: `erfolgreich welcome command: ${member.user.username}`, ephemeral: true, }); + } catch (error) { console.error("fehler bei welcome command", error); await interaction.reply({ @@ -249,4 +323,26 @@ export class GreetingService { }); } } + + async checkPermission(member: GuildMember | APIInteractionGuildMember | null) { + let permission = false; + if (member?.roles instanceof GuildMemberRoleManager) { + if (member.roles.cache.has(config.discord.roleAdmin) || member.roles.cache.has(config.discord.roleMod)) { + console.log("user has permission"); + permission = true; + } + } + return permission; + } + + async checkRole(member: GuildMember) { + let hasRole = false; + if (member?.roles instanceof GuildMemberRoleManager) { + if (member.roles.cache.has(config.discord.roleAdmin) || member.roles.cache.has(config.discord.roleMod)) { + console.log("user has permission"); + hasRole = true; + } + } + return hasRole; + } } diff --git a/src/commands/index.ts b/src/commands/index.ts index 8aa11df..d5f5662 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,7 +1,7 @@ import { SlashCommandBuilder, userMention } from "discord.js"; import { z } from "zod"; -export const Commands = z.enum(["giessen", "medikamente", "hilfe", "accept", "welcome", "embed", "message"]); +export const Commands = z.enum(["giessen", "medikamente", "hilfe", "accept", "welcome", "embed", "message", "reminder"]); export const CommandsMeta: Record, { description: string }> = { giessen: { @@ -24,6 +24,9 @@ export const CommandsMeta: Record, { description: stri }, message: { description: "admin use only" + }, + reminder: { + description: "admin use only" } } @@ -76,6 +79,13 @@ export default function getCommands() { option.setName('input') .setDescription('input for bot') .setRequired(true)), + new SlashCommandBuilder() + .setName(Commands.Enum.reminder) + .setDescription(CommandsMeta.reminder.description) + .addStringOption(option => + option.setName('input') + .setDescription('input for bot') + .setRequired(true)), ].map((command) => command.toJSON()); diff --git a/src/config.ts b/src/config.ts index 33f986d..4d5055b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,6 +22,8 @@ export default { vchannelIdForGroup: process.env.DISCORD_VCHANNEL_ID_FOR_GROUP || "", // roles roleStudy: process.env.PEOPLE || "", + roleMod: process.env.MOD || "", + roleAdmin: process.env.ADMIN || "", // other applicationId: process.env.DISCORD_APPLICATION_ID || "", diff --git a/src/controllers/discord.controller.ts b/src/controllers/discord.controller.ts index e5a9166..8517deb 100644 --- a/src/controllers/discord.controller.ts +++ b/src/controllers/discord.controller.ts @@ -4,7 +4,7 @@ import { Client, Events, IntentsBitField, - VoiceState, + type VoiceState, type ButtonInteraction, type CacheType, type ChatInputCommandInteraction, @@ -48,7 +48,7 @@ export default class DiscordController extends EventEmitter { this.customMessageService = new CustomMessageService(); this.dynamicChannelService = new DynamicChannelService(); - var channelListeners = new Map(); + let channelListeners = new Map(); // log when running client.once("ready", async () => { await this.setActivity(); @@ -70,32 +70,48 @@ export default class DiscordController extends EventEmitter { await this.greetingService.welcome(member); }); - 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 { - // create new channel with same settings - /*const newChannel = await channel.clone({ + 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 { + // create new channel with same settings + /*const newChannel = await channel.clone({ name: channel.name + "; " + newState.member?.displayName, position: channel.position });*/ + const newChannel = await this.dynamicChannelService.createChannel( + oldState, + newState, + channel, + ); - const newChannel = await this.dynamicChannelService.createChannel(oldState, newState, channel); + // move user in new channel + await newState.setChannel(newChannel); - // move user in new channel - await newState.setChannel(newChannel); - - // create specific listener for channel - const channelListener = async (oldState: VoiceState, newState: VoiceState) => { - /*if (oldState.channelId === newChannel.id || newState.channelId === newChannel.id) { + // create specific listener for channel + const channelListener = async ( + oldState: VoiceState, + newState: VoiceState, + ) => { + /*if (oldState.channelId === newChannel.id || newState.channelId === newChannel.id) { // check if channel empty if (newChannel.members.size === 0) { newChannel.delete() @@ -105,22 +121,27 @@ export default class DiscordController extends EventEmitter { channelListeners.delete(newChannel.id); } }*/ - channelListeners = await this.dynamicChannelService.deleteChannel(oldState, newState, newChannel, channelListeners, channelListener); + channelListeners = + await this.dynamicChannelService.deleteChannel( + oldState, + newState, + newChannel, + channelListeners, + channelListener, + ); + }; + // save listener in map + channelListeners.set(newChannel.id, 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); + // add listener + client.on(Events.VoiceStateUpdate, channelListener); + } catch (error) { + console.error("error while duplicating channel", error); + } } } - } - }); - + }, + ); } async setActivity() { @@ -191,6 +212,9 @@ export default class DiscordController extends EventEmitter { case Commands.Enum.message: await this.customMessageService.handleInteraction(interaction); return; + case Commands.Enum.reminder: + await this.greetingService.handleInteraction(interaction); + return; default: break; }