nv-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +0 -0
  2. package/package.json +16 -0
  3. package/src/command-handler/ChannelCommands.js +52 -0
  4. package/src/command-handler/Command.js +21 -0
  5. package/src/command-handler/CommandHandler.js +219 -0
  6. package/src/command-handler/CustomCommands.js +74 -0
  7. package/src/command-handler/DisabledCommands.js +50 -0
  8. package/src/command-handler/PrefixHandler.js +50 -0
  9. package/src/command-handler/SlashCommands.js +111 -0
  10. package/src/command-handler/commands/channelcommand.js +69 -0
  11. package/src/command-handler/commands/customcommand.js +28 -0
  12. package/src/command-handler/commands/delcustomcmd.js +21 -0
  13. package/src/command-handler/commands/prefix.js +21 -0
  14. package/src/command-handler/commands/requiredpermissions.js +110 -0
  15. package/src/command-handler/commands/requiredroles.js +112 -0
  16. package/src/command-handler/commands/togglecommand.js +42 -0
  17. package/src/command-handler/validations/run-time/argument-count.js +24 -0
  18. package/src/command-handler/validations/run-time/channel-command.js +26 -0
  19. package/src/command-handler/validations/run-time/disabled-commands.js +21 -0
  20. package/src/command-handler/validations/run-time/guild-only.js +15 -0
  21. package/src/command-handler/validations/run-time/has-permissions.js +50 -0
  22. package/src/command-handler/validations/run-time/has-roles.js +43 -0
  23. package/src/command-handler/validations/run-time/owner-only.js +12 -0
  24. package/src/command-handler/validations/run-time/test-only.js +10 -0
  25. package/src/command-handler/validations/runtime/argument-count.js +24 -0
  26. package/src/command-handler/validations/runtime/channel-command.js +26 -0
  27. package/src/command-handler/validations/runtime/disabled-commands.js +21 -0
  28. package/src/command-handler/validations/runtime/guild-only.js +15 -0
  29. package/src/command-handler/validations/runtime/has-permissions.js +50 -0
  30. package/src/command-handler/validations/runtime/has-roles.js +43 -0
  31. package/src/command-handler/validations/runtime/owner-only.js +12 -0
  32. package/src/command-handler/validations/runtime/test-only.js +10 -0
  33. package/src/command-handler/validations/syntax/bad-cooldown-types.js +28 -0
  34. package/src/command-handler/validations/syntax/callback-required.js +9 -0
  35. package/src/command-handler/validations/syntax/defer-reply.js +14 -0
  36. package/src/command-handler/validations/syntax/desc-required-for-slash.js +11 -0
  37. package/src/command-handler/validations/syntax/owner-only-without-owners.js +11 -0
  38. package/src/command-handler/validations/syntax/permissions-without-guild-only.js +10 -0
  39. package/src/command-handler/validations/syntax/test-without-server.js +11 -0
  40. package/src/event-handler/EventHandler.js +87 -0
  41. package/src/event-handler/events/interactionCreate/isButton/test.js +3 -0
  42. package/src/event-handler/events/interactionCreate/isCommand/slash-commands.js +77 -0
  43. package/src/event-handler/events/messageCreate/isHuman/legacy-commands.js +36 -0
  44. package/src/index.js +90 -0
  45. package/src/models/channel-commands-schema.js +16 -0
  46. package/src/models/cooldown-schema.js +16 -0
  47. package/src/models/custom-command-schema.js +16 -0
  48. package/src/models/disabled-commands-schema.js +12 -0
  49. package/src/models/guild-prefix-schema.js +16 -0
  50. package/src/models/required-permissions-schema.js +16 -0
  51. package/src/models/required-roles-schema.js +16 -0
  52. package/src/util/Cooldowns.js +232 -0
  53. package/src/util/FeatureHandler.js +20 -0
  54. package/src/util/get-all-files.js +28 -0
