@pikokr/command.ts 5.0.0-dev.84d717c → 5.0.0-dev.9362fb5

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 (43) hide show
  1. package/.github/workflows/codeql-analysis.yml +29 -29
  2. package/.github/workflows/docs.yml +1 -1
  3. package/.vscode/settings.json +9 -9
  4. package/README.md +2 -0
  5. package/dist/index.d.ts +122 -18
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/docs/index.yml +1 -1
  9. package/package.json +7 -3
  10. package/publish-version.js +10 -0
  11. package/renovate.json +5 -0
  12. package/src/applicationCommand/ApplicationCommand.ts +15 -15
  13. package/src/applicationCommand/ApplicationCommandExtension.ts +169 -0
  14. package/src/applicationCommand/ApplicationCommandOption.ts +9 -2
  15. package/src/applicationCommand/index.ts +9 -1
  16. package/src/core/components/BaseComponent.ts +35 -11
  17. package/src/core/components/ComponentArgument.ts +8 -0
  18. package/src/core/components/ComponentArgumentDecorator.ts +9 -1
  19. package/src/core/components/decoratorCreator.ts +23 -10
  20. package/src/core/components/index.ts +13 -3
  21. package/src/core/converter/index.ts +16 -0
  22. package/src/core/extensions/CTSExtension.ts +17 -0
  23. package/src/core/extensions/Extension.ts +62 -0
  24. package/src/core/extensions/index.ts +9 -0
  25. package/src/core/hooks/componentHook.ts +40 -0
  26. package/src/core/hooks/index.ts +11 -1
  27. package/src/core/hooks/moduleHook.ts +11 -3
  28. package/src/core/index.ts +13 -1
  29. package/src/core/listener/index.ts +29 -0
  30. package/src/core/structures/CommandClient.ts +71 -0
  31. package/src/core/structures/Registry.ts +82 -2
  32. package/src/core/structures/index.ts +9 -0
  33. package/src/core/symbols.ts +13 -3
  34. package/src/core/utils/checks.ts +27 -0
  35. package/src/core/utils/errors.ts +9 -0
  36. package/src/core/utils/index.ts +10 -0
  37. package/src/index.ts +1 -0
  38. package/src/textCommand/TextCommand.ts +11 -0
  39. package/src/textCommand/index.ts +1 -0
  40. package/test/index.ts +47 -17
  41. package/tsconfig.json +2 -2
  42. package/tsconfig.prod.json +6 -2
  43. package/tsup.config.ts +1 -0
