Skip to content

Commit

Permalink
feat: add context menu and improve events types
Browse files Browse the repository at this point in the history
  • Loading branch information
nethriis committed May 12, 2024
1 parent 57d7074 commit 2896dfd
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 45 deletions.
25 changes: 22 additions & 3 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import jiti from 'jiti'
import { dirname } from 'pathe'
import { filename } from 'pathe/utils'
import { SlashCommandBuilder } from 'discord.js'
import {
ApplicationCommandType,
ContextMenuCommandBuilder,
SlashCommandBuilder
} from 'discord.js'
import {
CommandArgType,
type HarmonixCommandArgType,
Expand All @@ -10,7 +14,8 @@ import {
type Harmonix,
type HarmonixCommand,
type HarmonixCommandInput,
type CommandArg
type CommandArg,
HarmonixContextMenu
} from './types'

export const resolveHarmonixCommand = (
Expand Down Expand Up @@ -43,7 +48,7 @@ export const defineCommand = <Slash extends boolean, Args extends CommandArg[]>(
return { options, execute }
}

export const toJSON = (cmd: HarmonixCommand<true, CommandArg[]>) => {
export const slashToJSON = (cmd: HarmonixCommand<true, CommandArg[]>) => {
const builder = new SlashCommandBuilder()
.setName(cmd.options.name!)
.setDescription(cmd.options.description || 'No description provided')
Expand Down Expand Up @@ -114,6 +119,20 @@ export const toJSON = (cmd: HarmonixCommand<true, CommandArg[]>) => {
return builder.toJSON()
}

export const contextMenuToJSON = (ctm: HarmonixContextMenu) => {
const builder = new ContextMenuCommandBuilder()
.setName(ctm.options.name!)
.setType(
ctm.options.type === 'message'
? ApplicationCommandType.Message
: ctm.options.type === 'user'
? ApplicationCommandType.User
: ApplicationCommandType.Message
)

return builder.toJSON()
}

export const defineArgument = <
Type extends keyof HarmonixCommandArgType
>(options: {
Expand Down
56 changes: 56 additions & 0 deletions src/contextMenus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import jiti from 'jiti'
import type {
ContextMenuCallback,
ContextMenuOptions,
DefineContextMenu,
DefineContextMenuWithOptions,
Harmonix,
HarmonixContextMenu,
HarmonixContextMenuInput
} from './types'
import { filename } from 'pathe/utils'

export const resolveHarmonixContextMenu = (
ctm: HarmonixContextMenuInput,
harmonixOptions: Harmonix['options']
): HarmonixContextMenu => {
if (typeof ctm === 'string') {
const _jiti = jiti(harmonixOptions.rootDir, {
interopDefault: true
})
const _ctmPath = _jiti.resolve(ctm)
const contextMenu = _jiti(_ctmPath) as HarmonixContextMenu
const options: ContextMenuOptions = {
name: contextMenu.options.name || filename(_ctmPath).split('.')[0],
type: contextMenu.options.type || 'message'
}

return { options, callback: contextMenu.callback }
} else {
return ctm
}
}

export const defineContextMenu: DefineContextMenu &
DefineContextMenuWithOptions = (
...args: [ContextMenuOptions | ContextMenuCallback, ContextMenuCallback?]
): HarmonixContextMenu => {
let options: ContextMenuOptions = {}

if (args.length === 1) {
const [callback] = args as [ContextMenuCallback]

return {
options,
callback
}
} else {
const [opts, callback] = args as [ContextMenuOptions, ContextMenuCallback]

options = opts
return {
options,
callback
}
}
}
97 changes: 69 additions & 28 deletions src/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import {
type User,
ApplicationCommandOptionType
} from 'discord.js'
import consola from 'consola'
import {
type CommandArg,
CommandArgType,
type Harmonix,
type HarmonixCommand,
type HarmonixEvent,
type MessageOrInteraction
type MessageOrInteraction,
HarmonixContextMenu
} from './types'
import 'dotenv/config'
import { toJSON } from './commands'
import { slashToJSON, contextMenuToJSON } from './commands'

export const initCient = (harmonixOptions: Harmonix['options']) => {
const client = new Client({ intents: harmonixOptions.intents })
Expand All @@ -25,6 +27,51 @@ export const initCient = (harmonixOptions: Harmonix['options']) => {
return client
}

export const refreshApplicationCommands = async (
harmonix: Harmonix,
commands: (HarmonixCommand<true, CommandArg[]> | HarmonixContextMenu)[]
) => {
if (commands.length === 0) return
const rest = new REST().setToken(process.env.HARMONIX_CLIENT_TOKEN!)

try {
consola.info('Started refreshing application commands.')
await rest.put(
Routes.applicationCommands(
harmonix.options.clientId || process.env.HARMONIX_CLIENT_ID!
),
{
body: commands.map((cmd) =>
isHarmonixCommand(cmd) ? slashToJSON(cmd) : contextMenuToJSON(cmd)
)
}
)
consola.success('Successfully reloaded application commands.')
} catch {
consola.error('Failed to reload application commands.')
}
}

export const registerEvents = (harmonix: Harmonix, events: HarmonixEvent[]) => {
for (const event of events.filter((evt) => !evt.options.type)) {
if (event.options.once) {
harmonix.client?.once(event.options.name!, event.callback)
} else {
harmonix.client?.on(event.options.name!, event.callback)
}
}

harmonix.client?.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isModalSubmit()) return
const event = events
.filter((evt) => evt.options.type === 'modal')
.find((evt) => evt.options.name === interaction.customId)

if (!event) return
event.callback(interaction)
})
}

export const registerCommands = (
harmonix: Harmonix,
commands: HarmonixCommand<false, CommandArg[]>[]
Expand All @@ -50,19 +97,10 @@ export const registerCommands = (
})
}

export const registerSlashCommands = async (
export const registerSlashCommands = (
harmonix: Harmonix,
commands: HarmonixCommand<true, CommandArg[]>[]
) => {
if (commands.length === 0) return
const rest = new REST().setToken(process.env.HARMONIX_CLIENT_TOKEN!)

await rest.put(
Routes.applicationCommands(
harmonix.options.clientId || process.env.HARMONIX_CLIENT_ID!
),
{ body: commands.map((cmd) => toJSON(cmd)) }
)
harmonix.client?.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return
const cmd = commands.find(
Expand All @@ -89,26 +127,29 @@ export const registerSlashCommands = async (
})
}

export const registerEvents = (harmonix: Harmonix, events: HarmonixEvent[]) => {
for (const event of events.filter((evt) => !evt.options.type)) {
if (event.options.once) {
harmonix.client?.once(event.options.name!, event.callback)
} else {
harmonix.client?.on(event.options.name!, event.callback)
}
}

harmonix.client?.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isModalSubmit()) return
const event = events
.filter((evt) => evt.options.type === 'modal')
.find((evt) => evt.options.name === interaction.customId)
export const registerContextMenu = (
harmonix: Harmonix,
contextMenus: HarmonixContextMenu[]
) => {
harmonix.client?.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isContextMenuCommand()) return
const ctm = contextMenus.find(
(ctm) => ctm.options.name === interaction.commandName
)

if (!event) return
event.callback(interaction)
if (!ctm) return
ctm.callback(interaction)
})
}

const isHarmonixCommand = (
command: HarmonixCommand<true, CommandArg[]> | HarmonixContextMenu
): command is HarmonixCommand<true, CommandArg[]> => {
return (
(command as HarmonixCommand<true, CommandArg[]>).options.slash !== undefined
)
}

export const resolveArgument = async (
entity: MessageOrInteraction,
type: CommandArgType,
Expand Down
16 changes: 11 additions & 5 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type {
EventOptions,
Harmonix,
HarmonixEvent,
HarmonixEventInput
HarmonixEventInput,
HarmonixEvents
} from './types'

export const resolveHarmonixEvent = (
Expand Down Expand Up @@ -36,20 +37,25 @@ export const resolveHarmonixEvent = (
}
}

export const defineEvent: DefineEvent & DefineEventWithOptions = (
...args: [EventOptions | EventCallback, EventCallback?]
export const defineEvent: DefineEvent & DefineEventWithOptions = <
Event extends keyof HarmonixEvents = any
>(
...args: [EventOptions | EventCallback<Event>, EventCallback<Event>?]
): HarmonixEvent => {
let options: EventOptions = {}

if (args.length === 1) {
const [callback] = args as [EventCallback]
const [callback] = args as [EventCallback<keyof HarmonixEvents>]

return {
options,
callback
}
} else {
const [opts, callback] = args as [EventOptions, EventCallback]
const [opts, callback] = args as [
EventOptions,
EventCallback<keyof HarmonixEvents>
]

options = opts
return {
Expand Down
23 changes: 20 additions & 3 deletions src/harmonix.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { LoadConfigOptions } from 'c12'
import { loadOptions } from './options'
import { scanCommands, scanEvents } from './scan'
import { scanCommands, scanContextMenus, scanEvents } from './scan'
import { resolveHarmonixCommand } from './commands'
import { resolveHarmonixEvent } from './events'
import { resolveHarmonixContextMenu } from './contextMenus'
import {
initCient,
refreshApplicationCommands,
registerCommands,
registerContextMenu,
registerEvents,
registerSlashCommands
} from './discord'
Expand All @@ -32,6 +35,15 @@ export const createHarmonix = async (
resolveHarmonixEvent(evt, harmonix.options)
)

const scannedContextMenus = await scanContextMenus(harmonix)
const _contextMenus = [
...(harmonix.options.contextMenus || []),
...scannedContextMenus
]
const contextMenus = _contextMenus.map((ctm) =>
resolveHarmonixContextMenu(ctm, harmonix.options)
)

if (!process.env.HARMONIX_CLIENT_TOKEN) {
throw new Error(
'Client token is required. Please provide it in the environment variable HARMONIX_CLIENT_TOKEN.'
Expand All @@ -43,15 +55,20 @@ export const createHarmonix = async (
)
}
harmonix.client = initCient(harmonix.options)
refreshApplicationCommands(harmonix, [
...commands.filter((cmd) => cmd.options.slash),
...contextMenus
])
registerEvents(harmonix, events)
registerCommands(
harmonix,
commands.filter((cmd) => !cmd.options.slash)
)
await registerSlashCommands(
registerSlashCommands(
harmonix,
commands.filter((cmd) => cmd.options.slash)
)
registerEvents(harmonix, events)
registerContextMenu(harmonix, contextMenus)

return harmonix
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './events'
export * from './commands'
export * from './contextMenus'
export * from './modals'
export * from './options'
export * from './harmonix'
Expand Down
14 changes: 13 additions & 1 deletion src/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ export const scanCommands = async (harmonix: Harmonix) => {
}

export const scanEvents = async (harmonix: Harmonix) => {
const files = await scanFiles(harmonix, 'events')
const files = await Promise.all([
scanFiles(harmonix, 'events'),
scanFiles(harmonix, 'listeners')
]).then((r) => r.flat())

return files.map((f) => f.fullPath)
}

export const scanContextMenus = async (harmonix: Harmonix) => {
const files = await Promise.all([
scanFiles(harmonix, 'context-menus'),
scanFiles(harmonix, 'contextMenus')
]).then((r) => r.flat())

return files.map((f) => f.fullPath)
}
Expand Down
Loading

0 comments on commit 2896dfd

Please sign in to comment.