@pikokr/command.ts 5.0.0-dev.daaf714 → 5.0.1
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.
- package/.github/workflows/codeql-analysis.yml +29 -29
- package/.github/workflows/docs.yml +6 -6
- package/.github/workflows/publish.stable.yml +2 -2
- package/.github/workflows/publish.yml +2 -2
- package/.vscode/settings.json +9 -9
- package/README.md +2 -0
- package/dist/index.d.ts +158 -47
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/docs/index.yml +1 -1
- package/package.json +18 -15
- package/publish-version.js +10 -0
- package/renovate.json +5 -0
- package/src/applicationCommand/ApplicationCommand.ts +14 -14
- package/src/applicationCommand/ApplicationCommandExtension.ts +169 -0
- package/src/applicationCommand/ApplicationCommandOption.ts +9 -2
- package/src/applicationCommand/index.ts +8 -0
- package/src/core/components/BaseComponent.ts +41 -11
- package/src/core/components/ComponentArgument.ts +8 -0
- package/src/core/components/ComponentArgumentDecorator.ts +9 -1
- package/src/core/components/decoratorCreator.ts +23 -10
- package/src/core/components/index.ts +13 -3
- package/src/core/converter/index.ts +16 -0
- package/src/core/extensions/CTSExtension.ts +17 -0
- package/src/core/extensions/Extension.ts +62 -0
- package/src/core/extensions/index.ts +9 -0
- package/src/core/hooks/componentHook.ts +40 -0
- package/src/core/hooks/index.ts +11 -1
- package/src/core/hooks/moduleHook.ts +11 -3
- package/src/core/index.ts +13 -1
- package/src/core/listener/index.ts +29 -0
- package/src/core/structures/CommandClient.ts +78 -0
- package/src/core/structures/Registry.ts +156 -6
- package/src/core/structures/index.ts +9 -0
- package/src/core/symbols.ts +14 -3
- package/src/core/utils/checks.ts +27 -0
- package/src/core/utils/errors.ts +9 -0
- package/src/core/utils/index.ts +10 -0
- package/src/index.ts +7 -6
- package/src/textCommand/TextCommand.ts +20 -0
- package/src/textCommand/TextCommandExtension.ts +128 -0
- package/src/textCommand/index.ts +11 -0
- package/src/textCommand/parameters.ts +14 -0
- package/test/index.ts +57 -17
- package/tsconfig.json +2 -3
- package/tsconfig.prod.json +6 -2
- package/tsup.config.ts +8 -8
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* File: moduleHook.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
|
import { Collection } from 'discord.js'
|
|
2
10
|
import { ModuleHookStoreSymbol } from '../symbols'
|
|
3
11
|
|
|
4
|
-
type ModuleHookStore = Collection<string
|
|
12
|
+
type ModuleHookStore = Collection<string, Function[]>
|
|
5
13
|
|
|
6
14
|
export const getModuleHookStore = (target: object) => {
|
|
7
15
|
let result: ModuleHookStore | null = Reflect.getMetadata(ModuleHookStoreSymbol, target)
|
|
@@ -19,11 +27,11 @@ export const moduleHook = (name: string): MethodDecorator => {
|
|
|
19
27
|
return (target, key) => {
|
|
20
28
|
const store = getModuleHookStore(target)
|
|
21
29
|
|
|
22
|
-
let v = store.get(
|
|
30
|
+
let v = store.get(name)
|
|
23
31
|
|
|
24
32
|
if (!v) {
|
|
25
33
|
v = []
|
|
26
|
-
store.set(
|
|
34
|
+
store.set(name, v)
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
v.push(Reflect.get(target, key))
|
package/src/core/index.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
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 './components'
|
|
2
|
-
export * from './structures'
|
|
3
10
|
export * from './hooks'
|
|
11
|
+
export * from './converter'
|
|
12
|
+
export * from './utils'
|
|
13
|
+
export * from './listener'
|
|
14
|
+
export * from './structures'
|
|
15
|
+
export * from './extensions'
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
import { BaseComponent } from '../components/BaseComponent'
|
|
10
|
+
import { createComponentDecorator } from '../components/decoratorCreator'
|
|
11
|
+
|
|
12
|
+
export class ListenerComponent extends BaseComponent<{ emitter: string; event: string }, { emitter?: string; event: string }> {
|
|
13
|
+
defaultOptions() {
|
|
14
|
+
return { emitter: 'discord' }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor(options: ListenerComponent['options'], method: Function, argTypes: unknown[]) {
|
|
18
|
+
super(
|
|
19
|
+
{
|
|
20
|
+
event: options.event,
|
|
21
|
+
emitter: options.emitter ?? 'discord',
|
|
22
|
+
},
|
|
23
|
+
method,
|
|
24
|
+
argTypes,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const listener = createComponentDecorator(ListenerComponent)
|
|
@@ -0,0 +1,78 @@
|
|
|
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 { TextCommandConfig } from '../../textCommand'
|
|
15
|
+
import { TextCommandExtension } from '../../textCommand/TextCommandExtension'
|
|
16
|
+
import { CommandClientSymbol } from '../symbols'
|
|
17
|
+
import { Registry } from './Registry'
|
|
18
|
+
export class CommandClient extends EventEmitter {
|
|
19
|
+
ctsLogger: Logger
|
|
20
|
+
registry: Registry
|
|
21
|
+
|
|
22
|
+
owners: Set<Snowflake> = new Set()
|
|
23
|
+
|
|
24
|
+
constructor(public discord: Client, public logger: Logger = new Logger({ dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone })) {
|
|
25
|
+
super()
|
|
26
|
+
|
|
27
|
+
this.ctsLogger = logger.getChildLogger({ prefix: [chalk.blue('[command.ts]')], displayFilePath: 'hidden', displayFunctionName: false })
|
|
28
|
+
|
|
29
|
+
this.registry = new Registry(this.ctsLogger, this)
|
|
30
|
+
|
|
31
|
+
this.registry.registerEventEmitter('cts', this)
|
|
32
|
+
this.registry.registerEventEmitter('discord', this.discord)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async fetchOwners() {
|
|
36
|
+
if (!this.discord.application) throw new Error('The client is not logged in.')
|
|
37
|
+
|
|
38
|
+
this.ctsLogger.info('Fetching owners...')
|
|
39
|
+
|
|
40
|
+
await this.discord.application.fetch()
|
|
41
|
+
|
|
42
|
+
const owner = this.discord.application.owner
|
|
43
|
+
|
|
44
|
+
if (!owner) throw new Error('Cannot find application owner')
|
|
45
|
+
|
|
46
|
+
const owners: string[] = []
|
|
47
|
+
|
|
48
|
+
if (owner instanceof User) {
|
|
49
|
+
this.owners.add(owner.id)
|
|
50
|
+
owners.push(owner.tag)
|
|
51
|
+
} else if (owner instanceof Team) {
|
|
52
|
+
for (const [id, member] of owner.members) {
|
|
53
|
+
this.owners.add(id)
|
|
54
|
+
owners.push(member.user.tag)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.ctsLogger.info(`Fetched ${chalk.green(owners.length)} owners(${owners.map((x) => chalk.blue(x)).join(', ')})`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async enableApplicationCommandsExtension(config: ApplicationCommandExtensionConfig) {
|
|
62
|
+
await this.registry.registerModule(new ApplicationCommandExtension(config))
|
|
63
|
+
this.ctsLogger.info('Application command extension enabled.')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async enableTextCommandsExtension(config: TextCommandConfig) {
|
|
67
|
+
await this.registry.registerModule(new TextCommandExtension(config))
|
|
68
|
+
this.ctsLogger.info('Text command extension enabled.')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getApplicationCommandsExtension() {
|
|
72
|
+
return this.registry.extensions.find((x) => x.constructor === ApplicationCommandExtension) as ApplicationCommandExtension | undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static getFromModule(ext: object): CommandClient {
|
|
76
|
+
return Reflect.getMetadata(CommandClientSymbol, ext)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,30 +1,176 @@
|
|
|
1
|
-
|
|
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'
|
|
12
|
+
import _, { result } from 'lodash'
|
|
13
|
+
import { Logger } from 'tslog'
|
|
2
14
|
import { getComponentStore } from '../components'
|
|
15
|
+
import type { BaseComponent } from '../components'
|
|
3
16
|
import { getModuleHookStore } from '../hooks'
|
|
17
|
+
import { ListenerComponent } from '../listener'
|
|
18
|
+
import { CommandClientSymbol, FilePathSymbol } from '../symbols'
|
|
19
|
+
import { CommandClient } from './CommandClient'
|
|
20
|
+
import walkSync from 'walk-sync'
|
|
21
|
+
import path from 'path'
|
|
4
22
|
|
|
5
23
|
export class Registry {
|
|
6
24
|
extensions: object[] = []
|
|
7
25
|
|
|
8
|
-
|
|
9
|
-
|
|
26
|
+
emitters: Collection<string, EventEmitter> = new Collection()
|
|
27
|
+
|
|
28
|
+
logger: Logger
|
|
29
|
+
|
|
30
|
+
constructor(logger: Logger, public client: CommandClient) {
|
|
31
|
+
this.logger = logger.getChildLogger({
|
|
32
|
+
prefix: [chalk.green('[Registry]')],
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getComponentsWithTypeGlobal<T extends typeof BaseComponent<Config>, Config>(type: T): InstanceType<T>[] {
|
|
37
|
+
const result: InstanceType<T>[] = []
|
|
10
38
|
|
|
11
39
|
for (const ext of this.extensions) {
|
|
12
|
-
|
|
40
|
+
result.push(...this.getComponentsWithType(ext, type))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getComponentsWithType<T extends typeof BaseComponent<Config>, Config>(ext: object, type: T): InstanceType<T>[] {
|
|
47
|
+
const componentStore = getComponentStore(ext)
|
|
48
|
+
|
|
49
|
+
return Array.from(componentStore.filter((x) => (x.constructor as unknown) === type).values() as Iterable<InstanceType<T>>)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
registerEventListeners(ext: object) {
|
|
53
|
+
const listeners = this.getComponentsWithType(ext, ListenerComponent)
|
|
54
|
+
|
|
55
|
+
for (const listener of listeners) {
|
|
56
|
+
const emitter = this.emitters.get(listener.options.emitter)
|
|
13
57
|
|
|
14
|
-
|
|
58
|
+
if (emitter) {
|
|
59
|
+
const bound = listener.method.bind(ext)
|
|
60
|
+
|
|
61
|
+
Reflect.defineMetadata('bound', bound, listener)
|
|
62
|
+
|
|
63
|
+
emitter.addListener(listener.options.event, bound)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
unregisterEventListeners(ext: object) {
|
|
69
|
+
const listeners = this.getComponentsWithType(ext, ListenerComponent)
|
|
70
|
+
|
|
71
|
+
for (const listener of listeners) {
|
|
72
|
+
const emitter = this.emitters.get(listener.options.emitter)
|
|
73
|
+
const bound = Reflect.getMetadata('bound', listener)
|
|
74
|
+
|
|
75
|
+
if (emitter && bound) {
|
|
76
|
+
emitter.removeListener(listener.options.event, bound)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async loadAllModulesInDirectory(dir: string): Promise<object[]> {
|
|
82
|
+
const results: object[] = []
|
|
83
|
+
|
|
84
|
+
const files = walkSync(dir).filter((x) => x.endsWith('.ts') || x.endsWith('.js'))
|
|
85
|
+
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
try {
|
|
88
|
+
const p = path.join(dir, file)
|
|
89
|
+
const mod = require(p)
|
|
90
|
+
|
|
91
|
+
if (typeof mod.setup !== 'function') continue
|
|
92
|
+
|
|
93
|
+
const modules = await mod.setup()
|
|
94
|
+
|
|
95
|
+
results.push(...(await this.registerModules(modules, p)))
|
|
96
|
+
} catch (e) {
|
|
97
|
+
this.logger.error(`Failed to load ${file}`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return results
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async registerModules(modules: object | object[], p: string) {
|
|
105
|
+
const results: object[] = []
|
|
106
|
+
if (modules instanceof Array) {
|
|
107
|
+
for (const module of modules) {
|
|
108
|
+
await this.registerModule(module)
|
|
109
|
+
Reflect.defineMetadata(FilePathSymbol, p, module)
|
|
110
|
+
results.push(module)
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
await this.registerModule(modules)
|
|
114
|
+
Reflect.defineMetadata(FilePathSymbol, p, modules)
|
|
115
|
+
results.push(modules)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return results
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async reloadModules() {
|
|
122
|
+
const result: { file: string; result: boolean; error?: Error }[] = []
|
|
123
|
+
const paths = new Set<string>()
|
|
124
|
+
for (const module of this.extensions) {
|
|
125
|
+
const file = Reflect.getMetadata(FilePathSymbol, module)
|
|
126
|
+
if (!file) continue
|
|
127
|
+
|
|
128
|
+
paths.add(file)
|
|
129
|
+
|
|
130
|
+
await this.unregisterModule(module)
|
|
131
|
+
delete require.cache[require.resolve(file)]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const path of paths) {
|
|
135
|
+
try {
|
|
136
|
+
const mod = require(path)
|
|
137
|
+
|
|
138
|
+
if (typeof mod.setup !== 'function') continue
|
|
139
|
+
|
|
140
|
+
const modules = await mod.setup()
|
|
141
|
+
|
|
142
|
+
await this.registerModules(modules, path)
|
|
143
|
+
|
|
144
|
+
result.push({
|
|
145
|
+
file: path,
|
|
146
|
+
result: true,
|
|
147
|
+
})
|
|
148
|
+
} catch (e) {
|
|
149
|
+
result.push({
|
|
150
|
+
file: path,
|
|
151
|
+
result: false,
|
|
152
|
+
error: e as Error,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
15
155
|
}
|
|
16
156
|
|
|
17
157
|
return result
|
|
18
158
|
}
|
|
19
159
|
|
|
20
160
|
async registerModule(ext: object) {
|
|
161
|
+
Reflect.defineMetadata(CommandClientSymbol, this.client, ext)
|
|
162
|
+
|
|
163
|
+
this.registerEventListeners(ext)
|
|
21
164
|
await this.runModuleHook(ext, 'load')
|
|
22
165
|
this.extensions.push(ext)
|
|
166
|
+
this.logger.info(`Module registered: ${chalk.green(ext.constructor.name)}`)
|
|
23
167
|
}
|
|
24
168
|
|
|
25
169
|
async unregisterModule(ext: object) {
|
|
170
|
+
this.unregisterEventListeners(ext)
|
|
26
171
|
await this.runModuleHook(ext, 'unload')
|
|
27
172
|
_.remove(this.extensions, (x) => x === ext)
|
|
173
|
+
this.logger.info(`Module unregistered: ${chalk.green(ext.constructor.name)}`)
|
|
28
174
|
}
|
|
29
175
|
|
|
30
176
|
runModuleHook(ext: object, hookName: string, ...args: unknown[]) {
|
|
@@ -34,8 +180,12 @@ export class Registry {
|
|
|
34
180
|
|
|
35
181
|
if (functions) {
|
|
36
182
|
for (const fn of functions) {
|
|
37
|
-
fn.
|
|
183
|
+
fn.call(ext, ...args)
|
|
38
184
|
}
|
|
39
185
|
}
|
|
40
186
|
}
|
|
187
|
+
|
|
188
|
+
registerEventEmitter(name: string, emitter: EventEmitter) {
|
|
189
|
+
this.emitters.set(name, emitter)
|
|
190
|
+
}
|
|
41
191
|
}
|
package/src/core/symbols.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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()
|
|
14
|
+
export const FilePathSymbol = 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
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
8
|
|
|
9
9
|
export * from './core'
|
|
10
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)
|