@@ -0,0 +1,71 @@
1
+ /*
2
+ * File: CommandClient.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'
10
+ import { Client, Snowflake, Team, User } from 'discord.js'
11
+ import EventEmitter from 'events'
12
+ import { Logger } from 'tslog'
13
+ import { ApplicationCommandExtension, ApplicationCommandExtensionConfig } from '../../applicationCommand/ApplicationCommandExtension'
14
+ import { CommandClientSymbol } from '../symbols'
15
+ import { Registry } from './Registry'
16
+ export class CommandClient extends EventEmitter {
17
+ ctsLogger: Logger
18
+ registry: Registry
19
+
20
+ owners: Set<Snowflake> = new Set()
21
+
22
+ constructor(public discord: Client, public logger: Logger = new Logger({ dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone })) {
23
+ super()
24
+
25
+ this.ctsLogger = logger.getChildLogger({ prefix: [chalk.blue('[command.ts]')], displayFilePath: 'hidden', displayFunctionName: false })
26
+
27
+ this.registry = new Registry(this.ctsLogger, this)
28
+
29
+ this.registry.registerEventEmitter('cts', this)
30
+ this.registry.registerEventEmitter('discord', this.discord)
31
+ }
32
+
33
+ async fetchOwners() {
34
+ if (!this.discord.application) throw new Error('The client is not logged in.')
35
+
36
+ this.ctsLogger.info('Fetching owners...')
37
+
38
+ await this.discord.application.fetch()
39
+
40
+ const owner = this.discord.application.owner
41
+
42
+ if (!owner) throw new Error('Cannot find application owner')
43
+
44
+ const owners: string[] = []
45
+
46
+ if (owner instanceof User) {
47
+ this.owners.add(owner.id)
48
+ owners.push(owner.tag)
49
+ } else if (owner instanceof Team) {
50
+ for (const [id, member] of owner.members) {
51
+ this.owners.add(id)
52
+ owners.push(member.user.tag)
53
+ }
54
+ }
55
+
56
+ this.ctsLogger.info(`Fetched ${chalk.green(owners.length)} owners(${owners.map((x) => chalk.blue(x)).join(', ')})`)
57
+ }
58
+
59
+ async enableApplicationCommandsExtension(config: ApplicationCommandExtensionConfig) {
60
+ await this.registry.registerModule(new ApplicationCommandExtension(config))
61
+ this.ctsLogger.info('Application command extension enabled.')
62
+ }
63
+
64
+ getApplicationCommandsExtension() {
65
+ return this.registry.extensions.find((x) => x.constructor === ApplicationCommandExtension) as ApplicationCommandExtension | undefined
66
+ }
67
+
68
+ static getFromModule(ext: object): CommandClient {
69
+ return Reflect.getMetadata(CommandClientSymbol, ext)
70
+ }
71
+ }
@@ -1,19 +1,95 @@
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'
10
+ import { Collection } from 'discord.js'
11
+ import EventEmitter from 'events'
1
12
  import _ from 'lodash'
13
+ import { Logger } from 'tslog'
14
+ import { getComponentStore } from '../components'
15
+ import type { BaseComponent } from '../components'
2
16
  import { getModuleHookStore } from '../hooks'
17
+ import { ListenerComponent } from '../listener'
18
+ import { CommandClientSymbol } from '../symbols'
19
+ import { CommandClient } from './CommandClient'
3
20
 
4
21
  export class Registry {
5
22
  extensions: object[] = []
6
23
 
7
- async withGroup(name: string, code: () => void | Promise<void>) {}
24
+ emitters: Collection<string, EventEmitter> = new Collection()
25
+
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>[] {
35
+ const result: InstanceType<T>[] = []
36
+
37
+ for (const ext of this.extensions) {
38
+ result.push(...this.getComponentsWithType(ext, type))
39
+ }
40
+
41
+ return result
42
+ }
43
+
44
+ getComponentsWithType<T extends typeof BaseComponent<Config>, Config>(ext: object, type: T): InstanceType<T>[] {
45
+ const componentStore = getComponentStore(ext)
46
+
47
+ return Array.from(componentStore.filter((x) => (x.constructor as unknown) === type).values() as Iterable<InstanceType<T>>)
48
+ }
49
+
50
+ registerEventListeners(ext: object) {
51
+ const listeners = this.getComponentsWithType(ext, ListenerComponent)
52
+
53
+ for (const listener of listeners) {
54
+ const emitter = this.emitters.get(listener.options.emitter)
55
+
56
+ if (emitter) {
57
+ const bound = listener.method.bind(ext)
58
+
59
+ Reflect.defineMetadata('bound', bound, listener)
60
+
61
+ emitter.addListener(listener.options.event, bound)
62
+ }
63
+ }
64
+ }
65
+
66
+ unregisterEventListeners(ext: object) {
67
+ const listeners = this.getComponentsWithType(ext, ListenerComponent)
68
+
69
+ for (const listener of listeners) {
70
+ const emitter = this.emitters.get(listener.options.emitter)
71
+ const bound = Reflect.getMetadata('bound', listener)
72
+
73
+ if (emitter && bound) {
74
+ emitter.removeListener(listener.options.event, bound)
75
+ }
76
+ }
77
+ }
8
78
 
9
79
  async registerModule(ext: object) {
80
+ Reflect.defineMetadata(CommandClientSymbol, this.client, ext)
81
+
82
+ this.registerEventListeners(ext)
10
83
  await this.runModuleHook(ext, 'load')
11
84
  this.extensions.push(ext)
85
+ this.logger.info(`Module registered: ${chalk.green(ext.constructor.name)}`)
12
86
  }
13
87
 
14
88
  async unregisterModule(ext: object) {
89
+ this.unregisterEventListeners(ext)
15
90
  await this.runModuleHook(ext, 'unload')
16
91
  _.remove(this.extensions, (x) => x === ext)
92
+ this.logger.info(`Module unregistered: ${chalk.green(ext.constructor.name)}`)
17
93
  }
18
94
 
19
95
  runModuleHook(ext: object, hookName: string, ...args: unknown[]) {
@@ -23,8 +99,12 @@ export class Registry {
23
99
 
24
100
  if (functions) {
25
101
  for (const fn of functions) {
26
- fn.apply(ext, args)
102
+ fn.call(ext, ...args)
27
103
  }
28
104
  }
29
105
  }
106
+
107
+ registerEventEmitter(name: string, emitter: EventEmitter) {
108
+ this.emitters.set(name, emitter)
109
+ }
30
110
  }
@@ -1 +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'
10
+ export * from './CommandClient'
@@ -1,3 +1,13 @@
1
- export const ComponentStoreSymbol = Symbol()
2
- export const ComponentArgStoreSymbol = Symbol()
3
- export const ModuleHookStoreSymbol = 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
@@ -8,3 +8,4 @@
8
8
 
9
9
  export * from './core'
10
10
  export * from './applicationCommand'
11
+ export * from './textCommand'
@@ -0,0 +1,11 @@
1
+ import { createComponentDecorator } from '../core/components/decoratorCreator'
2
+ import { BaseComponent } from '../core/components/BaseComponent'
3
+
4
+ type TextCommandOptions = {
5
+ name: string
6
+ description?: string
7
+ }
8
+
9
+ export class TextCommandComponent extends BaseComponent<TextCommandOptions> {}
10
+
11
+ export const command = createComponentDecorator(TextCommandComponent)
@@ -0,0 +1 @@
1
+ export * from './TextCommand'
package/test/index.ts CHANGED
@@ -1,22 +1,30 @@
1
- import { ApplicationCommandOptionType } from 'discord.js'
2
- import { applicationCommand, getComponentStore, moduleHook, option, Registry } from '../src'
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
+ */
3
8
 
