Skip to content

Commit e461388

Browse files
committed
refactor: code architecture
1 parent 043d170 commit e461388

File tree

12 files changed

+297
-240
lines changed

12 files changed

+297
-240
lines changed

bin/index.mjs

Lines changed: 7 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,15 @@
11
#!/usr/bin/env node
22

3-
import { loadCommands, loadEvents, loadHarmonyConfig } from '../dist/index.mjs'
4-
import {
5-
Client,
6-
Collection,
7-
SlashCommandBuilder,
8-
REST,
9-
Routes
10-
} from 'discord.js'
3+
import { Harmony } from '../dist/index.mjs'
4+
import { resolve } from 'pathe'
115
import 'dotenv/config'
126

13-
const commands = loadCommands()
14-
const events = loadEvents()
7+
const initHarmony = async () => {
8+
const harmony = await Harmony.create({ cwd: resolve('./playground') })
159

16-
const client = new Client({
17-
intents: ['Guilds', 'GuildMessages', 'MessageContent']
18-
})
10+
console.log(harmony)
1911

20-
client.commands = new Collection()
21-
console.log('commands', commands)
22-
console.log('events', events)
23-
24-
for (const command in commands) {
25-
const { slash, data, execute } = commands[command]()
26-
27-
if (slash || data.slash) {
28-
client.commands.set(command, {
29-
data: new SlashCommandBuilder()
30-
.setName(command)
31-
.setDescription(data.description),
32-
slash: slash || data.slash,
33-
execute
34-
})
35-
} else {
36-
client.commands.set(command, {
37-
data,
38-
slash: false,
39-
execute
40-
})
41-
}
42-
}
43-
44-
for (const event in events) {
45-
const { once, data, callback } = events[event]()
46-
47-
if (once || data.once) {
48-
client.once(event, callback)
49-
} else {
50-
client.on(event, callback)
51-
}
52-
}
53-
54-
const rest = new REST().setToken(process.env.CLIENT_TOKEN)
55-
56-
const refreshSlashCommands = async () => {
57-
try {
58-
console.log('Started refreshing application (/) commands.')
59-
60-
const commands = client.commands
61-
.filter((command) => command.slash)
62-
.map(({ data }) => data.toJSON())
63-
64-
await rest.put(Routes.applicationCommands(process.env.CLIENT_ID), {
65-
body: commands
66-
})
67-
68-
console.log('Successfully reloaded application (/) commands.')
69-
} catch (error) {
70-
console.log(error)
71-
}
12+
harmony.initClient()
7213
}
7314

74-
refreshSlashCommands()
75-
76-
client.on('interactionCreate', async (interaction) => {
77-
if (!interaction.isChatInputCommand()) return
78-
const command = interaction.client.commands.get(interaction.commandName)
79-
80-
if (!command || !command.slash) {
81-
console.log(`No command matching ${interaction.commandName} was found.`)
82-
return
83-
}
84-
85-
try {
86-
await command.execute(client, interaction)
87-
} catch (error) {
88-
console.log(error)
89-
if (interaction.deferred || interaction.replied) {
90-
await interaction.followUp({
91-
content: 'There was an error while executing this command!',
92-
ephemeral: true
93-
})
94-
} else {
95-
await interaction.reply({
96-
content: 'There was an error while executing this command!',
97-
ephemeral: true
98-
})
99-
}
100-
}
101-
})
102-
103-
client.on('messageCreate', async (message) => {
104-
const config = await loadHarmonyConfig()
105-
106-
if (message.author.bot) return
107-
if (!message.content.startsWith(config.defaultPrefix)) return
108-
109-
const commandName = message.content.slice(config.defaultPrefix.length)
110-
const command = client.commands.get(commandName)
111-
112-
if (!command || command.slash) return
113-
114-
try {
115-
await command.execute(client, message)
116-
} catch (error) {
117-
console.log(error)
118-
message.reply('There was an error while executing this command!')
119-
}
120-
})
121-
122-
client.login(process.env.CLIENT_TOKEN)
15+
initHarmony()

playground/commands/moderations/ban.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,9 @@ import { defineCommand } from '../../../src'
33
export default defineCommand(
44
{
55
slash: true,
6-
description: 'Ban a user from the server',
7-
args: [
8-
{
9-
name: 'user',
10-
description: 'The user to kick',
11-
type: 6,
12-
required: true
13-
},
14-
{
15-
name: 'reason',
16-
description: 'The reason for banning the user',
17-
type: 3,
18-
required: false
19-
}
20-
]
6+
description: 'Ban a user from the server'
217
},
22-
(_, message) => {
23-
message.reply('Banned user')
8+
(_, interaction) => {
9+
interaction.reply('Banned user')
2410
}
2511
)

playground/commands/moderations/kick.slash.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,7 @@ import { defineCommand } from '../../../src'
22

33
export default defineCommand(
44
{
5-
description: 'Kick a user from the server',
6-
args: [
7-
{
8-
name: 'user',
9-
description: 'The user to kick',
10-
type: 6,
11-
required: true
12-
},
13-
{
14-
name: 'reason',
15-
description: 'The reason for kicking the user',
16-
type: 3,
17-
required: false
18-
}
19-
]
5+
description: 'Kick a user from the server'
206
},
217
(_, message) => {
228
message.reply('Kicked user')

playground/events/ready.once.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineEvent } from '../../src'
22

3-
export default defineEvent({}, (client) => {
3+
export default defineEvent((client) => {
44
console.log(`Ready! Logged in as ${client.user?.tag}`)
55
})

src/commands.ts

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import createJiti from 'jiti'
22
import { filename } from 'pathe/utils'
3-
import { getAllFiles } from './utils'
4-
import type { CommandExecute, CommandOptions, CommandResult } from '../types'
3+
import type { CommandExecute, CommandResult } from '../types'
4+
import { dirname } from 'pathe'
55

66
const jiti = createJiti(undefined as unknown as string, {
77
interopDefault: true,
@@ -10,34 +10,77 @@ const jiti = createJiti(undefined as unknown as string, {
1010
extensions: ['.ts', '.js']
1111
})
1212

13-
const commandsPath = getAllFiles('./playground/commands')
14-
15-
const parseFileName = (fileName: string) => {
16-
const parts = fileName.split('.')
17-
const name = parts[0]
18-
const hasSlash = parts[parts.length - 1] === 'slash'
13+
interface CommandContext {
14+
name: string
15+
category: string
16+
slash: boolean
17+
execute: CommandExecute<boolean>
18+
}
1919

20-
return [name, hasSlash]
20+
interface CommandOptions {
21+
name?: string
22+
description?: string
23+
category?: string
24+
arguments?: any[]
25+
nsfw?: boolean
26+
slash?: boolean
27+
ownerOnly?: boolean
28+
guildOnly?: boolean
29+
userPermissions?: any[]
30+
botPermissions?: any[]
31+
cooldown?: number
2132
}
2233

23-
export const loadCommands = () => {
24-
const commands = Object.fromEntries(
25-
commandsPath.map((path) => {
26-
const [name, hasSlash] = parseFileName(filename(path))!
34+
export class Command {
35+
public name: string
36+
public description: string
37+
public category: string
38+
public arguments: any[]
39+
public nsfw: boolean
40+
public slash: boolean
41+
public ownerOnly: boolean
42+
public guildOnly: boolean
43+
public userPermissions: any[]
44+
public botPermissions: any[]
45+
public cooldown: number
46+
47+
public execute: CommandExecute<boolean>
48+
49+
constructor(context: CommandContext, options: CommandOptions = {}) {
50+
this.name = options.name ?? context.name
51+
this.description = options.description ?? ''
52+
this.category = options.category ?? context.category
53+
this.arguments = options.arguments ?? []
54+
this.nsfw = options.nsfw ?? false
55+
this.slash = options.slash || context.slash
56+
this.ownerOnly = options.ownerOnly ?? false
57+
this.guildOnly = options.guildOnly ?? false
58+
this.userPermissions = options.userPermissions ?? []
59+
this.botPermissions = options.botPermissions ?? []
60+
this.cooldown = options.cooldown ?? 0
2761

28-
return [name, () => ({ slash: hasSlash, ...jiti(path) })]
29-
})
30-
) as Record<string, () => { slash: boolean } & CommandResult>
62+
this.execute = context.execute
63+
}
64+
}
65+
66+
export const getCommand = (path: string) => {
67+
const command = jiti(path) as CommandResult<boolean>
68+
const context: CommandContext = {
69+
name: filename(path).split('.')[0],
70+
category: filename(dirname(path)),
71+
slash: filename(path).endsWith('.slash'),
72+
execute: command.execute
73+
}
3174

32-
return commands
75+
return new Command(context, command.options)
3376
}
3477

35-
export const defineCommand = (
36-
options: CommandOptions,
37-
execute: CommandExecute
38-
): CommandResult => {
78+
export const defineCommand = <Slash extends boolean>(
79+
options: CommandOptions & { slash?: Slash },
80+
execute: CommandExecute<Slash>
81+
): CommandResult<Slash> => {
3982
return {
40-
data: options,
83+
options,
4184
execute
4285
}
4386
}

src/config.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1-
import { loadConfig } from 'c12'
2-
import { resolve } from 'pathe'
1+
import { LoadConfigOptions, loadConfig } from 'c12'
32
import { defu } from 'defu'
43
import type { HarmonyConfig } from '../types'
54

6-
export const loadHarmonyConfig = async () => {
5+
export interface LoadHarmonyConfigOptions
6+
extends LoadConfigOptions<HarmonyConfig> {}
7+
8+
export const loadHarmonyConfig = async (opts: LoadHarmonyConfigOptions) => {
79
const { config } = await loadConfig<HarmonyConfig>({
810
name: 'harmony',
911
configFile: 'harmony.config',
1012
rcFile: '.harmonyrc',
1113
dotenv: true,
1214
globalRc: true,
13-
cwd: resolve('./playground')
15+
...opts
1416
})
1517

1618
return defu(config, {
17-
defaultPrefix: '!'
19+
defaultPrefix: '!',
20+
dir: {
21+
commands: './commands',
22+
events: './events'
23+
}
1824
})
1925
}
2026

0 commit comments

Comments
 (0)