-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
647d2fd
commit d825a95
Showing
1 changed file
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { | ||
SlashCommandBuilder, | ||
PermissionFlagsBits, | ||
ActionRowBuilder, | ||
ButtonBuilder, | ||
ButtonStyle, | ||
} from 'discord.js'; | ||
|
||
|
||
export default { | ||
data: new SlashCommandBuilder() | ||
.setName('dm') | ||
.setDescription('Send a direct message to a user or role') | ||
.addSubcommand(subcommand => | ||
subcommand | ||
.setName('user') | ||
.setDescription('Send a DM to a specific user') | ||
.addUserOption(option => | ||
option.setName('target') | ||
.setDescription('The user to send the DM to') | ||
.setRequired(true)) | ||
.addStringOption(option => | ||
option.setName('message') | ||
.setDescription('The message to send (Use {user} for recipient\'s name)') | ||
.setRequired(true)) | ||
) | ||
.addSubcommand(subcommand => | ||
subcommand | ||
.setName('role') | ||
.setDescription('Send a DM to all users with a specific role') | ||
.addRoleOption(option => | ||
option.setName('target') | ||
.setDescription('The role to send the DM to') | ||
.setRequired(true)) | ||
.addStringOption(option => | ||
option.setName('message') | ||
.setDescription('The message to send (Use {user} for recipient\'s name)') | ||
.setRequired(true)) | ||
).toJSON(), | ||
|
||
|
||
userPermissions: [PermissionFlagsBits.ManageMessages], | ||
botPermissions: [PermissionFlagsBits.SendMessages], | ||
cooldown: 5, | ||
nwfwMode: false, | ||
testMode: false, | ||
devOnly: true, | ||
|
||
run: async (client, interaction) => { | ||
const subcommand = interaction.options.getSubcommand(); | ||
let message = interaction.options.getString('message').trim(); | ||
const sendMessage = async (user) => { | ||
if (!user) { | ||
return { success: false, reason: 'USER_NOT_FOUND' }; | ||
} | ||
|
||
try { | ||
const personalizedMessage = message.replace(/{user}/g, user.displayName); | ||
await user.send(personalizedMessage); | ||
return { success: true }; | ||
} catch (error) { | ||
if (error.code === 50007) { | ||
return { success: false, reason: 'DM_CLOSED' }; | ||
} else if (error.code === 10013) { | ||
return { success: false, reason: 'USER_NOT_FOUND' }; | ||
} else if (error.code === 50013) { | ||
return { success: false, reason: 'MISSING_PERMISSIONS' }; | ||
} else if (error.code === 50016) { | ||
return { success: false, reason: 'RATE_LIMIT' }; | ||
} | ||
return { success: false, reason: 'UNKNOWN', error }; | ||
} | ||
}; | ||
|
||
if (subcommand === 'user') { | ||
const user = interaction.options.getUser('target'); | ||
const result = await sendMessage(user); | ||
|
||
if (result.success) { | ||
await interaction.reply({ content: `Message sent to ${user.tag}`, ephemeral: true }); | ||
} else { | ||
let errorMessage = `Failed to send message to ${user.tag}. `; | ||
switch (result.reason) { | ||
case 'DM_CLOSED': | ||
errorMessage += 'They have their DMs closed.'; | ||
break; | ||
case 'USER_NOT_FOUND': | ||
errorMessage += 'User not found.'; | ||
break; | ||
case 'MISSING_PERMISSIONS': | ||
errorMessage += 'Missing permissions to send message.'; | ||
break; | ||
case 'RATE_LIMIT': | ||
errorMessage += 'Rate limit hit. Please try again later.'; | ||
break; | ||
default: | ||
errorMessage += 'An unknown error occurred.'; | ||
} | ||
await interaction.reply({ content: errorMessage, ephemeral: true }); | ||
} | ||
} else if (subcommand === 'role') { | ||
const role = interaction.options.getRole('target'); | ||
const members = role.members; | ||
|
||
const row = new ActionRowBuilder() | ||
.addComponents( | ||
new ButtonBuilder() | ||
.setCustomId('confirm') | ||
.setLabel('Confirm') | ||
.setStyle(ButtonStyle.Primary), | ||
new ButtonBuilder() | ||
.setCustomId('cancel') | ||
.setLabel('Cancel') | ||
.setStyle(ButtonStyle.Danger), | ||
); | ||
|
||
await interaction.reply({ | ||
content: `Are you sure you want to send this message to ${members.size} members with the ${role.name} role?`, | ||
components: [row], | ||
ephemeral: true | ||
}); | ||
|
||
const filter = i => i.user.id === interaction.user.id; | ||
const confirmation = await interaction.channel.awaitMessageComponent({ filter, time: 30000 }) | ||
.catch(() => null); | ||
|
||
if (!confirmation || confirmation.customId === 'cancel') { | ||
return interaction.editReply({ content: 'Command cancelled.', components: [] }); | ||
} | ||
|
||
let successCount = 0; | ||
let failureCount = 0; | ||
let count = 0; | ||
const totalMembers = members.size; | ||
const updateInterval = Math.max(1, Math.floor(totalMembers / 20)); // Update every 5% | ||
let cancelled = false; | ||
|
||
const queue = members.map(member => ({ member, sent: false })); | ||
const batchSize = 10; | ||
const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); | ||
|
||
const processBatch = async () => { | ||
const batch = queue.filter(item => !item.sent).slice(0, batchSize); | ||
if (batch.length === 0 || cancelled) return; | ||
|
||
await Promise.all(batch.map(async (item) => { | ||
if (cancelled) return; | ||
const result = await sendMessage(item.member); | ||
item.sent = true; | ||
if (result.success) successCount++; else failureCount++; | ||
count++; | ||
|
||
if (count % updateInterval === 0 || count === totalMembers) { | ||
const progress = (count / totalMembers * 100).toFixed(2); | ||
const progressBar = '█'.repeat(Math.floor(progress / 5)) + '░'.repeat(20 - Math.floor(progress / 5)); | ||
await interaction.editReply({ | ||
content: `Progress: ${progressBar} ${progress}%\n${count}/${totalMembers} messages sent`, | ||
components: [new ActionRowBuilder().addComponents( | ||
new ButtonBuilder() | ||
.setCustomId('cancel_sending') | ||
.setLabel('Cancel Sending') | ||
.setStyle(ButtonStyle.Danger) | ||
)], | ||
ephemeral: true | ||
}); | ||
} | ||
})); | ||
|
||
await delay(1000); | ||
await processBatch(); | ||
}; | ||
|
||
processBatch(); | ||
|
||
const cancelListener = interaction.channel.createMessageComponentCollector({ filter, time: 600000 }); | ||
cancelListener.on('collect', async i => { | ||
if (i.customId === 'cancel_sending') { | ||
cancelled = true; | ||
await i.update({ content: 'Cancelling the operation. Please wait...', components: [] }); | ||
} | ||
}); | ||
|
||
while (queue.some(item => !item.sent) && !cancelled) { | ||
await delay(1000); | ||
} | ||
|
||
cancelListener.stop(); | ||
|
||
const finalMessage = cancelled | ||
? `Operation cancelled. ${successCount} messages sent, ${failureCount} failed.` | ||
: `Finished! ${successCount} messages sent, ${failureCount} failed.`; | ||
|
||
await interaction.editReply({ | ||
content: finalMessage, | ||
components: [] | ||
}); | ||
} | ||
|
||
}, | ||
}; |