@pikokr/command.ts 5.0.0-dev.a0bc517 → 5.0.0-dev.c0902b8

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 (42) hide show
  1. package/.github/workflows/codeql-analysis.yml +3 -3
  2. package/.github/workflows/docs.yml +2 -2
  3. package/dist/index.d.ts +125 -34
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/package.json +17 -14
  7. package/publish-version.js +10 -0
  8. package/renovate.json +5 -0
  9. package/scripts/docs.ts +8 -8
  10. package/src/applicationCommand/ApplicationCommand.ts +14 -18
  11. package/src/applicationCommand/ApplicationCommandExtension.ts +169 -0
  12. package/src/applicationCommand/ApplicationCommandOption.ts +9 -2
  13. package/src/applicationCommand/index.ts +8 -0
  14. package/src/core/components/BaseComponent.ts +42 -14
  15. package/src/core/components/ComponentArgument.ts +8 -0
  16. package/src/core/components/ComponentArgumentDecorator.ts +8 -0
  17. package/src/core/components/decoratorCreator.ts +17 -3
  18. package/src/core/components/index.ts +13 -3
  19. package/src/core/converter/index.ts +16 -0
  20. package/src/core/extensions/CTSExtension.ts +17 -0
  21. package/src/core/extensions/Extension.ts +62 -0
  22. package/src/core/extensions/index.ts +9 -0
  23. package/src/core/hooks/componentHook.ts +40 -0
  24. package/src/core/hooks/index.ts +11 -1
  25. package/src/core/hooks/moduleHook.ts +11 -3
  26. package/src/core/index.ts +13 -1
  27. package/src/core/listener/index.ts +22 -2
  28. package/src/core/structures/CommandClient.ts +68 -4
  29. package/src/core/structures/Registry.ts +29 -5
  30. package/src/core/structures/index.ts +8 -0
  31. package/src/core/symbols.ts +13 -4
  32. package/src/core/utils/checks.ts +27 -0
  33. package/src/core/utils/errors.ts +9 -0
  34. package/src/core/utils/index.ts +10 -0
  35. package/src/index.ts +11 -10
  36. package/src/textCommand/TextCommand.ts +20 -0
  37. package/src/textCommand/TextCommandExtension.ts +128 -0
  38. package/src/textCommand/index.ts +11 -0
  39. package/src/textCommand/parameters.ts +14 -0
  40. package/test/index.ts +51 -27
  41. package/tsconfig.prod.json +1 -0
  42. package/tsup.config.ts +8 -8