@@ -0,0 +1,43 @@
1
+ const requiredRoles = require('../../../models/required-roles-schema')
2
+
3
+ module.exports = async (command, usage) => {
4
+ const { guild, member, message, interaction } = usage
5
+
6
+ if (!member) {
7
+ return true
8
+ }
9
+
10
+ const _id = `${guild.id}-${command.commandName}`
11
+ const document = await requiredRoles.findById(_id)
12
+
13
+ if (document) {
14
+ let hasRole = false
15
+
16
+ for (const roleId of document.roles) {
17
+ if (member.roles.cache.has(roleId)) {
18
+ hasRole = true
19
+ break
20
+ }
21
+ }
22
+
23
+ if (hasRole) {
24
+ return true
25
+ }
26
+
27
+ const reply = {
28
+ content: `You need one of these roles: ${document.roles.map(
29
+ (roleId) => `<@&${roleId}>`
30
+ )}`,
31
+ allowedMentions: {
32
+ roles: [],
33
+ },
34
+ }
35
+
36
+ if (message) message.reply(reply)
37
+ else if (interaction) interaction.reply(reply)
38
+
39
+ return false
40
+ }
41
+
42
+ return true
43
+ }
@@ -0,0 +1,12 @@
1
+ module.exports = (command, usage) => {
2
+ const { instance, commandObject } = command
3
+ const { botOwners } = instance
4
+ const { ownerOnly } = commandObject
5
+ const { user } = usage
6
+
7
+ if (ownerOnly === true && !botOwners.includes(user.id)) {
8
+ return false
9
+ }
10
+
11
+ return true
12
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = (command, usage) => {
2
+ const { instance, commandObject } = command
3
+ const { guild } = usage
4
+
5
+ if (commandObject.testOnly !== true) {
6
+ return true
7
+ }
8
+
9
+ return instance.testServers.includes(guild?.id)
10
+ }
@@ -0,0 +1,28 @@
1
+ const { cooldownTypes } = require('../../../util/Cooldowns')
2
+
3
+ module.exports = (command) => {
4
+ const { commandObject, commandName } = command
5
+
6
+ if (!commandObject.cooldowns) {
7
+ return
8
+ }
9
+
10
+ let counter = 0
11
+ for (const type of cooldownTypes) {
12
+ if (commandObject.cooldowns[type]) {
13
+ ++counter
14
+ }
15
+ }
16
+
17
+ if (counter === 0) {
18
+ throw new Error(
19
+ `Command "${commandName}" does have a cooldown object, but no cooldown types were specified. Please use one of the following: ${cooldownTypes}`
20
+ )
21
+ }
22
+
23
+ if (counter > 1) {
24
+ throw new Error(
25
+ `Command "${commandName}" has multiple cooldown types, you must specify only one.`
26
+ )
27
+ }
28
+ }
@@ -0,0 +1,9 @@
1
+ module.exports = (command) => {
2
+ const { commandObject, commandName } = command
3
+
4
+ if (!commandObject.callback) {
5
+ throw new Error(
6
+ `Command "${commandName}" does not have a callback function.`
7
+ )
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ module.exports = (command) => {
2
+ const { commandObject, commandName } = command
3
+ const { deferReply } = commandObject
4
+
5
+ if (
6
+ deferReply &&
7
+ typeof deferReply !== 'boolean' &&
8
+ deferReply !== 'ephemeral'
9
+ ) {
10
+ throw new Error(
11
+ `Command "${commandName}" does not have a valid value for "deferReply". Please use a boolean value or the string "ephemeral".`
12
+ )
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = (command) => {
2
+ const { commandName, commandObject } = command
3
+
4
+ if (commandObject.slash === true || commandObject.slash === 'both') {
5
+ if (!commandObject.description) {
6
+ throw new Error(
7
+ `Command "${commandName}" is a slash command but does not have a description`
8
+ )
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = (command) => {
2
+ const { instance, commandName, commandObject } = command
3
+
4
+ if (commandObject.ownerOnly !== true || instance.botOwners.length) {
5
+ return
6
+ }
7
+
8
+ throw new Error(
9
+ `Command "${commandName}" is a owner only command, but no owners were specified.`
10
+ )
11
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = (command) => {
2
+ const { commandObject, commandName } = command
3
+ const { guildOnly, permissions = [] } = commandObject
4
+
5
+ if (guildOnly !== true && permissions.length) {
6
+ throw new Error(
7
+ `Command "${commandName}" is not a guildOnly command, but permissions are specified.`
8
+ )
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = (command) => {
2
+ const { instance, commandName, commandObject } = command
3
+
4
+ if (commandObject.testOnly !== true || instance.testServers.length) {
5
+ return
6
+ }
7
+
8
+ throw new Error(
9
+ `Command "${commandName}" is a test only command, but no test servers were specified.`
10
+ )
11
+ }
@@ -0,0 +1,87 @@
1
+ const { InteractionType } = require('discord.js')
2
+ const path = require('path')
3
+
4
+ const getAllFiles = require('../util/get-all-files')
5
+
6
+ class EventHandler {
7
+ // <eventName, array of [function, dynamic validation functions]>
8
+ _eventCallbacks = new Map()
9
+
10
+ constructor(instance, events, client) {
11
+ this._instance = instance
12
+ this._eventsDir = events?.dir
13
+ this._client = client
14
+
15
+ delete events.dir
16
+ this._events = events
17
+
18
+ this._builtInEvents = {
19
+ interactionCreate: {
20
+ isButton: (interaction) => interaction.isButton(),
21
+ isCommand: (interaction) =>
22
+ interaction.type === InteractionType.ApplicationCommand || interaction.type === InteractionType.ApplicationCommandAutocomplete,
23
+ },
24
+ messageCreate: {
25
+ isHuman: (message) => !message.author.bot,
26
+ },
27
+ }
28
+
29
+ this.readFiles()
30
+ this.registerEvents()
31
+ }
32
+
33
+ async readFiles() {
34
+ const defaultEvents = getAllFiles(path.join(__dirname, 'events'), true)
35
+ const folders = this._eventsDir ? getAllFiles(this._eventsDir, true) : []
36
+
37
+ for (const folderPath of [...defaultEvents, ...folders]) {
38
+ const event = folderPath.split(/[\/\\]/g).pop()
39
+ const files = getAllFiles(folderPath)
40
+
41
+ const functions = this._eventCallbacks.get(event) || []
42
+
43
+ for (const file of files) {
44
+ const isBuiltIn = !folderPath.includes(this._eventsDir)
45
+ const func = require(file)
46
+ const result = [func]
47
+
48
+ const split = file.split(event)[1].split(/[\/\\]/g)
49
+ const methodName = split[split.length - 2]
50
+
51
+ if (
52
+ isBuiltIn &&
53
+ this._builtInEvents[event] &&
54
+ this._builtInEvents[event][methodName]
55
+ ) {
56
+ result.push(this._builtInEvents[event][methodName])
57
+ } else if (this._events[event] && this._events[event][methodName]) {
58
+ result.push(this._events[event][methodName])
59
+ }
60
+
61
+ functions.push(result)
62
+ }
63
+
64
+ this._eventCallbacks.set(event, functions)
65
+ }
66
+ }
67
+
68
+ registerEvents() {
69
+ const instance = this._instance
70
+
71
+ for (const eventName of this._eventCallbacks.keys()) {
72
+ const functions = this._eventCallbacks.get(eventName)
73
+
74
+ this._client.on(eventName, async function () {
75
+ for (const [func, dynamicValidation] of functions) {
76
+ if (dynamicValidation && !(await dynamicValidation(...arguments))) {
77
+ continue
78
+ }
79
+
80
+ func(...arguments, instance)
81
+ }
82
+ })
83
+ }
84
+ }
85
+ }
86
+
87
+ module.exports = EventHandler
@@ -0,0 +1,3 @@
1
+ module.exports = (interaction) => {
2
+ interaction.reply('testing')
3
+ }
@@ -0,0 +1,77 @@
1
+ const { InteractionType } = require('discord.js')
2
+
3
+ const handleAutocomplete = async (interaction, commands) => {
4
+ const command = commands.get(interaction.commandName)
5
+ if (!command) {
6
+ return
7
+ }
8
+
9
+ const { autocomplete } = command.commandObject
10
+ if (!autocomplete) {
11
+ return
12
+ }
13
+
14
+ const focusedOption = interaction.options.getFocused(true)
15
+ const choices = await autocomplete(interaction, command, focusedOption.name)
16
+
17
+ const filtered = choices
18
+ .filter((choice) =>
19
+ choice.toLowerCase().startsWith(focusedOption.value.toLowerCase())
20
+ )
21
+ .slice(0, 25)
22
+
23
+ await interaction.respond(
24
+ filtered.map((choice) => ({
25
+ name: choice,
26
+ value: choice,
27
+ }))
28
+ )
29
+ }
30
+
31
+ module.exports = async (interaction, instance) => {
32
+ const { commandHandler } = instance
33
+ const { commands, customCommands } = commandHandler
34
+
35
+ if (interaction.type === InteractionType.ApplicationCommandAutocomplete) {
36
+ handleAutocomplete(interaction, commands)
37
+ return
38
+ }
39
+
40
+ if (interaction.type !== InteractionType.ApplicationCommand) {
41
+ return
42
+ }
43
+
44
+ const args = interaction.options.data.map(({ value }) => {
45
+ return String(value)
46
+ })
47
+
48
+ const command = commands.get(interaction.commandName)
49
+ if (!command) {
50
+ customCommands.run(interaction.commandName, null, interaction)
51
+ return
52
+ }
53
+
54
+ const { deferReply } = command.commandObject
55
+
56
+ if (deferReply) {
57
+ await interaction.deferReply({
58
+ ephemeral: deferReply === 'ephemeral',
59
+ })
60
+ }
61
+
62
+ const response = await commandHandler.runCommand(
63
+ command,
64
+ args,
65
+ null,
66
+ interaction
67
+ )
68
+ if (!response) {
69
+ return
70
+ }
71
+
72
+ if (deferReply) {
73
+ interaction.editReply(response).catch(() => {})
74
+ } else {
75
+ interaction.reply(response).catch(() => {})
76
+ }
77
+ }
@@ -0,0 +1,36 @@
1
+ module.exports = async (message, instance) => {
2
+ const { guild, content } = message
3
+ const { commandHandler } = instance
4
+ const { prefixHandler, commands, customCommands } = commandHandler
5
+
6
+ const prefix = prefixHandler.get(guild?.id)
7
+ if (!content.startsWith(prefix)) {
8
+ return
9
+ }
10
+
11
+ const args = content.split(/\s+/)
12
+ const commandName = args.shift().substring(prefix.length).toLowerCase()
13
+
14
+ const command = commands.get(commandName)
15
+ if (!command) {
16
+ customCommands.run(commandName, message)
17
+ return
18
+ }
19
+
20
+ const { reply, deferReply } = command.commandObject
21
+
22
+ if (deferReply) {
23
+ message.channel.sendTyping()
24
+ }
25
+
26
+ const response = await commandHandler.runCommand(command, args, message)
27
+ if (!response) {
28
+ return
29
+ }
30
+
31
+ if (reply) {
32
+ message.reply(response).catch(() => {})
33
+ } else {
34
+ message.channel.send(response).catch(() => {})
35
+ }
36
+ }
package/src/index.js ADDED
@@ -0,0 +1,90 @@
1
+ const mongoose = require('mongoose')
2
+
3
+ const CommandHandler = require('./command-handler/CommandHandler')
4
+ const Cooldowns = require('./util/Cooldowns')
5
+ const EventHandler = require('./event-handler/EventHandler')
6
+ const FeatureHandler = require('./util/FeatureHandler')
7
+
8
+ class Main {
9
+ constructor(obj) {
10
+ this.init(obj)
11
+ }
12
+
13
+ async init({
14
+ client,
15
+ mongoUri,
16
+ commandsDir,
17
+ featuresDir,
18
+ testServers = [],
19
+ botOwners = [],
20
+ cooldownConfig = {},
21
+ disabledDefaultCommands = [],
22
+ events = {},
23
+ validations = {},
24
+ }) {
25
+ if (!client) {
26
+ throw new Error('A client is required.')
27
+ }
28
+
29
+ this._testServers = testServers
30
+ this._botOwners = botOwners
31
+ this._cooldowns = new Cooldowns({
32
+ instance: this,
33
+ ...cooldownConfig,
34
+ })
35
+ this._disabledDefaultCommands = disabledDefaultCommands.map((cmd) =>
36
+ cmd.toLowerCase()
37
+ )
38
+ this._validations = validations
39
+
40
+ if (mongoUri) {
41
+ await this.connectToMongo(mongoUri)
42
+ }
43
+
44
+ if (commandsDir) {
45
+ this._commandHandler = new CommandHandler(this, commandsDir, client)
46
+ }
47
+
48
+ if (featuresDir) {
49
+ new FeatureHandler(this, featuresDir, client)
50
+ }
51
+
52
+ this._eventHandler = new EventHandler(this, events, client)
53
+ }
54
+
55
+ get testServers() {
56
+ return this._testServers
57
+ }
58
+
59
+ get botOwners() {
60
+ return this._botOwners
61
+ }
62
+
63
+ get cooldowns() {
64
+ return this._cooldowns
65
+ }
66
+
67
+ get disabledDefaultCommands() {
68
+ return this._disabledDefaultCommands
69
+ }
70
+
71
+ get commandHandler() {
72
+ return this._commandHandler
73
+ }
74
+
75
+ get eventHandler() {
76
+ return this._eventHandler
77
+ }
78
+
79
+ get validations() {
80
+ return this._validations
81
+ }
82
+
83
+ async connectToMongo(mongoUri) {
84
+ await mongoose.connect(mongoUri, {
85
+ keepAlive: true,
86
+ })
87
+ }
88
+ }
89
+
90
+ module.exports = Main
@@ -0,0 +1,16 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const channelCommandSchema = new Schema({
4
+ // guildId-commandName
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ channels: {
10
+ type: [String],
11
+ required: true,
12
+ },
13
+ })
14
+
15
+ const name = 'channel-commands'
16
+ module.exports = models[name] || model(name, channelCommandSchema)
@@ -0,0 +1,16 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const cooldownSchema = new Schema({
4
+ // The key from Cooldowns.getKey()
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ expires: {
10
+ type: Date,
11
+ required: true,
12
+ },
13
+ })
14
+
15
+ const name = 'cooldowns'
16
+ module.exports = models[name] || model(name, cooldownSchema)
@@ -0,0 +1,16 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const customCommandSchema = new Schema({
4
+ // guildId-commandName
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ response: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ })
14
+
15
+ const name = 'custom-commands'
16
+ module.exports = models[name] || model(name, customCommandSchema)
@@ -0,0 +1,12 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const disabledCommandSchema = new Schema({
4
+ // guildId-commandName
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ })
10
+
11
+ const name = 'disabled-commands'
12
+ module.exports = models[name] || model(name, disabledCommandSchema)
@@ -0,0 +1,16 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const guildPrefixSchema = new Schema({
4
+ // guild ID
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ prefix: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ })
14
+
15
+ const name = 'guild-prefixes'
16
+ module.exports = models[name] || model(name, guildPrefixSchema)
@@ -0,0 +1,16 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const requiredPermissionsSchema = new Schema({
4
+ // guildId-commandName
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ permissions: {
10
+ type: [String],
11
+ required: true,
12
+ },
13
+ })
14
+
15
+ const name = 'required-permissions'
16
+ module.exports = models[name] || model(name, requiredPermissionsSchema)
@@ -0,0 +1,16 @@
1
+ const { Schema, model, models } = require('mongoose')
2
+
3
+ const requiredRolesSchema = new Schema({
4
+ // guildId-commandName
5
+ _id: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ roles: {
10
+ type: [String],
11
+ required: true,
12
+ },
13
+ })
14
+
15
+ const name = 'required-roles'
16
+ module.exports = models[name] || model(name, requiredRolesSchema)