Merge branch 'feature/slash-command' into experimental

This commit is contained in:
Siwoo Jeon 2024-10-01 13:54:52 +09:00
commit e0e82d44f8
Signed by: migan
GPG key ID: 036E9A8C5E8E48DA
8 changed files with 225 additions and 62 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "muffinbot", "name": "muffinbot",
"version": "4.0.0-pudding.d240928e", "version": "4.0.0-pudding.e241001a",
"main": "dist/index.js", "main": "dist/index.js",
"private": true, "private": true,
"dependencies": { "dependencies": {

View file

@ -22,7 +22,7 @@ container.version = version
container.database = new PrismaClient() container.database = new PrismaClient()
container.dokdoAliases = ['dokdo', 'dok', 'Dokdo', 'Dok', '테스트'] container.dokdoAliases = ['dokdo', 'dok', 'Dokdo', 'Dok', '테스트']
container.chatBot = new ChatBot(container.database) container.chatBot = new ChatBot(container.database)
container.lastUpdated = new Date('2024-09-28') container.lastUpdated = new Date('2024-10-01')
if (release.startsWith('e')) { if (release.startsWith('e')) {
container.channel = 'EXPERIMENTAL' container.channel = 'EXPERIMENTAL'

View file

@ -1,16 +1,18 @@
import { ApplyOptions } from '@sapphire/decorators'
import {
type SelectMenuComponentOptionData,
type User,
ChatInputCommandInteraction,
ComponentType,
codeBlock,
Message,
} from 'discord.js'
import { import {
Args, Args,
Command, Command,
container, container,
DetailedDescriptionCommandObject, DetailedDescriptionCommandObject,
} from '@sapphire/framework' } from '@sapphire/framework'
import {
type SelectMenuComponentOptionData,
type Message,
ComponentType,
codeBlock,
} from 'discord.js'
import { ApplyOptions } from '@sapphire/decorators'
@ApplyOptions<Command.Options>({ @ApplyOptions<Command.Options>({
name: '삭제', name: '삭제',
@ -22,13 +24,36 @@ import { ApplyOptions } from '@sapphire/decorators'
}, },
}) })
class DeleteLearnCommand extends Command { class DeleteLearnCommand extends Command {
public async messageRun(msg: Message, args: Args) { public registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder =>
builder
.setName(this.name)
.setDescription(this.description)
.addStringOption(option =>
option
.setRequired(true)
.setName('단어')
.setDescription('삭제할 단어를 입력해주세요.'),
),
)
}
private async _run(ctx: Message | ChatInputCommandInteraction, args?: Args) {
let command: string | null
let user: User
if (ctx instanceof Message) {
command = await args!.rest('string').catch(() => null)
user = ctx.author
} else {
command = ctx.options.getString('단어', true)
user = ctx.user
}
const CUSTOM_ID = 'maa$deleteLearn' const CUSTOM_ID = 'maa$deleteLearn'
const command = await args.rest('string').catch(() => null)
const options: SelectMenuComponentOptionData[] = [] const options: SelectMenuComponentOptionData[] = []
const deleteDataList: string[] = [] const deleteDataList: string[] = []
if (!command) { if (!command) {
return await msg.reply( return await ctx.reply(
`사용법: \n\`\`\`${(this.detailedDescription as DetailedDescriptionCommandObject).usage}\`\`\``, `사용법: \n\`\`\`${(this.detailedDescription as DetailedDescriptionCommandObject).usage}\`\`\``,
) )
} }
@ -36,12 +61,12 @@ class DeleteLearnCommand extends Command {
const deleteDatas = await this.container.database.learn.findMany({ const deleteDatas = await this.container.database.learn.findMany({
where: { where: {
command, command,
user_id: msg.author.id, user_id: user.id,
}, },
}) })
if (!deleteDatas) { if (!deleteDatas) {
return await msg.reply('해당하는 걸 찾ㅈ을 수 없어요.') return await ctx.reply('해당하는 걸 찾ㅈ을 수 없어요.')
} }
for (let i = 1; i <= deleteDatas.length; i++) { for (let i = 1; i <= deleteDatas.length; i++) {
@ -53,7 +78,7 @@ class DeleteLearnCommand extends Command {
}) })
} }
await msg.reply({ await ctx.reply({
embeds: [ embeds: [
{ {
title: '삭제', title: '삭제',
@ -67,7 +92,7 @@ class DeleteLearnCommand extends Command {
components: [ components: [
{ {
type: ComponentType.StringSelect, type: ComponentType.StringSelect,
customId: `${CUSTOM_ID}@${msg.author.id}`, customId: `${CUSTOM_ID}@${user.id}`,
placeholder: '지울 데이터를 선택해ㅈ주세요', placeholder: '지울 데이터를 선택해ㅈ주세요',
options, options,
}, },
@ -76,6 +101,14 @@ class DeleteLearnCommand extends Command {
], ],
}) })
} }
public async messageRun(msg: Message, args: Args) {
await this._run(msg, args)
}
public async chatInputRun(interaction: ChatInputCommandInteraction) {
await this._run(interaction)
}
} }
void container.stores.loadPiece({ void container.stores.loadPiece({

View file

@ -1,6 +1,10 @@
import { Args, Command, container } from '@sapphire/framework' import { Args, Command, container } from '@sapphire/framework'
import { codeBlock, type Message } from 'discord.js'
import { ApplyOptions } from '@sapphire/decorators' import { ApplyOptions } from '@sapphire/decorators'
import {
type ChatInputCommandInteraction,
codeBlock,
Message,
} from 'discord.js'
@ApplyOptions<Command.Options>({ @ApplyOptions<Command.Options>({
name: '도움말', name: '도움말',
@ -12,8 +16,32 @@ import { ApplyOptions } from '@sapphire/decorators'
}, },
}) })
class HelpCommand extends Command { class HelpCommand extends Command {
public async messageRun(msg: Message, args: Args) { public registerApplicationCommands(registry: Command.Registry) {
const commandName = await args.pick('string').catch(() => null) const commands = this.container.stores.get('commands').map(command => {
return {
name: command.name,
value: command.name,
}
})
registry.registerChatInputCommand(builder =>
builder
.setName(this.name)
.setDescription(this.description)
.addStringOption(option =>
option
.setName('명령어')
.setDescription('해당 명령어에 대ㅎ한 도움말을 볼 수 있어요.')
.addChoices(commands),
),
)
}
private async _run(ctx: Message | ChatInputCommandInteraction, args?: Args) {
let commandName: string | null
if (ctx instanceof Message) {
commandName = await args!.pick('string').catch(() => null)
} else {
commandName = ctx.options.getString('명령어')
}
if ( if (
!commandName || !commandName ||
!this.container.stores.get('commands').get(commandName) !this.container.stores.get('commands').get(commandName)
@ -24,7 +52,7 @@ class HelpCommand extends Command {
commandList.push(`${module.name} - ${module.description}`) commandList.push(`${module.name} - ${module.description}`)
}) })
await msg.reply({ await ctx.reply({
embeds: [ embeds: [
{ {
title: `${this.container.client.user?.username}의 도움말`, title: `${this.container.client.user?.username}의 도움말`,
@ -44,7 +72,7 @@ class HelpCommand extends Command {
this.container.stores.get('commands').get(commandName)! this.container.stores.get('commands').get(commandName)!
if (typeof detailedDescription === 'string') return if (typeof detailedDescription === 'string') return
await msg.reply({ await ctx.reply({
embeds: [ embeds: [
{ {
title: `${this.container.client.user?.username}의 도움말`, title: `${this.container.client.user?.username}의 도움말`,
@ -86,6 +114,13 @@ class HelpCommand extends Command {
}) })
} }
} }
public async messageRun(msg: Message, args: Args) {
await this._run(msg, args)
}
public async chatInputRun(interaction: ChatInputCommandInteraction) {
await this._run(interaction)
}
} }
void container.stores.loadPiece({ void container.stores.loadPiece({

View file

@ -1,6 +1,6 @@
import { ApplyOptions } from '@sapphire/decorators' import type { ChatInputCommandInteraction, Message } from 'discord.js'
import { Command, container } from '@sapphire/framework' import { Command, container } from '@sapphire/framework'
import { Message } from 'discord.js' import { ApplyOptions } from '@sapphire/decorators'
import { platform, arch } from 'os' import { platform, arch } from 'os'
@ApplyOptions<Command.Options>({ @ApplyOptions<Command.Options>({
@ -11,8 +11,14 @@ import { platform, arch } from 'os'
}, },
}) })
class InformationCommand extends Command { class InformationCommand extends Command {
public async messageRun(msg: Message) { public registerApplicationCommands(registry: Command.Registry) {
await msg.reply({ registry.registerChatInputCommand(builder =>
builder.setName(this.name).setDescription(this.description),
)
}
private async _run(ctx: Message | ChatInputCommandInteraction) {
await ctx.reply({
embeds: [ embeds: [
{ {
title: `${this.container.client.user?.username}의 정ㅂ보`, title: `${this.container.client.user?.username}의 정ㅂ보`,
@ -56,6 +62,14 @@ class InformationCommand extends Command {
], ],
}) })
} }
public async messageRun(msg: Message) {
await this._run(msg)
}
public async chatInputRun(interaction: ChatInputCommandInteraction) {
await this._run(interaction)
}
} }
void container.stores.loadPiece({ void container.stores.loadPiece({

View file

@ -1,5 +1,5 @@
import { ChatInputCommandInteraction, codeBlock, Message } from 'discord.js'
import { type Args, Command, container } from '@sapphire/framework' import { type Args, Command, container } from '@sapphire/framework'
import { codeBlock, type Message } from 'discord.js'
import { ApplyOptions } from '@sapphire/decorators' import { ApplyOptions } from '@sapphire/decorators'
@ApplyOptions<Command.Options>({ @ApplyOptions<Command.Options>({
@ -16,32 +16,69 @@ import { ApplyOptions } from '@sapphire/decorators'
}, },
}) })
class LearnCommand extends Command { class LearnCommand extends Command {
public async messageRun(msg: Message, args: Args) { public registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder =>
builder
.setName(this.name)
.setDescription(this.description)
.addStringOption(option =>
option
.setRequired(true)
.setName('단어')
.setDescription('등록할 단어를 입력해주세요.'),
)
.addStringOption(option =>
option
.setRequired(true)
.setName('대답')
.setDescription('해당 단어의 대답을 입력해주세요.'),
),
)
}
private async _run(ctx: Message | ChatInputCommandInteraction, args?: Args) {
if (ctx instanceof ChatInputCommandInteraction) ctx.deferReply()
if (typeof this.detailedDescription === 'string') return if (typeof this.detailedDescription === 'string') return
const config = this.container.config const config = this.container.config
const command = (await args.pick('string').catch(() => null))?.replaceAll( const IG_MSG = '해ㄷ당 단어는 배울ㄹ 수 없어요.'
const DI_MSG = '해당 단ㅇ어는 개발자님이 특별히 금지하였ㅇ어요.'
const SUCCESS_MSG = '을/를 배웠ㅇ어요.'
let command: string
let result: string
if (ctx instanceof Message) {
command = (await args!.pick('string').catch(() => null))!.replaceAll(
'_', '_',
' ', ' ',
) )
const result = (await args.pick('string').catch(() => null))?.replaceAll( result = (await args!.pick('string').catch(() => null))!.replaceAll(
'_', '_',
' ', ' ',
) )
if (!command || !result) {
return await msg.reply( if (!command || !result)
return await ctx.reply(
codeBlock( codeBlock(
'md', 'md',
`사용법: ${this.detailedDescription} `사용법: ${this.detailedDescription}
예시: ${this.detailedDescription.examples?.map(example => example).join('\n')}`, 예시: ${this.detailedDescription.examples?.map(example => example).join('\n')}`,
), ),
) )
} else {
command = ctx.options.getString('단어', true)
result = ctx.options.getString('대답', true)
} }
const commands: string[] = [] const commands: string[] = []
const aliases: string[] = [] const aliases: string[] = []
this.container.stores.get('commands').forEach(module => { this.container.stores.get('commands').forEach(module => {
commands.push(module.name) commands.push(module.name)
module.aliases.forEach(alias => aliases.push(alias)) module.aliases.forEach(alias => aliases.push(alias))
}) })
const ignore = [ const ignore = [
...commands, ...commands,
...aliases, ...aliases,
@ -52,27 +89,41 @@ class LearnCommand extends Command {
'간미', '간미',
] ]
const disallowed = ['@everyone', '@here', `<@${config.bot.owner_ID}>`] const disallowed = ['@everyone', '@here', `<@${config.bot.owner_ID}>`]
const user = ctx instanceof Message ? ctx.author : ctx.user
for (const ig of ignore) { for (const ig of ignore) {
if (command.includes(ig)) { if (command.includes(ig))
return msg.reply('해ㄷ당 단어는 배울ㄹ 수 없어요.') return ctx instanceof Message
} ? await ctx.reply(IG_MSG)
: await ctx.editReply(IG_MSG)
} }
for (const di of disallowed) { for (const di of disallowed) {
if (result.includes(di)) { if (result.includes(di))
return msg.reply('해당 단ㅇ어는 개발자님이 특별히 금지하였ㅇ어요.') return ctx instanceof Message
} ? await ctx.reply(DI_MSG)
: await ctx.editReply(DI_MSG)
} }
await this.container.database.learn.create({ await this.container.database.learn.create({
data: { data: {
user_id: msg.author.id, user_id: user.id,
command, command,
result, result,
}, },
}) })
await msg.reply(`${command}을/를 배웠ㅇ어요.`)
return ctx instanceof Message
? await ctx.reply(command + SUCCESS_MSG)
: await ctx.editReply(command + SUCCESS_MSG)
}
public async messageRun(msg: Message, args: Args) {
await this._run(msg, args)
}
public async chatInputRun(interaction: ChatInputCommandInteraction) {
await this._run(interaction)
} }
} }

View file

@ -1,6 +1,6 @@
import { ChatInputCommandInteraction, Message } from 'discord.js'
import { Command, container } from '@sapphire/framework' import { Command, container } from '@sapphire/framework'
import { ApplyOptions } from '@sapphire/decorators' import { ApplyOptions } from '@sapphire/decorators'
import { type Message } from 'discord.js'
@ApplyOptions<Command.Options>({ @ApplyOptions<Command.Options>({
name: '데이터학습량', name: '데이터학습량',
@ -11,14 +11,21 @@ import { type Message } from 'discord.js'
}, },
}) })
class LearnDataCommand extends Command { class LearnDataCommand extends Command {
public async messageRun(msg: Message<true>) { public registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder =>
builder.setName(this.name).setDescription(this.description),
)
}
private async _run(ctx: Message | ChatInputCommandInteraction) {
const user = ctx instanceof Message ? ctx.author : ctx.user
const db = this.container.database const db = this.container.database
const data = await db.statement.findMany() const data = await db.statement.findMany()
const nsfwData = await db.nsfw_content.findMany() const nsfwData = await db.nsfw_content.findMany()
const learnData = await db.learn.findMany() const learnData = await db.learn.findMany()
const userData = await db.learn.findMany({ const userData = await db.learn.findMany({
where: { where: {
user_id: msg.author.id, user_id: user.id,
}, },
}) })
const muffin: any[] = [] const muffin: any[] = []
@ -27,10 +34,18 @@ class LearnDataCommand extends Command {
else return else return
}) })
await msg.reply(`머핀 데이터: ${muffin.length} await ctx.reply(`머핀 데이터: ${muffin.length}
nsfw 데이터: ${nsfwData.length} nsfw 데이터: ${nsfwData.length}
단어: ${learnData.length} 단어: ${learnData.length}
${msg.author.username} 단어: ${userData.length}`) ${user.username} 단어: ${userData.length}`)
}
public async messageRun(msg: Message) {
await this._run(msg)
}
public async chatInputRun(interaction: ChatInputCommandInteraction) {
await this._run(interaction)
} }
} }

View file

@ -1,5 +1,5 @@
import { ChatInputCommandInteraction, Message, codeBlock } from 'discord.js'
import { ApplyOptions } from '@sapphire/decorators' import { ApplyOptions } from '@sapphire/decorators'
import { Message, codeBlock } from 'discord.js'
import { Command, container } from '@sapphire/framework' import { Command, container } from '@sapphire/framework'
@ApplyOptions<Command.Options>({ @ApplyOptions<Command.Options>({
@ -11,27 +11,34 @@ import { Command, container } from '@sapphire/framework'
}, },
}) })
class ListCommand extends Command { class ListCommand extends Command {
public async messageRun(msg: Message<boolean>) { public registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder =>
builder.setName(this.name).setDescription(this.description),
)
}
private async _run(ctx: Message | ChatInputCommandInteraction) {
const user = ctx instanceof Message ? ctx.author : ctx.user
const db = this.container.database const db = this.container.database
const data = await db.learn.findMany({ const data = await db.learn.findMany({
where: { where: {
user_id: msg.author.id, user_id: user.id,
}, },
}) })
const list: string[] = [] const list: string[] = []
if (!data[0]) { if (!data[0]) {
return await msg.reply('당신ㄴ은 단어를 가르쳐준 기억이 없ㅅ는데요.') return await ctx.reply('당신ㄴ은 단어를 가르쳐준 기억이 없ㅅ는데요.')
} }
for (const listData of data) { for (const listData of data) {
list.push(listData.command) list.push(listData.command)
} }
await msg.reply({ await ctx.reply({
embeds: [ embeds: [
{ {
title: `${msg.author.username}님의 지식`, title: `${user.username}님의 지식`,
description: `총합: ${data.length}\n${codeBlock( description: `총합: ${data.length}\n${codeBlock(
'md', 'md',
list.map(item => `- ${item}`).join('\n'), list.map(item => `- ${item}`).join('\n'),
@ -42,6 +49,14 @@ class ListCommand extends Command {
], ],
}) })
} }
public async messageRun(msg: Message<boolean>) {
await this._run(msg)
}
public async chatInputRun(interaction: ChatInputCommandInteraction) {
await this._run(interaction)
}
} }
void container.stores.loadPiece({ void container.stores.loadPiece({