@ -1,5 +1,5 @@
import { LoadingEmbed } from "../../utils/defaults.js" ;
import Discord , { CommandInteraction , Message , ActionRowBuilder , GuildMember , StringSelectMenuBuilder , StringSelectMenuInteraction , AutocompleteInteraction } from "discord.js" ;
import Discord , { CommandInteraction , Message , ActionRowBuilder , GuildMember , StringSelectMenuBuilder , StringSelectMenuInteraction , SelectMenuOptionBuilder } from "discord.js" ;
import EmojiEmbed from "../../utils/generateEmojiEmbed.js" ;
import confirmationMessage from "../../utils/confirmationMessage.js" ;
import type { SlashCommandSubcommandBuilder } from "@discordjs/builders" ;
@ -12,214 +12,34 @@ const command = (builder: SlashCommandSubcommandBuilder) =>
builder
. setName ( "stats" )
. setDescription ( "Controls channels which update when someone joins or leaves the server" )
. addChannelOption ( ( option ) = > option . setName ( "channel" ) . setDescription ( "The channel to modify" ) )
. addStringOption ( ( option ) = >
option
. setName ( "name" )
. setDescription ( "The new channel name | Enter any text or use the extra variables like {memberCount}" )
. setAutocomplete ( true )
) ;
const callback = async ( interaction : CommandInteraction ) : Promise < unknown > = > { // TODO: This command feels unintuitive. Clicking a channel in the select menu deletes it
// instead, it should give a submenu to edit the channel, enable/disable or delete it
singleNotify ( "statsChannelDeleted" , interaction . guild ! . id , true ) ;
const m = ( await interaction . reply ( {
embeds : LoadingEmbed ,
ephemeral : true ,
fetchReply : true
} ) ) as Message ;
let config = await client . database . guilds . read ( interaction . guild ! . id ) ;
if ( interaction . options . get ( "name" ) ? . value as string ) {
let channel ;
if ( Object . keys ( config . stats ) . length >= 25 ) {
return await interaction . editReply ( {
embeds : [
new EmojiEmbed ( )
. setEmoji ( "CHANNEL.TEXT.DELETE" )
. setTitle ( "Stats Channel" )
. setDescription ( "You can only have 25 stats channels in a server" )
. setStatus ( "Danger" )
]
} ) ;
}
try {
channel = interaction . options . get ( "channel" ) ? . channel as Discord . Channel ;
} catch {
return await interaction . editReply ( {
embeds : [
new EmojiEmbed ( )
. setEmoji ( "CHANNEL.TEXT.DELETE" )
. setTitle ( "Stats Channel" )
. setDescription ( "The channel you provided is not a valid channel" )
. setStatus ( "Danger" )
]
} ) ;
}
channel = channel as Discord . TextChannel ;
if ( channel . guild . id !== interaction . guild ! . id ) {
return interaction . editReply ( {
embeds : [
new EmojiEmbed ( )
. setTitle ( "Stats Channel" )
. setDescription ( "You must choose a channel in this server" )
. setStatus ( "Danger" )
. setEmoji ( "CHANNEL.TEXT.DELETE" )
]
} ) ;
}
let newName = await convertCurlyBracketString (
interaction . options . get ( "name" ) ? . value as string ,
"" ,
"" ,
interaction . guild ! . name ,
interaction . guild ! . members
) ;
if ( interaction . options . get ( "channel" ) ? . channel ! . type === Discord . ChannelType . GuildText ) {
newName = newName . toLowerCase ( ) . replace ( /[\s]/g , "-" ) ;
}
const confirmation = await new confirmationMessage ( interaction )
. setEmoji ( "CHANNEL.TEXT.EDIT" )
. setTitle ( "Stats Channel" )
. setDescription (
` Are you sure you want to set <# ${ channel . id } > to a stats channel? \ n \ n*Preview: ${ newName . replace (
/^ +| $/g ,
""
) } * `
)
. setColor ( "Warning" )
. setInverted ( true )
. setFailedMessage ( ` Could not convert <# ${ channel . id } > to a stats chanel. ` , "Danger" , "CHANNEL.TEXT.DELETE" )
. send ( true ) ;
if ( confirmation . cancelled ) return ;
if ( confirmation . success ) {
try {
const name = interaction . options . get ( "name" ) ? . value as string ;
const channel = interaction . options . get ( "channel" ) ? . channel as Discord . TextChannel ;
await client . database . guilds . write ( interaction . guild ! . id , {
[ ` stats. ${ channel . id } ` ] : { name : name , enabled : true }
} ) ;
const { log , NucleusColors , entry , renderUser , renderChannel } = client . logger ;
const data = {
meta : {
type : "statsChannelUpdate" ,
displayName : "Stats Channel Updated" ,
calculateType : "nucleusSettingsUpdated" ,
color : NucleusColors.yellow ,
emoji : "CHANNEL.TEXT.EDIT" ,
timestamp : new Date ( ) . getTime ( )
} ,
list : {
memberId : entry ( interaction . user . id , ` \` ${ interaction . user . id } \` ` ) ,
changedBy : entry ( interaction . user . id , renderUser ( interaction . user ) ) ,
channel : entry ( channel . id , renderChannel ( channel ) ) ,
name : entry (
interaction . options . get ( "name" ) ? . value as string ,
` \` ${ interaction . options . get ( "name" ) ? . value as string } \` `
)
} ,
hidden : {
guild : interaction.guild ! . id
}
} ;
log ( data ) ;
} catch ( e ) {
console . log ( e ) ;
return interaction . editReply ( {
embeds : [
new EmojiEmbed ( )
. setTitle ( "Stats Channel" )
. setDescription ( "Something went wrong and the stats channel could not be set" )
. setStatus ( "Danger" )
. setEmoji ( "CHANNEL.TEXT.DELETE" )
] ,
components : [ ]
} ) ;
}
} else {
return interaction . editReply ( {
embeds : [
new EmojiEmbed ( )
. setTitle ( "Stats Channel" )
. setDescription ( "No changes were made" )
const callback = async ( interaction : CommandInteraction ) = > {
if ( ! interaction . guild ) return ;
let closed = false ;
let page = 0 ;
do {
const config = await client . database . guilds . read ( interaction . guild . id ) ;
const stats = config . stats ; // stats: Record<string, { name: string; enabled: boolean }>
if ( ! stats ) {
await interaction . editReply ( { embeds : [ new EmojiEmbed ( )
. setTitle ( "Stats channels" )
. setDescription ( "You don't have ant stats channels yet" )
. setStatus ( "Success" )
. setEmoji ( "CHANNEL.TEXT.CREATE" )
] ,
components : [ ]
} ) ;
. setEmoji ( "" )
] } )
}
await statsChannelAddCallback ( client , interaction . member as GuildMember ) ;
}
let timedOut = false ;
while ( ! timedOut ) {
config = await client . database . guilds . read ( interaction . guild ! . id ) ;
const stats = config . stats ;
const selectMenu = new StringSelectMenuBuilder ( )
. setCustomId ( "remove" )
let pageSelect = new StringSelectMenuBuilder ( )
. setCustomId ( "page" )
. setPlaceholder ( "Select a stats channel to manage" )
. setMinValues ( 1 )
. setMaxValues ( Math . max ( 1 , Object . keys ( stats ) . length ) ) ;
await interaction . editReply ( {
embeds : [
new EmojiEmbed ( )
. setTitle ( "Stats Channel" )
. setDescription (
"The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list."
)
. setStatus ( "Success" )
. setEmoji ( "CHANNEL.TEXT.CREATE" )
] ,
components : [
new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents (
Object . keys ( stats ) . length
? [
selectMenu
. setPlaceholder ( "Select a stats channel to remove, stopping it updating" )
. addOptions (
Object . keys ( stats ) . map ( ( key ) = > ( {
label : interaction.guild ! . channels . cache . get ( key ) ! . name ,
value : key ,
description : ` ${ stats [ key ] ! . name } `
} ) )
)
]
: [
selectMenu
. setPlaceholder ( "The server has no stats channels" )
. setDisabled ( true )
. setOptions ( [
{
label : "*Placeholder*" ,
value : "placeholder" ,
description : "No stats channels"
}
] )
]
)
]
} ) ;
let i : StringSelectMenuInteraction ;
try {
i = await m . awaitMessageComponent ( {
time : 300000 ,
filter : ( i ) = > { return i . user . id === interaction . user . id && i . channel ! . id === interaction . channel ! . id }
} ) as StringSelectMenuInteraction ;
} catch ( e ) {
timedOut = true ;
continue ;
}
i . deferUpdate ( ) ;
if ( i . customId === "remove" ) {
const toRemove = i . values ;
await client . database . guilds . write (
interaction . guild ! . id ,
null ,
toRemove . map ( ( k ) = > ` stats. ${ k } ` )
) ;
}
}
await interaction . editReply ( {
embeds : [ new Discord . EmbedBuilder ( m . embeds [ 0 ] ! . data ) . setFooter ( { text : "Message timed out" } ) ] ,
components : [ ]
} ) ;
. setMaxValues ( 1 ) ;
for ( const [ id , { name , enabled } ] of Object . entries ( stats ) ) {
pageSelect . addOption ( )
}
// [ Action... ] -> Edit, delete, reorder
// [Back][Next][Add]
} while ( ! closed ) ;
closed = true ;
} ;
const check = ( interaction : CommandInteraction ) = > {
@ -229,21 +49,7 @@ const check = (interaction: CommandInteraction) => {
return true ;
} ;
const generateStatsChannelAutocomplete = ( prompt : string ) : string [ ] = > {
return [ prompt ] ;
} ;
const autocomplete = async ( interaction : AutocompleteInteraction ) : Promise < string [ ] > = > {
if ( ! interaction . guild ) return [ ] ;
const prompt = interaction . options . getString ( "tag" ) ;
// generateStatsChannelAutocomplete(int.options.getString("name") ?? "")
const results = generateStatsChannelAutocomplete ( prompt ? ? "" ) ;
return results ;
} ;
export { command } ;
export { callback } ;
export { check } ;
export { autocomplete } ;