mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-11 13:56:15 +00:00
feat(bots/discord/commands): add mute and unmute commands
This commit is contained in:
65
bots/discord/src/commands/moderation/mute.ts
Normal file
65
bots/discord/src/commands/moderation/mute.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { SlashCommandBuilder } from 'discord.js'
|
||||
|
||||
import CommandError, { CommandErrorType } from '$/classes/CommandError'
|
||||
import { applyRolePreset } from '$/utils/discord/rolePresets'
|
||||
import type { Command } from '..'
|
||||
|
||||
import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds'
|
||||
import { parse } from 'simple-duration'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('mute')
|
||||
.setDescription('Mute a member')
|
||||
.addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to mute'))
|
||||
.addStringOption(option => option.setName('reason').setDescription('The reason for muting the member'))
|
||||
.addStringOption(option => option.setName('duration').setDescription('The duration of the mute'))
|
||||
.toJSON(),
|
||||
|
||||
memberRequirements: {
|
||||
permissions: 8n,
|
||||
},
|
||||
|
||||
global: false,
|
||||
|
||||
async execute({ config, logger }, interaction) {
|
||||
const user = interaction.options.getUser('member', true)
|
||||
const reason = interaction.options.getString('reason')
|
||||
const duration = interaction.options.getString('duration')
|
||||
const durationMs = duration ? parse(duration) : null
|
||||
|
||||
if (Number.isInteger(durationMs) && durationMs! < 1)
|
||||
throw new CommandError(
|
||||
CommandErrorType.InvalidDuration,
|
||||
'The duration must be at least 1 millisecond long.',
|
||||
)
|
||||
|
||||
const member = await interaction.guild!.members.fetch(user.id)
|
||||
if (!member)
|
||||
throw new CommandError(
|
||||
CommandErrorType.InvalidUser,
|
||||
'The provided member is not in the server or does not exist.',
|
||||
)
|
||||
|
||||
await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null)
|
||||
|
||||
const embed = createModerationActionEmbed(
|
||||
'Muted',
|
||||
user,
|
||||
interaction.user,
|
||||
reason ?? 'No reason provided',
|
||||
durationMs,
|
||||
)
|
||||
|
||||
const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch())
|
||||
|
||||
const logConfig = config.moderation?.log
|
||||
if (logConfig) {
|
||||
const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel)
|
||||
if (!channel || !channel.isTextBased())
|
||||
return void logger.warn('The moderation log channel does not exist, skipping logging')
|
||||
|
||||
await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] })
|
||||
}
|
||||
},
|
||||
} satisfies Command
|
||||
44
bots/discord/src/commands/moderation/ีืunmute.ts
Normal file
44
bots/discord/src/commands/moderation/ีืunmute.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { SlashCommandBuilder } from 'discord.js'
|
||||
|
||||
import CommandError, { CommandErrorType } from '$/classes/CommandError'
|
||||
import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds'
|
||||
import { removeRolePreset } from '$/utils/discord/rolePresets'
|
||||
import type { Command } from '..'
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unmute')
|
||||
.setDescription('Unmute a member')
|
||||
.addUserOption(option => option.setName('member').setRequired(true).setDescription('The member to mute'))
|
||||
.toJSON(),
|
||||
|
||||
memberRequirements: {
|
||||
permissions: 8n,
|
||||
},
|
||||
|
||||
global: false,
|
||||
|
||||
async execute({ config, logger }, interaction) {
|
||||
const user = interaction.options.getUser('member', true)
|
||||
const member = await interaction.guild!.members.fetch(user.id)
|
||||
if (!member)
|
||||
throw new CommandError(
|
||||
CommandErrorType.InvalidUser,
|
||||
'The provided member is not in the server or does not exist.',
|
||||
)
|
||||
|
||||
await removeRolePreset(member, 'mute')
|
||||
const embed = createModerationActionEmbed('Muted', user, interaction.user)
|
||||
|
||||
const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch())
|
||||
|
||||
const logConfig = config.moderation?.log
|
||||
if (logConfig) {
|
||||
const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel)
|
||||
if (!channel || !channel.isTextBased())
|
||||
return void logger.warn('The moderation log channel does not exist, skipping logging')
|
||||
|
||||
await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] })
|
||||
}
|
||||
},
|
||||
} satisfies Command
|
||||
5
bots/discord/src/types.d.ts
vendored
5
bots/discord/src/types.d.ts
vendored
@@ -3,3 +3,8 @@ type IfTrue<Condition, True, False> = IfExtends<Condition, true, True, False>
|
||||
type EmptyObject<K = PropertyKey> = Record<K, never>
|
||||
type ValuesOf<T> = T[keyof T]
|
||||
type MaybeArray<T> = T | T[]
|
||||
|
||||
declare module 'simple-duration' {
|
||||
export function parse(duration: string): number
|
||||
export function stringify(duration: number): string
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
import { EmbedBuilder, type EmbedField, type User } from 'discord.js'
|
||||
import type { ConfigMessageScanResponseMessage } from '../../../config.schema'
|
||||
|
||||
export const createErrorEmbed = (title: string, description?: string) =>
|
||||
@@ -40,6 +40,35 @@ export const createMessageScanResponseEmbed = (
|
||||
return applyCommonEmbedStyles(embed, true, true, true)
|
||||
}
|
||||
|
||||
export const createModerationActionEmbed = (
|
||||
action: string,
|
||||
user: User,
|
||||
moderator: User,
|
||||
reason?: string,
|
||||
expires?: number | null,
|
||||
) => {
|
||||
const fields: EmbedField[] = []
|
||||
if (reason) fields.push({ name: 'Reason', value: reason, inline: true })
|
||||
if (Number.isInteger(expires) || expires === null)
|
||||
fields.push({
|
||||
name: 'Expires',
|
||||
value: Number.isInteger(expires) ? new Date(expires! * 1000).toLocaleString() : 'Never',
|
||||
inline: true,
|
||||
})
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`${action} ${user.tag}`)
|
||||
.setDescription(`${user.toString()} was ${action.toLowerCase()} by ${moderator.toString()}`)
|
||||
.addFields(fields)
|
||||
|
||||
return applyCommonEmbedStyles(embed, true, true, true)
|
||||
}
|
||||
|
||||
export const applyReferenceToModerationActionEmbed = (embed: EmbedBuilder, reference: string) => {
|
||||
embed.addFields({ name: 'Reference', value: `[Jump to message](${reference})`, inline: true })
|
||||
return embed
|
||||
}
|
||||
|
||||
export const applyCommonEmbedStyles = (
|
||||
embed: EmbedBuilder,
|
||||
setThumbnail = false,
|
||||
|
||||
Reference in New Issue
Block a user