mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-18 16:53:57 +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 EmptyObject<K = PropertyKey> = Record<K, never>
|
||||||
type ValuesOf<T> = T[keyof T]
|
type ValuesOf<T> = T[keyof T]
|
||||||
type MaybeArray<T> = T | 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 { 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'
|
import type { ConfigMessageScanResponseMessage } from '../../../config.schema'
|
||||||
|
|
||||||
export const createErrorEmbed = (title: string, description?: string) =>
|
export const createErrorEmbed = (title: string, description?: string) =>
|
||||||
@@ -40,6 +40,35 @@ export const createMessageScanResponseEmbed = (
|
|||||||
return applyCommonEmbedStyles(embed, true, true, true)
|
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 = (
|
export const applyCommonEmbedStyles = (
|
||||||
embed: EmbedBuilder,
|
embed: EmbedBuilder,
|
||||||
setThumbnail = false,
|
setThumbnail = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user