diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0b8736e..08c5e8b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,4 +18,7 @@ model Guild { MembershipScreening_Enabled Boolean @default(false) MembershipScreening_ApprovalChannel String? MembershipScreening_GivenRole String? + + ServiceAnnouncement_Enabled Boolean @default(false) + ServiceAnnouncement_Channel String? } diff --git a/src/discord/Settings.ts b/src/discord/Settings.ts index 791ab39..024378e 100644 --- a/src/discord/Settings.ts +++ b/src/discord/Settings.ts @@ -1,4 +1,4 @@ -import { Message, Interaction, CommandInteraction, Permissions } from "discord.js"; +import { Message, Interaction, CommandInteraction, Permissions, ThreadChannel, GuildChannel } from "discord.js"; import { makeInfoEmbed, makeErrorEmbed, makeSuccessEmbed, makeProcessingEmbed, sendMessageOrInteractionResponse, sendReply } from "../utils/DiscordMessage"; import DiscordProvider from "../providers/Discord"; import {registerAllGuildsCommands, unregisterAllGuildsCommands} from "../utils/DiscordInteraction"; @@ -10,11 +10,11 @@ const EMBEDS = { SETTINGS_INFO: (data: Message | Interaction) => { return makeInfoEmbed({ title: 'Settings', - description: `Change how ${DiscordProvider.client.user?.username} behave on ${data.guild?.name}`, + description: `Change how ${DiscordProvider.client.user?.username} behave on **${data.guild?.name}**`, fields: [ { name: 'Available arguments', - value: '``setPrefix``' + value: '``setPrefix`` ``setEnableServiceAnnouncement`` ``setServiceAnnouncementChannel``' } ], user: (data instanceof Interaction) ? data.user : data.author @@ -39,6 +39,25 @@ const EMBEDS = { user: (data instanceof Interaction) ? data.user : data.author }); }, + SERVICE_ANNOUNCEMENT_INVALID_STATUS: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: 'Invalid status, Use ``true`` or ``false``', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_SERVICE_ANNOUNCEMENT_STATUS_PROVIDED: async (data: Message | Interaction) => { + let GuildCache = await Cache.getGuild(data.guildId!); + + // TODO: Better error handling + if(typeof GuildCache === 'undefined') + throw new Error('Guild not found'); + + return makeInfoEmbed ({ + title: 'Service Announcement', + description: `Service Announcement is ${GuildCache.ServiceAnnouncement_Enabled ? "**enabled**" : "**disabled**"} on this server`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, PREFIX_TOO_LONG: (data: Message | Interaction) => { return makeErrorEmbed ({ title: 'Prefix too long', @@ -55,11 +74,69 @@ const EMBEDS = { }, PREFIX_UPDATED: (data: Message | Interaction, newPrefix: string) => { return makeSuccessEmbed ({ - title: 'Prefix updated', + title: 'Prefix Updated', description: `From now onwards, I shall be called by using \`\`${newPrefix}\`\``, user: (data instanceof Interaction) ? data.user : data.author }); }, + SERVICE_ANNOUNCEMENT_STATUS_UPDATED: (data: Message | Interaction, newStatus: boolean) => { + return makeSuccessEmbed ({ + title: `Service Announcement is now ${newStatus ? "enabled" : "disabled"}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + SERVICE_ANNOUNCEMENT_STATUS_ALREADY_SET: (data: Message | Interaction, status: boolean) => { + return makeInfoEmbed ({ + title: `Service Announcement is already ${status ? "enabled" : "disabled"}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + SERVICE_ANNOUNCEMENT_NO_PARAMETER: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: 'Missing parameter', + description: `You must define service announcement channel to enable this feature`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_CHANNEL_MENTIONED: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: 'No channel mentioned', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_CHANNEL_FOUND: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: 'Cannot find that channel', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + INVALID_CHANNEL: (data: Message | Interaction, channel: GuildChannel) => { + return makeErrorEmbed ({ + title: 'Invalid channel type, only TextChannel is supported', + description: '``' + channel.name +'``' + ' is not a text channel', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + INVALID_CHANNEL_THREAD: (data: Message | Interaction, channel: GuildChannel | ThreadChannel) => { + return makeErrorEmbed ({ + title: 'Thread channel is not supported', + description: '``' + channel.name +'``' + ' is a thread channel. Please use a regular text channel', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + BOT_NO_PERMISSION: (data: Message | Interaction, channel: GuildChannel) => { + return makeErrorEmbed ({ + title: `I don't have permission`, + description: 'I cannot access/send message in ' + '``' + channel.name +'``', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + SERVICE_ANNOUNCEMENT_CONFIGURED_CHANNEL: (data: Message | Interaction, channel: GuildChannel) => { + return makeSuccessEmbed ({ + title: 'Configured Service Announcement Channel', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, } export default class Settings { @@ -127,13 +204,110 @@ export default class Settings { return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.PREFIX_IS_MENTION(data)] }); let placeholder = await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.PROCESSING(data)] }); + await Prisma.client.guild.update({ where: { id: data.guildId! }, data: { prefix: newPrefix }}); Cache.updateGuildCache(data.guildId!); if(isMessage) - return (placeholder as Message).edit({ embeds: [EMBEDS.PREFIX_UPDATED(data, newPrefix)] }); + return await (placeholder as Message).edit({ embeds: [EMBEDS.PREFIX_UPDATED(data, newPrefix)] }); else if(isSlashCommand) return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.PREFIX_UPDATED(data, newPrefix)]}, true); + }, + setEnableServiceAnnouncement: async(data: Message | Interaction) => { + + if(data === null || !data.guildId || data.member === null || data.guild === null) return; + + if(data instanceof Message) { + if (!data.member.permissions.has([Permissions.FLAGS.ADMINISTRATOR])) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_PERMISSION(data)] }); + } + else if(isSlashCommand) { + if(!data.guild.members.cache.get(data.user.id)?.permissions.has([Permissions.FLAGS.ADMINISTRATOR])) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_PERMISSION(data)] }); + } + + + let newStatus: string | undefined; + if(data instanceof Message) { + let _name: string; + if(typeof args[1] === 'undefined') { + return await sendMessageOrInteractionResponse(data, { embeds: [await EMBEDS.NO_SERVICE_ANNOUNCEMENT_STATUS_PROVIDED(data)] }); + } + + let __name = args; + __name.shift(); + _name = __name.join(" "); + newStatus = _name; + } + else if(data instanceof CommandInteraction) + newStatus = data.options.getString('status')!; + + if(!newStatus) + return await sendMessageOrInteractionResponse(data, { embeds: [await EMBEDS.NO_SERVICE_ANNOUNCEMENT_STATUS_PROVIDED(data)] }); + + if(!["true", "false", "yes", "no", "y", "n", "enable", "disable"].includes(newStatus.toLowerCase())) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_INVALID_STATUS(data)] }); + + await Cache.updateGuildCache(data.guildId!); + let GuildCache = await Cache.getGuild(data.guildId!); + + const newStatusBool = ["true", "yes", "y", "enable"].includes(newStatus.toLowerCase()) ? true : false; + + if(GuildCache?.ServiceAnnouncement_Enabled === newStatusBool) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_STATUS_ALREADY_SET(data, newStatusBool)] }); + + if(GuildCache?.ServiceAnnouncement_Channel === null) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_NO_PARAMETER(data)] }); + + let placeholder = await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.PROCESSING(data)] }); + + await Prisma.client.guild.update( + { where: { id: data.guildId! }, + data: { + ServiceAnnouncement_Enabled: newStatusBool + } + }); + Cache.updateGuildCache(data.guildId!); + + if(isMessage) + return await (placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_STATUS_UPDATED(data, newStatusBool)]}); + else if(isSlashCommand) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_STATUS_UPDATED(data, newStatusBool)]}, true); + }, + setServiceAnnouncementChannel: async(data: Message | CommandInteraction) => { + + let channel; + if(data instanceof Message) { + if(typeof args[1] === 'undefined') + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_CHANNEL_MENTIONED(data)] }); + channel = data.mentions.channels.first(); + } + else if(data instanceof CommandInteraction) + channel = data.options.getChannel('channel'); + + if(!channel) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_CHANNEL_FOUND(data)] }); + + let TargetChannel = data.guild!.channels.cache.get(channel.id); + if(typeof TargetChannel === 'undefined') return; + + if(TargetChannel instanceof ThreadChannel || TargetChannel.isThread()) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.INVALID_CHANNEL_THREAD(data, TargetChannel)] }); + + if(!TargetChannel.isText()) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.INVALID_CHANNEL(data, TargetChannel)] }); + + if(!(data!.guild!.me?.permissionsIn(TargetChannel).has([Permissions.FLAGS.SEND_MESSAGES, Permissions.FLAGS.VIEW_CHANNEL]))) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.BOT_NO_PERMISSION(data, TargetChannel)] }); + + let placeholder = await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.PROCESSING(data)] }); + + await Prisma.client.guild.update({ where: {id: data.guildId! }, data: { ServiceAnnouncement_Channel: channel.id }}); + + if(isMessage) + return await (placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_CONFIGURED_CHANNEL(data, TargetChannel)]}); + else if(isSlashCommand) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_CONFIGURED_CHANNEL(data, TargetChannel)]}, true); } } @@ -157,6 +331,10 @@ export default class Settings { return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SETTINGS_INFO(data)] }); case "setprefix": return await funct.setPrefix(data); + case "setenableserviceannouncement": + return await funct.setEnableServiceAnnouncement(data); + case "setserviceannouncementchannel": + return await funct.setServiceAnnouncementChannel(data); } } diff --git a/src/discord/developer/ServiceAnnouncement.ts b/src/discord/developer/ServiceAnnouncement.ts new file mode 100644 index 0000000..91d95b1 --- /dev/null +++ b/src/discord/developer/ServiceAnnouncement.ts @@ -0,0 +1,298 @@ +import { Message, MessageEmbed, Interaction, CommandInteraction, TextChannel } from "discord.js"; +import { makeInfoEmbed, makeErrorEmbed, makeSuccessEmbed, makeProcessingEmbed, makeWarningEmbed, sendMessageOrInteractionResponse, sendMessage } from "../../utils/DiscordMessage"; +import DiscordProvider from "../../providers/Discord"; +import { registerAllGuildsCommands, unregisterAllGuildsCommands } from "../../utils/DiscordInteraction"; +import Prisma from "../../providers/Prisma"; +import Users from "../../services/Users"; + +import {Promise, reject} from "bluebird"; +import fs from "fs"; +import path from "path"; +import Logger from "../../libs/Logger"; +import { Guild } from "@prisma/client"; + +const EMBEDS = { + ANNOUNCEMENT_INFO: (data: Message | Interaction) => { + return makeInfoEmbed({ + title: 'Service Announcement', + description: `This module contains management tool for announcement feed\n The announcement message is located in \`\`configs/ServiceAnnouncement.json\`\``, + fields: [ + { + name: 'Available arguments', + value: '``reload`` ``previewNews`` ``sendNews`` ``previewMaintenance`` ``sendMaintenance`` ``previewMessage`` ``sendMessage`` ``previewAlert`` ``sendAlert``' + } + ], + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NOT_DEVELOPER: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: 'Developer only', + description: `This command is restricted to the developers only`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + MAKE_PAYLOAD: (data: Message | Interaction, payload: any) => { + const user = DiscordProvider.client.user; + payload.footer = { + text: `${user?.username}`, + iconURL: user?.avatarURL() || '' + } + + if (!payload.timestamp) + payload.timestamp = new Date(); + + if (payload.thumbnail?.url === 'bot_avatar') + payload.thumbnail.url = user?.avatarURL() || ''; + + if (payload.image?.url === 'bot_avatar') + payload.image.url = user?.avatarURL() || ''; + + if (payload.description) + payload.description = payload.description.replaceAll('{bot_username}', user?.username) + + return new MessageEmbed(payload) + }, + RELOADED: (data: Message | Interaction) => { + return makeSuccessEmbed({ + title: 'Service Announcement Configuration Reloaded', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + RELOAD_ERROR: (data: Message | Interaction, error: string) => { + return makeErrorEmbed({ + title: 'Unable to reload Service Announcement Configuration', + description: `\`\`\`${error}\`\`\``, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + SENDING_SERVICE_ANNOUNCEMENT: (data: Message | Interaction) => { + return makeProcessingEmbed({ + title: 'Service Announcement', + description: `Broadcasting Service Announcement`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + SERVICE_ANNOUNCEMENT_SENT: (data: Message | Interaction) => { + return makeSuccessEmbed({ + title: 'Service Announcement', + description: `Broadcasted Service Announcement`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS: (data: Message | Interaction) => { + return makeWarningEmbed({ + title: 'Service Announcement', + description: `Broadcasted Service Announcement with errors, check console for more info`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, +} + +let Announcements = { + News: { + color: "#FAEDF0", + title: '📰 Newsletter', + description: 'Some description here', + thumbnail: { + url: 'bot_avatar', + } + }, + Maintenance: { + color: "#383b80", + title: '🔧 Maintenance', + description: 'The bot is going offline for maintenance.', + thumbnail: { + url: 'bot_avatar', + } + }, + Message: { + color: "#A1DE93", + title: '✉️ Message', + description: 'Some description here', + thumbnail: { + url: 'bot_avatar', + } + }, + Alert: { + color: "#EC255A", + title: '🚨 Alert', + description: 'Some description here', + thumbnail: { + url: 'bot_avatar', + } + } + +} + +if (fs.existsSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'))) { + try { + const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json')); + const jsonData = JSON.parse(rawData.toString()); + Announcements = jsonData; + } catch (err) { + Logger.error("Unable to load custom ServiceAnnouncement config: " + err); + } +} else { + fs.writeFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'), JSON.stringify(Announcements, null, 4), 'utf8'); +} + +export default class InteractionManager { + async onCommand(command: string, args: any, message: Message) { + if (command.toLowerCase() !== 'serviceannouncement') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if (interaction.isCommand()) { + if (typeof interaction.commandName === 'undefined') return; + if ((interaction.commandName).toLowerCase() !== 'serviceannouncement') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if (!isSlashCommand && !isMessage) return; + + if (!Users.isDeveloper(data.member?.user.id!)) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NOT_DEVELOPER(data)] }); + + const funct = { + reload: async (data: Message | Interaction) => { + + if (fs.existsSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'))) { + try { + const rawData = fs.readFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json')); + const jsonData = JSON.parse(rawData.toString()); + Announcements = jsonData; + } catch (err: any) { + Logger.error("Unable to load custom ServiceAnnouncement config: " + err); + return await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.RELOAD_ERROR(data, err)] }); + } + } else { + fs.writeFileSync(path.join(process.cwd(), 'configs/ServiceAnnouncement.json'), JSON.stringify(Announcements, null, 4), 'utf8'); + } + + return await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.RELOADED(data)] }); + }, + previewNews: async (data: Message | Interaction) => { + return await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.MAKE_PAYLOAD(data, Announcements.News)] }); + }, + previewMaintenance: async (data: Message | Interaction) => { + return await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.MAKE_PAYLOAD(data, Announcements.Maintenance)] }); + }, + previewMessage: async (data: Message | Interaction) => { + return await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.MAKE_PAYLOAD(data, Announcements.Message)] }); + }, + previewAlert: async (data: Message | Interaction) => { + return await sendMessage(data.channel!, undefined, { embeds: [EMBEDS.MAKE_PAYLOAD(data, Announcements.Alert)] }); + }, + publishServiceAnnouncement: async (data: Message | Interaction, embed: any) => { + const Guilds = await Prisma.client.guild.findMany({}); + const toSend = new Map(); + + let withError = false; + + for (const Guild of Guilds) { + if (!Guild.ServiceAnnouncement_Enabled) continue; + if (Guild.ServiceAnnouncement_Channel === null) continue; + + let channel = undefined; + try { + channel = await DiscordProvider.client.channels.fetch(Guild.ServiceAnnouncement_Channel) as TextChannel; + } catch (err) { + // TODO: Handle channel not found error + withError = true; + const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id); + Logger.warn(`Unable to find channel for Service Announcement to ${guildObject?.name} (${Guild.id}) (Channel ID: ${Guild.ServiceAnnouncement_Channel})`); + continue; + } + + if (!channel) continue; + toSend.set(Guild, channel); + } + + let placeholder = await sendMessage(data.channel!, undefined, { embeds:[EMBEDS.SENDING_SERVICE_ANNOUNCEMENT(data)] }); + + await Promise.map(toSend, element => { + return new Promise(async (resolve, reject) => { + let Guild: Guild = element[0]; + let Channel: TextChannel = element[1]; + try { + const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id); + if(!guildObject) + throw new Error('Guild not found'); + Logger.info(`Sending Service Announcement to ${guildObject?.name} (${guildObject.id}) #${Channel.name} (${Channel.id})`); + await sendMessage(Channel, undefined, { embeds:[EMBEDS.MAKE_PAYLOAD(data, embed)] }) + } catch (err) { + withError = true; + const guildObject = DiscordProvider.client.guilds.cache.get(Guild.id); + if(guildObject) + Logger.info(`Unable to send Service Announcement to ${guildObject?.name} (${guildObject.id}) #${Channel.name} (${Channel.id})`); + + reject(err); + } + + await new Promise(resolve => setTimeout(resolve, 1000)); + resolve(); + }); + }, { concurrency: 2 }); + + if(withError) { + if(isMessage) + (placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS(data)] }); + else if(isSlashCommand) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT_WITH_ERRORS(data)] }, true); + } + else { + if(isMessage) + (placeholder as Message).edit({ embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT(data)] }); + else if(isSlashCommand) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.SERVICE_ANNOUNCEMENT_SENT(data)] }, true); + } + } + } + + let query; + + if (isMessage) { + if (data === null || !data.guildId || data.member === null || data.guild === null) return; + + if (args.length === 0) { + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.ANNOUNCEMENT_INFO(data)] }); + } + query = args[0].toLowerCase(); + + } + else if (isSlashCommand) { + query = args.getSubcommand(); + } + + switch (query) { + case "info": + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.ANNOUNCEMENT_INFO(data)] }); + case "reload": + return await funct.reload(data); + case "previewnews": + return await funct.previewNews(data); + case "previewmaintenance": + return await funct.previewMaintenance(data); + case "previewmessage": + return await funct.previewMessage(data); + case "previewalert": + return await funct.previewAlert(data); + case "sendnews": + return await funct.publishServiceAnnouncement(data, Announcements.News); + case "sendmaintenance": + return await funct.publishServiceAnnouncement(data, Announcements.Maintenance); + case "sendmessage": + return await funct.publishServiceAnnouncement(data, Announcements.Message); + case "sendalert": + return await funct.publishServiceAnnouncement(data, Announcements.Alert); + } + + } +} \ No newline at end of file diff --git a/src/providers/Discord.ts b/src/providers/Discord.ts index 7a070af..748f154 100644 --- a/src/providers/Discord.ts +++ b/src/providers/Discord.ts @@ -13,6 +13,7 @@ import Discord_InteractionManager from "../discord/InteractionManager"; import Discord_MembershipScreening from "../discord/MembershipScreening"; import Discord_osu from "../discord/osu"; import Discord_UserInfo from "../discord/UserInfo"; +import Discord_Developer_ServiceAnnouncement from "../discord/developer/ServiceAnnouncement" import Cache from "./Cache"; @@ -47,6 +48,8 @@ class Discord { this.loaded_module["MembershipScreening"] = new Discord_MembershipScreening(); + this.loaded_module["Discord_Developer_ServiceAnnouncement"] = new Discord_Developer_ServiceAnnouncement(); + for(const module in this.loaded_module) { let thisModule = this.loaded_module[module]; diff --git a/src/utils/DiscordInteraction.ts b/src/utils/DiscordInteraction.ts index a10c1f6..b9af9f5 100644 --- a/src/utils/DiscordInteraction.ts +++ b/src/utils/DiscordInteraction.ts @@ -52,6 +52,24 @@ GUILD_COMMANDS.push(new SlashCommandBuilder() .setRequired(true) ) ) + .addSubcommand(info => info + .setName('setenableserviceannouncement') + .setDescription('Enable or disable service announcement feature') + .addStringOption(prefix => prefix + .setName('status') + .setDescription('New status') + .setRequired(true) + ) + ) + .addSubcommand(info => info + .setName('setserviceannouncementchannel') + .setDescription('Set channel where service announcement will be sent') + .addChannelOption(prefix => prefix + .setName('channel') + .setDescription('New status') + .setRequired(true) + ) + ) ); GUILD_COMMANDS.push(new SlashCommandBuilder() .setName('membershipscreening')