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
package/README.md ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "nv-kit",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "discord.js": "^14.0.2",
14
+ "mongoose": "^6.3.5"
15
+ }
16
+ }
@@ -0,0 +1,52 @@
1
+ const channelCommands = require('../models/channel-commands-schema')
2
+
3
+ class ChannelCommands {
4
+ // `${guildId}-${commandName}`: [channelIds]
5
+ _channelCommands = new Map()
6
+
7
+ async action(action, guildId, commandName, channelId) {
8
+ const _id = `${guildId}-${commandName}`
9
+
10
+ const result = await channelCommands.findOneAndUpdate(
11
+ {
12
+ _id,
13
+ },
14
+ {
15
+ _id,
16
+ [action === 'add' ? '$addToSet' : '$pull']: {
17
+ channels: channelId,
18
+ },
19
+ },
20
+ {
21
+ upsert: true,
22
+ new: true,
23
+ }
24
+ )
25
+
26
+ this._channelCommands.set(_id, result.channels)
27
+ return result.channels
28
+ }
29
+
30
+ async add(guildId, commandName, channelId) {
31
+ return await this.action('add', guildId, commandName, channelId)
32
+ }
33
+
34
+ async remove(guildId, commandName, channelId) {
35
+ return await this.action('remove', guildId, commandName, channelId)
36
+ }
37
+
38
+ async getAvailableChannels(guildId, commandName) {
39
+ const _id = `${guildId}-${commandName}`
40
+ let channels = this._channelCommands.get(_id)
41
+
42
+ if (!channels) {
43
+ const results = await channelCommands.findById(_id)
44
+ channels = results ? results.channels : []
45
+ this._channelCommands.set(_id, channels)
46
+ }
47
+
48
+ return channels
49
+ }
50
+ }
51
+
52
+ module.exports = ChannelCommands
@@ -0,0 +1,21 @@
1
+ class Command {
2
+ constructor(instance, commandName, commandObject) {
3
+ this._instance = instance
4
+ this._commandName = commandName.toLowerCase()
5
+ this._commandObject = commandObject
6
+ }
7
+
8
+ get instance() {
9
+ return this._instance
10
+ }
11
+
12
+ get commandName() {
13
+ return this._commandName
14
+ }
15
+
16
+ get commandObject() {
17
+ return this._commandObject
18
+ }
19
+ }
20
+
21
+ module.exports = Command
@@ -0,0 +1,219 @@
1
+ const path = require('path')
2
+
3
+ const getAllFiles = require('../util/get-all-files')
4
+ const Command = require('./Command')
5
+ const SlashCommands = require('./SlashCommands')
6
+ const { cooldownTypes } = require('../util/Cooldowns')
7
+ const ChannelCommands = require('./ChannelCommands')
8
+ const CustomCommands = require('./CustomCommands')
9
+ const DisabledCommands = require('./DisabledCommands')
10
+ const PrefixHandler = require('./PrefixHandler')
11
+
12
+ class CommandHandler {
13
+ // <commandName, instance of the Command class>
14
+ _commands = new Map()
15
+ _validations = this.getValidations(
16
+ path.join(__dirname, 'validations', 'runtime')
17
+ )
18
+ _channelCommands = new ChannelCommands()
19
+ _customCommands = new CustomCommands(this)
20
+ _disabledCommands = new DisabledCommands()
21
+ _prefixes = new PrefixHandler()
22
+
23
+ constructor(instance, commandsDir, client) {
24
+ this._instance = instance
25
+ this._commandsDir = commandsDir
26
+ this._slashCommands = new SlashCommands(client)
27
+ this._client = client
28
+
29
+ this._validations = [
30
+ ...this._validations,
31
+ ...this.getValidations(instance.validations?.runtime),
32
+ ]
33
+
34
+ this.readFiles()
35
+ }
36
+
37
+ get commands() {
38
+ return this._commands
39
+ }
40
+
41
+ get channelCommands() {
42
+ return this._channelCommands
43
+ }
44
+
45
+ get slashCommands() {
46
+ return this._slashCommands
47
+ }
48
+
49
+ get customCommands() {
50
+ return this._customCommands
51
+ }
52
+
53
+ get disabledCommands() {
54
+ return this._disabledCommands
55
+ }
56
+
57
+ get prefixHandler() {
58
+ return this._prefixes
59
+ }
60
+
61
+ async readFiles() {
62
+ const defaultCommands = getAllFiles(path.join(__dirname, './commands'))
63
+ const files = getAllFiles(this._commandsDir)
64
+ const validations = [
65
+ ...this.getValidations(path.join(__dirname, 'validations', 'syntax')),
66
+ ...this.getValidations(this._instance.validations?.syntax),
67
+ ]
68
+
69
+ for (let file of [...defaultCommands, ...files]) {
70
+ const commandObject = require(file)
71
+
72
+ let commandName = file.split(/[\/\\]/)
73
+ commandName = commandName.pop()
74
+ commandName = commandName.split('.')[0]
75
+
76
+ const command = new Command(this._instance, commandName, commandObject)
77
+
78
+ const {
79
+ description,
80
+ type,
81
+ testOnly,
82
+ delete: del,
83
+ aliases = [],
84
+ init = () => {},
85
+ } = commandObject
86
+
87
+ if (
88
+ del ||
89
+ this._instance.disabledDefaultCommands.includes(
90
+ commandName.toLowerCase()
91
+ )
92
+ ) {
93
+ if (type === 'SLASH' || type === 'BOTH') {
94
+ if (testOnly) {
95
+ for (const guildId of this._instance.testServers) {
96
+ this._slashCommands.delete(command.commandName, guildId)
97
+ }
98
+ } else {
99
+ this._slashCommands.delete(command.commandName)
100
+ }
101
+ }
102
+
103
+ return
104
+ }
105
+
106
+ for (const validation of validations) {
107
+ validation(command)
108
+ }
109
+
110
+ await init(this._client, this._instance)
111
+
112
+ const names = [command.commandName, ...aliases]
113
+
114
+ for (const name of names) {
115
+ this._commands.set(name, command)
116
+ }
117
+
118
+ if (type === 'SLASH' || type === 'BOTH') {
119
+ const options =
120
+ commandObject.options ||
121
+ this._slashCommands.createOptions(commandObject)
122
+
123
+ if (testOnly) {
124
+ for (const guildId of this._instance.testServers) {
125
+ this._slashCommands.create(
126
+ command.commandName,
127
+ description,
128
+ options,
129
+ guildId
130
+ )
131
+ }
132
+ } else {
133
+ this._slashCommands.create(command.commandName, description, options)
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ async runCommand(command, args, message, interaction) {
140
+ const { callback, type, cooldowns } = command.commandObject
141
+
142
+ if (message && type === 'SLASH') {
143
+ return
144
+ }
145
+
146
+ const guild = message ? message.guild : interaction.guild
147
+ const member = message ? message.member : interaction.member
148
+ const user = message ? message.author : interaction.user
149
+ const channel = message ? message.channel : interaction.channel
150
+
151
+ const usage = {
152
+ instance: command.instance,
153
+ message,
154
+ interaction,
155
+ args,
156
+ text: args.join(' '),
157
+ guild,
158
+ member,
159
+ user,
160
+ channel,
161
+ }
162
+
163
+ for (const validation of this._validations) {
164
+ if (!(await validation(command, usage, this._prefixes.get(guild?.id)))) {
165
+ return
166
+ }
167
+ }
168
+
169
+ if (cooldowns) {
170
+ let cooldownType
171
+
172
+ for (const type of cooldownTypes) {
173
+ if (cooldowns[type]) {
174
+ cooldownType = type
175
+ break
176
+ }
177
+ }
178
+
179
+ const cooldownUsage = {
180
+ cooldownType,
181
+ userId: user.id,
182
+ actionId: `command_${command.commandName}`,
183
+ guildId: guild?.id,
184
+ duration: cooldowns[cooldownType],
185
+ errorMessage: cooldowns.errorMessage,
186
+ }
187
+
188
+ const result = this._instance.cooldowns.canRunAction(cooldownUsage)
189
+
190
+ if (typeof result === 'string') {
191
+ return result
192
+ }
193
+
194
+ await this._instance.cooldowns.start(cooldownUsage)
195
+
196
+ usage.cancelCooldown = () => {
197
+ this._instance.cooldowns.cancelCooldown(cooldownUsage)
198
+ }
199
+
200
+ usage.updateCooldown = (expires) => {
201
+ this._instance.cooldowns.updateCooldown(cooldownUsage, expires)
202
+ }
203
+ }
204
+
205
+ return await callback(usage)
206
+ }
207
+
208
+ getValidations(folder) {
209
+ if (!folder) {
210
+ return []
211
+ }
212
+
213
+ const validations = getAllFiles(folder).map((filePath) => require(filePath))
214
+
215
+ return validations
216
+ }
217
+ }
218
+
219
+ module.exports = CommandHandler
@@ -0,0 +1,74 @@
1
+ const customCommandSchema = require('../models/custom-command-schema')
2
+
3
+ class CustomCommands {
4
+ // guildId-commandName: response
5
+ _customCommands = new Map()
6
+
7
+ constructor(commandHandler) {
8
+ this._commandHandler = commandHandler
9
+ this.loadCommands()
10
+ }
11
+
12
+ async loadCommands() {
13
+ const results = await customCommandSchema.find({})
14
+
15
+ for (const result of results) {
16
+ const { _id, response } = result
17
+ this._customCommands.set(_id, response)
18
+ }
19
+ }
20
+
21
+ async create(guildId, commandName, description, response) {
22
+ const _id = `${guildId}-${commandName}`
23
+
24
+ this._customCommands.set(_id, response)
25
+
26
+ this._commandHandler.slashCommands.create(
27
+ commandName,
28
+ description,
29
+ [],
30
+ guildId
31
+ )
32
+
33
+ await customCommandSchema.findOneAndUpdate(
34
+ {
35
+ _id,
36
+ },
37
+ {
38
+ _id,
39
+ response,
40
+ },
41
+ {
42
+ upsert: true,
43
+ }
44
+ )
45
+ }
46
+
47
+ async delete(guildId, commandName) {
48
+ const _id = `${guildId}-${commandName}`
49
+
50
+ this._customCommands.delete(_id)
51
+
52
+ this._commandHandler.slashCommands.delete(commandName, guildId)
53
+
54
+ await customCommandSchema.deleteOne({ _id })
55
+ }
56
+
57
+ async run(commandName, message, interaction) {
58
+ const guild = message ? message.guild : interaction.guild
59
+ if (!guild) {
60
+ return
61
+ }
62
+
63
+ const _id = `${guild.id}-${commandName}`
64
+ const response = this._customCommands.get(_id)
65
+ if (!response) {
66
+ return
67
+ }
68
+
69
+ if (message) message.channel.send(response).catch(() => {})
70
+ else if (interaction) interaction.reply(response).catch(() => {})
71
+ }
72
+ }
73
+
74
+ module.exports = CustomCommands
@@ -0,0 +1,50 @@
1
+ const disabledCommandSchema = require('../models/disabled-commands-schema')
2
+
3
+ class DisabledCommands {
4
+ // array of `${guildId}-${commandName}`
5
+ _disabledCommands = []
6
+
7
+ constructor() {
8
+ this.loadDisabledCommands()
9
+ }
10
+
11
+ async loadDisabledCommands() {
12
+ const results = await disabledCommandSchema.find({})
13
+
14
+ for (const result of results) {
15
+ this._disabledCommands.push(result._id)
16
+ }
17
+ }
18
+
19
+ async disable(guildId, commandName) {
20
+ if (this.isDisabled(guildId, commandName)) {
21
+ return
22
+ }
23
+
24
+ const _id = `${guildId}-${commandName}`
25
+ this._disabledCommands.push(_id)
26
+
27
+ try {
28
+ await new disabledCommandSchema({
29
+ _id,
30
+ }).save()
31
+ } catch (ignored) {}
32
+ }
33
+
34
+ async enable(guildId, commandName) {
35
+ if (!this.isDisabled(guildId, commandName)) {
36
+ return
37
+ }
38
+
39
+ const _id = `${guildId}-${commandName}`
40
+ this._disabledCommands = this._disabledCommands.filter((id) => id !== _id)
41
+
42
+ await disabledCommandSchema.deleteOne({ _id })
43
+ }
44
+
45
+ isDisabled(guildId, commandName) {
46
+ return this._disabledCommands.includes(`${guildId}-${commandName}`)
47
+ }
48
+ }
49
+
50
+ module.exports = DisabledCommands
@@ -0,0 +1,50 @@
1
+ const guildPrefixSchema = require('../models/guild-prefix-schema')
2
+
3
+ class PrefixHandler {
4
+ // <guildId: prefix>
5
+ _prefixes = new Map()
6
+ _defaultPrefix = '!'
7
+
8
+ constructor() {
9
+ this.loadPrefixes()
10
+ }
11
+
12
+ async loadPrefixes() {
13
+ const results = await guildPrefixSchema.find({})
14
+
15
+ for (const result of results) {
16
+ this._prefixes.set(result._id, result.prefix)
17
+ }
18
+ }
19
+
20
+ get defaultPrefix() {
21
+ return this._defaultPrefix
22
+ }
23
+
24
+ get(guildId) {
25
+ if (!guildId) {
26
+ return defaultPrefix
27
+ }
28
+
29
+ return this._prefixes.get(guildId) || this.defaultPrefix
30
+ }
31
+
32
+ async set(guildId, prefix) {
33
+ this._prefixes.set(guildId, prefix)
34
+
35
+ await guildPrefixSchema.findOneAndUpdate(
36
+ {
37
+ _id: guildId,
38
+ },
39
+ {
40
+ _id: guildId,
41
+ prefix,
42
+ },
43
+ {
44
+ upsert: true,
45
+ }
46
+ )
47
+ }
48
+ }
49
+
50
+ module.exports = PrefixHandler
@@ -0,0 +1,111 @@
1
+ const { ApplicationCommandOptionType } = require('discord.js')
2
+
3
+ class SlashCommands {
4
+ constructor(client) {
5
+ this._client = client
6
+ }
7
+
8
+ async getCommands(guildId) {
9
+ let commands
10
+
11
+ if (guildId) {
12
+ const guild = await this._client.guilds.fetch(guildId)
13
+ commands = guild.commands
14
+ } else {
15
+ commands = this._client.application.commands
16
+ }
17
+
18
+ await commands.fetch()
19
+
20
+ return commands
21
+ }
22
+
23
+ areOptionsDifferent(options, existingOptions) {
24
+ for (let a = 0; a < options.length; ++a) {
25
+ const option = options[a]
26
+ const existing = existingOptions[a]
27
+
28
+ if (
29
+ option.name !== existing.name ||
30
+ option.type !== existing.type ||
31
+ option.description !== existing.description
32
+ ) {
33
+ return true
34
+ }
35
+ }
36
+
37
+ return false
38
+ }
39
+
40
+ async create(name, description, options, guildId) {
41
+ const commands = await this.getCommands(guildId)
42
+
43
+ const existingCommand = commands.cache.find((cmd) => cmd.name === name)
44
+ if (existingCommand) {
45
+ const { description: existingDescription, options: existingOptions } =
46
+ existingCommand
47
+
48
+ if (
49
+ description !== existingDescription ||
50
+ options.length !== existingOptions.length ||
51
+ this.areOptionsDifferent(options, existingOptions)
52
+ ) {
53
+ console.log(`Updating the command "${name}"`)
54
+
55
+ await commands.edit(existingCommand.id, {
56
+ description,
57
+ options,
58
+ })
59
+ }
60
+ return
61
+ }
62
+
63
+ await commands.create({
64
+ name,
65
+ description,
66
+ options,
67
+ })
68
+ }
69
+
70
+ async delete(commandName, guildId) {
71
+ const commands = await this.getCommands(guildId)
72
+
73
+ const existingCommand = commands.cache.find(
74
+ (cmd) => cmd.name === commandName
75
+ )
76
+ if (!existingCommand) {
77
+ return
78
+ }
79
+
80
+ await existingCommand.delete()
81
+ }
82
+
83
+ createOptions({ expectedArgs = '', minArgs = 0 }) {
84
+ const options = []
85
+
86
+ // <num 1> <num 2>
87
+
88
+ if (expectedArgs) {
89
+ const split = expectedArgs
90
+ .substring(1, expectedArgs.length - 1)
91
+ // num 1> <num 2
92
+ .split(/[>\]] [<\[]/)
93
+ // ['num 1', 'num 2']
94
+
95
+ for (let a = 0; a < split.length; ++a) {
96
+ const arg = split[a]
97
+
98
+ options.push({
99
+ name: arg.toLowerCase().replace(/\s+/g, '-'),
100
+ description: arg,
101
+ type: ApplicationCommandOptionType.String,
102
+ required: a < minArgs,
103
+ })
104
+ }
105
+ }
106
+
107
+ return options
108
+ }
109
+ }
110
+
111
+ module.exports = SlashCommands
@@ -0,0 +1,69 @@
1
+ const { ApplicationCommandOptionType } = require('discord.js')
2
+
3
+ module.exports = {
4
+ description: 'Specifies what commands can be ran inside of what channels',
5
+
6
+ type: 'SLASH',
7
+ testOnly: true,
8
+ guildOnly: true,
9
+
10
+ options: [
11
+ {
12
+ name: 'command',
13
+ description: 'The command to restrict to specific channels',
14
+ required: true,
15
+ type: ApplicationCommandOptionType.String,
16
+ autocomplete: true,
17
+ },
18
+ {
19
+ name: 'channel',
20
+ description: 'The channel to use for this command',
21
+ required: true,
22
+ type: ApplicationCommandOptionType.Channel,
23
+ },
24
+ ],
25
+
26
+ autocomplete: (_, command) => {
27
+ return [...command.instance.commandHandler.commands.keys()]
28
+ },
29
+
30
+ callback: async ({ instance, guild, interaction }) => {
31
+ const commandName = interaction.options.getString('command')
32
+ const channel = interaction.options.getChannel('channel')
33
+
34
+ const command = instance.commandHandler.commands.get(
35
+ commandName.toLowerCase()
36
+ )
37
+ if (!command) {
38
+ return `The command "${commandName}" does not exist.`
39
+ }
40
+
41
+ const { channelCommands } = instance.commandHandler
42
+
43
+ let availableChannels = []
44
+ const canRun = (
45
+ await channelCommands.getAvailableChannels(guild.id, commandName)
46
+ ).includes(channel.id)
47
+
48
+ if (canRun) {
49
+ availableChannels = await channelCommands.remove(
50
+ guild.id,
51
+ commandName,
52
+ channel.id
53
+ )
54
+ } else {
55
+ availableChannels = await channelCommands.add(
56
+ guild.id,
57
+ commandName,
58
+ channel.id
59
+ )
60
+ }
61
+
62
+ if (availableChannels.length) {
63
+ const channelNames = availableChannels.map((c) => `<#${c}> `)
64
+ return `The command "${commandName}" can now only be ran inside of the following channels: ${channelNames}`
65
+ }
66
+
67
+ return `The command "${commandName}" can now be ran inside of any text channel.`
68
+ },
69
+ }
@@ -0,0 +1,28 @@
1
+ const { PermissionFlagsBits } = require('discord.js')
2
+
3
+ module.exports = {
4
+ description: 'Creates a custom command',
5
+
6
+ minArgs: 3,
7
+ syntaxError: 'Correct syntax: {PREFIX}customCommand {ARGS}',
8
+ expectedArgs: '<command name> <description> <response>',
9
+
10
+ type: 'SLASH',
11
+ guildOnly: true,
12
+ testOnly: true,
13
+
14
+ permissions: [PermissionFlagsBits.Administrator],
15
+
16
+ callback: async ({ instance, args, guild }) => {
17
+ const [commandName, description, response] = args
18
+
19
+ await instance.commandHandler.customCommands.create(
20
+ guild.id,
21
+ commandName,
22
+ description,
23
+ response
24
+ )
25
+
26
+ return `Custom command "${commandName}" has been created!`
27
+ },
28
+ }