@@ -1,17 +1,37 @@
1
+ /*
2
+ * File: Registry.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ import chalk from 'chalk'
1
10
  import { Collection } from 'discord.js'
2
11
  import EventEmitter from 'events'
3
12
  import _ from 'lodash'
4
- import { BaseComponent, getComponentStore } from '../components'
13
+ import { Logger } from 'tslog'
14
+ import { getComponentStore } from '../components'
15
+ import type { BaseComponent } from '../components'
5
16
  import { getModuleHookStore } from '../hooks'
6
17
  import { ListenerComponent } from '../listener'
7
- import { ListenersSymbol } from '../symbols'
18
+ import { CommandClientSymbol } from '../symbols'
19
+ import { CommandClient } from './CommandClient'
8
20
 
9
21
  export class Registry {
10
22
  extensions: object[] = []
11
23
 
12
24
  emitters: Collection<string, EventEmitter> = new Collection()
13
25
 
14
- getComponentsWithTypeGlobal<T extends typeof BaseComponent<Config, RequiredConfig>, Config, RequiredConfig>(type: T): InstanceType<T>[] {
26
+ logger: Logger
27
+
28
+ constructor(logger: Logger, public client: CommandClient) {
29
+ this.logger = logger.getChildLogger({
30
+ prefix: [chalk.green('[Registry]')],
31
+ })
32
+ }
33
+
34
+ getComponentsWithTypeGlobal<T extends typeof BaseComponent<Config>, Config>(type: T): InstanceType<T>[] {
15
35
  const result: InstanceType<T>[] = []
16
36
 
17
37
  for (const ext of this.extensions) {
@@ -21,7 +41,7 @@ export class Registry {
21
41
  return result
22
42
  }
23
43
 
24
- getComponentsWithType<T extends typeof BaseComponent<Config, RequiredConfig>, Config, RequiredConfig>(ext: object, type: T): InstanceType<T>[] {
44
+ getComponentsWithType<T extends typeof BaseComponent<Config>, Config>(ext: object, type: T): InstanceType<T>[] {
25
45
  const componentStore = getComponentStore(ext)
26
46
 
27
47
  return Array.from(componentStore.filter((x) => (x.constructor as unknown) === type).values() as Iterable<InstanceType<T>>)
@@ -57,15 +77,19 @@ export class Registry {
57
77
  }
58
78
 
59
79
  async registerModule(ext: object) {
80
+ Reflect.defineMetadata(CommandClientSymbol, this.client, ext)
81
+
60
82
  this.registerEventListeners(ext)
61
83
  await this.runModuleHook(ext, 'load')
62
84
  this.extensions.push(ext)
85
+ this.logger.info(`Module registered: ${chalk.green(ext.constructor.name)}`)
63
86
  }
64
87
 
65
88
  async unregisterModule(ext: object) {
66
89
  this.unregisterEventListeners(ext)
67
90
  await this.runModuleHook(ext, 'unload')
68
91
  _.remove(this.extensions, (x) => x === ext)
92
+ this.logger.info(`Module unregistered: ${chalk.green(ext.constructor.name)}`)
69
93
  }
70
94
 
71
95
  runModuleHook(ext: object, hookName: string, ...args: unknown[]) {
@@ -75,7 +99,7 @@ export class Registry {
75
99
 
76
100
  if (functions) {
77
101
  for (const fn of functions) {
78
- fn.apply(ext, args)
102
+ fn.call(ext, ...args)
79
103
  }
80
104
  }
81
105
  }
@@ -1,2 +1,10 @@
1
+ /*
2
+ * File: index.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
1
9
  export * from './Registry'
2
10
  export * from './CommandClient'
@@ -1,4 +1,13 @@
1
- export const ComponentStoreSymbol = Symbol()
2
- export const ComponentArgStoreSymbol = Symbol()
3
- export const ModuleHookStoreSymbol = Symbol()
4
- export const ListenersSymbol = Symbol()
1
+ /*
2
+ * File: symbols.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ export const ComponentStoreSymbol = Symbol()
10
+ export const ComponentArgStoreSymbol = Symbol()
11
+ export const ModuleHookStoreSymbol = Symbol()
12
+ export const CommandClientSymbol = Symbol()
13
+ export const ComponentHookSymbol = Symbol()
@@ -0,0 +1,27 @@
1
+ /*
2
+ * File: checks.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ import { BaseInteraction, Interaction, Message } from 'discord.js'
10
+ import { createComponentHook } from '../hooks'
11
+ import { ComponentHookFn } from '../hooks/componentHook'
12
+ import { CommandClient } from '../structures'
13
+ import { OwnerOnlyError } from './errors'
14
+
15
+ export const createCheckDecorator = (fn: ComponentHookFn) => createComponentHook('beforeCall', fn)
16
+
17
+ export const ownerOnly = createCheckDecorator(async (client: CommandClient, i: Interaction | Message) => {
18
+ let isOwner = false
19
+
20
+ if (i instanceof BaseInteraction) {
21
+ isOwner = client.owners.has(i.user.id)
22
+ } else if (i instanceof Message) {
23
+ isOwner = client.owners.has(i.author.id)
24
+ }
25
+
26
+ if (!isOwner) throw new OwnerOnlyError()
27
+ })
@@ -0,0 +1,9 @@
1
+ /*
2
+ * File: errors.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ export class OwnerOnlyError {}
@@ -0,0 +1,10 @@
1
+ /*
2
+ * File: index.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ export * from './checks'
10
+ export * from './errors'
package/src/index.ts CHANGED
@@ -1,10 +1,11 @@
1
- /*
2
- * File: index.ts
3
- *
4
- * Copyright (c) 2022-2022 pikokr
5
- *
6
- * Licensed under MIT License. Please see more defails in LICENSE file.
7
- */
8
-
9
- export * from './core'
10
- export * from './applicationCommand'
1
+ /*
2
+ * File: index.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ export * from './core'
10
+ export * from './applicationCommand'
11
+ export * from './textCommand'
@@ -0,0 +1,20 @@
1
+ /*
2
+ * File: TextCommand.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ import { createComponentDecorator } from '../core/components/decoratorCreator'
10
+ import { BaseComponent } from '../core/components/BaseComponent'
11
+
12
+ type TextCommandOptions = {
13
+ name: string
14
+ aliases?: string
15
+ description?: string
16
+ }
17
+
18
+ export class TextCommandComponent extends BaseComponent<TextCommandOptions> {}
19
+
20
+ export const command = createComponentDecorator(TextCommandComponent)
@@ -0,0 +1,128 @@
1
+ /*
2
+ * File: TextCommandExtension.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ import { listener } from '../core/listener'
10
+ import { Message } from 'discord.js'
11
+ import { CTSExtension } from '../core/extensions/CTSExtension'
12
+ import { TextCommandComponent } from './TextCommand'
13
+ import { TextCommandRestOption } from './parameters'
14
+ import { argConverter } from '../core'
15
+
16
+ export type TextCommandConfig = {
17
+ prefix: string | string[] | ((msg: Message) => Promise<string | string[]> | string | string[])
18
+ }
19
+
20
+ export class TextCommandExtension extends CTSExtension {
21
+ constructor(private config: TextCommandConfig) {
22
+ super()
23
+ }
24
+
25
+ private async processPrefix(msg: Message): Promise<number | null> {
26
+ const content = msg.content
27
+ let prefix = this.config.prefix
28
+
29
+ if (typeof prefix === 'function') {
30
+ prefix = await prefix(msg)
31
+ }
32
+
33
+ if (typeof prefix === 'string') {
34
+ if (content.startsWith(prefix)) return prefix.length
35
+ return null
36
+ }
37
+
38
+ if (prefix instanceof Array) {
39
+ const p = prefix.find((x) => content.startsWith(x))
40
+
41
+ if (p) return p.length
42
+ return null
43
+ }
44
+
45
+ return null
46
+ }
47
+
48
+ @listener({ event: 'messageCreate', emitter: 'discord' })
49
+ private async messageCreate(msg: Message) {
50
+ const startIndex = await this.processPrefix(msg)
51
+
52
+ if (!startIndex) return
53
+
54
+ const content = msg.content.slice(startIndex)
55
+
56
+ const commands: TextCommandComponent[] = []
57
+
58
+ const extensions = new Map<TextCommandComponent, object>()
59
+
60
+ for (const ext of this.commandClient.registry.extensions) {
61
+ for (const cmd of this.commandClient.registry.getComponentsWithType(ext, TextCommandComponent)) {
62
+ commands.push(cmd)
63
+ extensions.set(cmd, ext)
64
+ }
65
+ }
66
+
67
+ let commandNameLength = 0
68
+
69
+ const command = commands.find((x) => {
70
+ const names = [x.options.name]
71
+
72
+ if (x.options.aliases) {
73
+ names.push(...x.options.aliases)
74
+ }
75
+
76
+ for (const name of names) {
77
+ if (content.startsWith(name)) {
78
+ if (content.length === name.length) {
79
+ commandNameLength = name.length
80
+ return true
81
+ }
82
+ commandNameLength = name.length
83
+ return content.startsWith(name + ' ')
84
+ }
85
+ }
86
+
87
+ return false
88
+ })
89
+
90
+ if (!command) return
91
+
92
+ const ext = extensions.get(command)
93
+
94
+ if (!ext) return
95
+
96
+ const args: unknown[] = []
97
+
98
+ let argStrings = content.slice(commandNameLength + 1).split(/ /g)
99
+
100
+ await this.convertArguments(TextCommandComponent, args, command.argTypes, async (arg, i, converter) => {
101
+ if (converter.options.parameterless) return [msg]
102
+
103
+ if (arg.decorators.find((x) => x.constructor === TextCommandRestOption)) {
104
+ const text = argStrings.join(' ')
105
+ argStrings = []
106
+ return [text, msg]
107
+ }
108
+ return [argStrings.shift(), msg]
109
+ })
110
+
111
+ await command.execute(ext, args, [msg])
112
+ }
113
+
114
+ @argConverter({ component: TextCommandComponent, type: Message, parameterless: true })
115
+ async mesage(msg: Message) {
116
+ return msg
117
+ }
118
+
119
+ @argConverter({ component: TextCommandComponent, type: String })
120
+ async str(value: string) {
121
+ return value
122
+ }
123
+
124
+ @argConverter({ component: TextCommandComponent, type: Number })
125
+ async num(value: string) {
126
+ return Number(value)
127
+ }
128
+ }
@@ -0,0 +1,11 @@
1
+ /*
2
+ * File: index.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ export * from './TextCommand'
10
+ export type { TextCommandConfig } from './TextCommandExtension'
11
+ export * from './parameters'
@@ -0,0 +1,14 @@
1
+ /*
2
+ * File: parameters.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
+ import { ComponentArgumentDecorator } from '../core'
10
+ import { createArgumentDecorator } from '../core'
11
+
12
+ export class TextCommandRestOption extends ComponentArgumentDecorator<void> {}
13
+
14
+ export const rest = createArgumentDecorator(TextCommandRestOption)
package/test/index.ts CHANGED
@@ -1,23 +1,30 @@
1
- import { ApplicationCommandOptionType, Client } from 'discord.js'
2
- import { applicationCommand, ApplicationCommandComponent, CommandClient, moduleHook, option, Registry } from '../src'
3
- import { listener } from '../src/core/listener'
1
+ /*
2
+ * File: index.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
4
8
 
5
- class Test {
9
+ import { ApplicationCommandOptionType, ApplicationCommandType, ChatInputCommandInteraction, Client, Message } from 'discord.js'
10
+ import { applicationCommand, CommandClient, moduleHook, option, ownerOnly, listener, Extension, command, rest } from '../src'
11
+ import 'dotenv/config'
12
+ import { Logger } from 'tslog'
13
+ import chalk from 'chalk'
14
+
15
+ class Test extends Extension {
6
16
  @applicationCommand({
17
+ type: ApplicationCommandType.ChatInput,
7
18
  name: 'test',
8
19
  description: 'wow this is test',
9
20
  })
10
- async testCommand(
11
- @option({
12
- name: 'hello',
13
- description: '와아',
14
- type: ApplicationCommandOptionType.String,
15
- })
16
- hello: string,
17
- world: string,
18
- ) {}
21
+ async testCommand(i: ChatInputCommandInteraction) {
22
+ i.reply('Wow')
23
+ }
19
24
 
25
+ @ownerOnly
20
26
  @applicationCommand({
27
+ type: ApplicationCommandType.ChatInput,
21
28
  name: 'test2',
22
29
  description: 'wow this is test wow',
23
30
  })
@@ -26,44 +33,61 @@ class Test {
26
33
  name: 'sans',
27
34
  description: '와',
28
35
  type: ApplicationCommandOptionType.String,
36
+ required: true,
29
37
  })
30
38
  wa: string,
31
- ) {}
39
+ i: ChatInputCommandInteraction,
40
+ ) {
41
+ i.reply(wa)
42
+ }
32
43
 
33
44
  @moduleHook('load')
34
45
  load() {
35
- console.log('load')
46
+ this.logger.info('Load')
36
47
  }
37
48
 
38
49
  @moduleHook('unload')
39
50
  unload() {
40
- console.log('unload')
51
+ this.logger.info('Unload')
52
+ }
53
+
54
+ @listener({ event: 'ready' })
55
+ async testEvent() {
56
+ this.logger.info(`Login: ${chalk.green(client.user!.tag)}`)
57
+ await this.commandClient.fetchOwners()
41
58
  }
42
59
 
43
- @listener({ event: 'test', emitter: 'discord' })
44
- testEvent() {
45
- console.log('test')
60
+ @command({ name: 'test' })
61
+ async test(msg: Message, @rest() sans: string) {
62
+ console.log(sans)
63
+ await msg.reply('Wow')
46
64
  }
47
65
  }
48
66
 
49
67
  const ext = new Test()
50
68
 
51
- const client = new Client({ intents: [] })
69
+ const client = new Client({ intents: ['GuildMessages', 'MessageContent', 'DirectMessages', 'Guilds'] })
70
+
71
+ const logger = new Logger({ dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone })
52
72
 
53
- const cc = new CommandClient(client)
73
+ const cc = new CommandClient(client, logger)
54
74
 
55
75
  const registry = cc.registry
56
76
 
57
77
  const run = async () => {
58
- await registry.registerModule(ext)
78
+ await cc.enableApplicationCommandsExtension({
79
+ guilds: ['832938554438844438'],
80
+ })
59
81
 
60
- // listener test
61
- client.emit('test')
82
+ await cc.enableTextCommandsExtension({
83
+ prefix: '.',
84
+ })
85
+
86
+ await registry.registerModule(ext)
62
87
 
63
- await registry.unregisterModule(ext)
88
+ await client.login(process.env.TOKEN)
64
89
 
65
- // shold not work
66
- client.emit('test')
90
+ await cc.getApplicationCommandsExtension()!.sync()
67
91
  }
68
92
 
69
93
  run()
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
3
  "exclude": ["node_modules", "scripts", "test", "tsup.config.ts"],
4
+ "include": ["src"],
4
5
  "compilerOptions": {
5
6
  "rootDir": "./src",
6
7
  }
package/tsup.config.ts CHANGED
@@ -1,11 +1,11 @@
1
- /*
2
- * File: tsup.config.ts
3
- *
4
- * Copyright (c) 2022-2022 pikokr
5
- *
6
- * Licensed under MIT License. Please see more defails in LICENSE file.
7
- */
8
-
1
+ /*
2
+ * File: tsup.config.ts
3
+ *
4
+ * Copyright (c) 2022-2022 pikokr
5
+ *
6
+ * Licensed under MIT License. Please see more defails in LICENSE file.
7
+ */
8
+
9
9
  import { defineConfig } from 'tsup'
10
10
 
11
11
  export default defineConfig({