4
- class Test {
9
+ import { ApplicationCommandOptionType, ApplicationCommandType, ChatInputCommandInteraction, Client } from 'discord.js'
10
+ import { applicationCommand, CommandClient, moduleHook, option, ownerOnly, listener, Extension } from '../dist'
11
+ import 'dotenv/config'
12
+ import { Logger } from 'tslog'
13
+ import chalk from 'chalk'
14
+
15
+ class Test extends Extension {
5
16
  @applicationCommand({
17
+ type: ApplicationCommandType.ChatInput,
6
18
  name: 'test',
7
19
  description: 'wow this is test',
8
20
  })
9
- async testCommand(
10
- @option({
11
- name: 'hello',
12
- description: '와아',
13
- type: ApplicationCommandOptionType.String,
14
- })
15
- hello: string,
16
- world: string,
17
- ) {}
21
+ async testCommand(i: ChatInputCommandInteraction) {
22
+ i.reply('Wow')
23
+ }
18
24
 
25
+ @ownerOnly
19
26
  @applicationCommand({
27
+ type: ApplicationCommandType.ChatInput,
20
28
  name: 'test2',
21
29
  description: 'wow this is test wow',
22
30
  })
@@ -25,29 +33,51 @@ class Test {
25
33
  name: 'sans',
26
34
  description: '와',
27
35
  type: ApplicationCommandOptionType.String,
36
+ required: true,
28
37
  })
29
38
  wa: string,
30
- ) {}
39
+ i: ChatInputCommandInteraction,
40
+ ) {
41
+ i.reply(wa)
42
+ }
31
43
 
32
44
  @moduleHook('load')
33
45
  load() {
34
- console.log('load')
46
+ this.logger.info('Load')
35
47
  }
36
48
 
37
49
  @moduleHook('unload')
38
50
  unload() {
39
- 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()
40
58
  }
41
59
  }
42
60
 
43
61
  const ext = new Test()
44
62
 
45
- const registry = new Registry()
63
+ const client = new Client({ intents: [] })
64
+
65
+ const logger = new Logger({ dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone })
66
+
67
+ const cc = new CommandClient(client, logger)
68
+
69
+ const registry = cc.registry
46
70
 
47
71
  const run = async () => {
72
+ await cc.enableApplicationCommandsExtension({
73
+ guilds: ['832938554438844438'],
74
+ })
75
+
48
76
  await registry.registerModule(ext)
49
77
 
50
- await registry.unregisterModule(ext)
78
+ await client.login(process.env.TOKEN)
79
+
80
+ await cc.getApplicationCommandsExtension()!.sync()
51
81
  }
52
82
 
53
83
  run()
package/tsconfig.json CHANGED
@@ -15,11 +15,10 @@
15
15
  "declaration": true,
16
16
  /* Generates corresponding '.d.ts' file. */
17
17
  // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
18
- "sourceMap": true, /* Generates corresponding '.map' file. */
18
+ "sourceMap": true /* Generates corresponding '.map' file. */,
19
19
  // "outFile": "./", /* Concatenate and emit output to single file. */
20
20
  "outDir": "./dist",
21
21
  /* Redirect output structure to the directory. */
22
- "rootDir": "./src",
23
22
  /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
24
23
  // "composite": true, /* Enable project compilation */
25
24
  // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
@@ -31,6 +30,7 @@
31
30
 
32
31
  /* Strict Type-Checking Options */
33
32
  "strict": true,
33
+
34
34
  /* Enable all strict type-checking options. */
35
35
  // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
36
36
  // "strictNullChecks": true, /* Enable strict null checks. */
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
- "exclude": ["node_modules", "scripts", "test", "tsup.config.ts"]
4
- }
3
+ "exclude": ["node_modules", "scripts", "test", "tsup.config.ts"],
4
+ "include": ["src"],
5
+ "compilerOptions": {
6
+ "rootDir": "./src",
7
+ }
8
+ }
package/tsup.config.ts CHANGED
@@ -15,4 +15,5 @@ export default defineConfig({
15
15
  clean: true,
16
16
  dts: true,
17
17
  minify: true,
18
+ tsconfig: 'tsconfig.prod.json',
18
19
  })