mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-23 03:01:03 +00:00
feat(bots/discord): framework changes and new features
- Migrated to a new command framework which looks better and works better - Fixed commands not being bundled correctly - Added message (prefix) commands with argument validation - Added a new CommandErrorType, for invalid arguments - `/eval` is now a bit safer - Corrected colors for the coinflip embed - `/stop` now works even when the bot is not connected to the API
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
import { type Response, responses } from '$/database/schemas'
|
||||
import type {
|
||||
Config,
|
||||
ConfigMessageScanResponse,
|
||||
ConfigMessageScanResponseLabelConfig
|
||||
} from 'config.schema'
|
||||
import type { Config, ConfigMessageScanResponse, ConfigMessageScanResponseLabelConfig } from 'config.schema'
|
||||
import type { Message, PartialUser, User } from 'discord.js'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { createMessageScanResponseEmbed } from './embeds'
|
||||
@@ -17,7 +13,7 @@ export const getResponseFromText = async (
|
||||
): Promise<ConfigMessageScanResponse & { label?: string }> => {
|
||||
let responseConfig: Awaited<ReturnType<typeof getResponseFromText>> = {
|
||||
triggers: {},
|
||||
response: null
|
||||
response: null,
|
||||
}
|
||||
|
||||
const firstLabelIndexes: number[] = []
|
||||
@@ -29,7 +25,7 @@ export const getResponseFromText = async (
|
||||
// Filter override check is not neccessary here, we are already passing responses that match the filter
|
||||
// from the messageCreate handler, see line 17 of messageCreate handler
|
||||
const {
|
||||
triggers: { text: textTriggers, image: imageTriggers }
|
||||
triggers: { text: textTriggers, image: imageTriggers },
|
||||
} = trigger
|
||||
if (responseConfig) break
|
||||
|
||||
@@ -92,7 +88,7 @@ export const getResponseFromText = async (
|
||||
logger.debug('No match from NLP, doing after regexes')
|
||||
for (let i = 0; i < responses.length; i++) {
|
||||
const {
|
||||
triggers: { text: textTriggers }
|
||||
triggers: { text: textTriggers },
|
||||
} = responses[i]!
|
||||
const firstLabelIndex = firstLabelIndexes[i] ?? -1
|
||||
|
||||
@@ -113,10 +109,7 @@ export const getResponseFromText = async (
|
||||
return responseConfig
|
||||
}
|
||||
|
||||
export const messageMatchesFilter = (
|
||||
message: Message,
|
||||
filter: NonNullable<Config['messageScan']>['filter'],
|
||||
) => {
|
||||
export const messageMatchesFilter = (message: Message, filter: NonNullable<Config['messageScan']>['filter']) => {
|
||||
if (!filter) return true
|
||||
|
||||
const memberRoles = new Set(message.member?.roles.cache.keys())
|
||||
@@ -124,7 +117,12 @@ export const messageMatchesFilter = (
|
||||
|
||||
// If matches blacklist, will return false
|
||||
// Any other case, will return true
|
||||
return !(blFilter && (blFilter.channels?.includes(message.channelId) || blFilter.roles?.some(role => memberRoles.has(role)) || blFilter.users?.includes(message.author.id)))
|
||||
return !(
|
||||
blFilter &&
|
||||
(blFilter.channels?.includes(message.channelId) ||
|
||||
blFilter.roles?.some(role => memberRoles.has(role)) ||
|
||||
blFilter.users?.includes(message.author.id))
|
||||
)
|
||||
}
|
||||
|
||||
export const handleUserResponseCorrection = async (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { config, logger } from '$/context'
|
||||
import decancer from 'decancer'
|
||||
import type { ChatInputCommandInteraction, EmbedBuilder, Guild, GuildMember, User } from 'discord.js'
|
||||
import type { ChatInputCommandInteraction, EmbedBuilder, Guild, GuildMember, Message, User } from 'discord.js'
|
||||
import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds'
|
||||
|
||||
const PresetLogAction = {
|
||||
@@ -10,19 +10,23 @@ const PresetLogAction = {
|
||||
|
||||
export const sendPresetReplyAndLogs = (
|
||||
action: keyof typeof PresetLogAction,
|
||||
interaction: ChatInputCommandInteraction,
|
||||
interaction: ChatInputCommandInteraction | Message,
|
||||
executor: GuildMember,
|
||||
user: User,
|
||||
preset: string,
|
||||
expires?: number | null,
|
||||
) =>
|
||||
sendModerationReplyAndLogs(
|
||||
interaction,
|
||||
createModerationActionEmbed(PresetLogAction[action], user, interaction.user, undefined, expires, [
|
||||
createModerationActionEmbed(PresetLogAction[action], user, executor.user, undefined, expires, [
|
||||
[{ name: 'Preset', value: preset, inline: true }],
|
||||
]),
|
||||
)
|
||||
|
||||
export const sendModerationReplyAndLogs = async (interaction: ChatInputCommandInteraction, embed: EmbedBuilder) => {
|
||||
export const sendModerationReplyAndLogs = async (
|
||||
interaction: ChatInputCommandInteraction | Message,
|
||||
embed: EmbedBuilder,
|
||||
) => {
|
||||
const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch())
|
||||
const logChannel = await getLogChannel(interaction.guild!)
|
||||
await logChannel?.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] })
|
||||
@@ -46,7 +50,7 @@ export const getLogChannel = async (guild: Guild) => {
|
||||
}
|
||||
|
||||
export const cureNickname = async (member: GuildMember) => {
|
||||
if (!member.manageable) throw new Error('Member is not manageable')
|
||||
if (!member.manageable) return
|
||||
const name = member.displayName
|
||||
let cured = decancer(name)
|
||||
.toString()
|
||||
|
||||
@@ -2,10 +2,13 @@ import { GuildMember, type User } from 'discord.js'
|
||||
import config from '../../../config'
|
||||
|
||||
export const isAdmin = (userOrMember: User | GuildMember) => {
|
||||
return config.admin?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember))
|
||||
return (
|
||||
config.admin?.users?.includes(userOrMember.id) ||
|
||||
(userOrMember instanceof GuildMember && isMemberAdmin(userOrMember))
|
||||
)
|
||||
}
|
||||
|
||||
export const isMemberAdmin = (member: GuildMember) => {
|
||||
const roles = new Set(member.roles.cache.keys())
|
||||
return Boolean(config?.admin?.roles?.[member.guild.id]?.some(role => roles.has(role)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import { and, eq } from 'drizzle-orm'
|
||||
// TODO: Fix this type
|
||||
type PresetKey = string
|
||||
|
||||
export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number | null) => {
|
||||
export const applyRolePreset = async (member: GuildMember, presetName: PresetKey, untilMs: number) => {
|
||||
const afterInsert = await applyRolesUsingPreset(presetName, member, true)
|
||||
const until = untilMs ? Math.ceil(untilMs / 1000) : null
|
||||
const until = untilMs === Infinity ? null : Math.ceil(untilMs / 1000)
|
||||
|
||||
await database
|
||||
.insert(appliedPresets)
|
||||
|
||||
@@ -7,17 +7,27 @@ export const listAllFilesRecursive = (dir: string): string[] =>
|
||||
.filter(x => x.isFile())
|
||||
.map(x => join(x.parentPath, x.name).replaceAll(pathSep, posixPathSep))
|
||||
|
||||
export const generateCommandsIndex = (dirPath: string) => generateIndexes(dirPath, x => !x.endsWith('types.ts'))
|
||||
export const generateCommandsIndex = (dirPath: string) =>
|
||||
generateIndexes(dirPath, (x, i) => `export { default as C${i} } from './${x}'`)
|
||||
|
||||
export const generateEventsIndex = (dirPath: string) => generateIndexes(dirPath)
|
||||
|
||||
const generateIndexes = async (dirPath: string, pathFilter?: (path: string) => boolean) => {
|
||||
const generateIndexes = async (
|
||||
dirPath: string,
|
||||
customMap?: (path: string, index: number) => string,
|
||||
pathFilter?: (path: string) => boolean,
|
||||
) => {
|
||||
const files = listAllFilesRecursive(dirPath)
|
||||
.filter(x => (x.endsWith('.ts') && !x.endsWith('index.ts') && pathFilter ? pathFilter(x) : true))
|
||||
.filter(x => x.endsWith('.ts') && !x.endsWith('index.ts') && (pathFilter ? pathFilter(x) : true))
|
||||
.map(x => relative(dirPath, x).replaceAll(pathSep, posixPathSep))
|
||||
|
||||
writeFileSync(
|
||||
join(dirPath, 'index.ts'),
|
||||
`// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH\n\n${files.map(c => `import './${c.split('.').at(-2)}'`).join('\n')}`,
|
||||
`// AUTO-GENERATED BY A SCRIPT, DON'T TOUCH\n\n${files
|
||||
.map((c, i) => {
|
||||
const path = c.split('.').at(-2)!
|
||||
return customMap ? customMap(path, i) : `import './${path}'`
|
||||
})
|
||||
.join('\n')}`,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user