Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
K
Koishi Nestjs
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
3rdeye
Koishi Nestjs
Commits
7af29461
Commit
7af29461
authored
Oct 31, 2021
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
interceptor in context
parent
489d798f
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
246 additions
and
23 deletions
+246
-23
README.md
README.md
+55
-0
src/koishi-interceptor-manager/koishi-interceptor-manager.service.ts
...interceptor-manager/koishi-interceptor-manager.service.ts
+35
-0
src/koishi.module.ts
src/koishi.module.ts
+2
-0
src/koishi.service.ts
src/koishi.service.ts
+50
-2
src/providers/koishi-injection.service.ts
src/providers/koishi-injection.service.ts
+16
-2
src/providers/koishi-metascan.service.ts
src/providers/koishi-metascan.service.ts
+17
-19
src/utility/koishi.interfaces.ts
src/utility/koishi.interfaces.ts
+1
-0
src/utility/replaced-context.ts
src/utility/replaced-context.ts
+70
-0
No files found.
README.md
View file @
7af29461
...
...
@@ -89,6 +89,8 @@ Koishi-Nest 的配置项和 Koishi 配置项一致,参照 [Koishi 文档](http
*
`module`
Nest 模块名。
*
上下文选择器见本文
**上下文选择器**
部分。
*
`globalInterceptors`
全局命令拦截器,详见
**命令拦截器**
部分。
插件的使用可以参考
[
Koishi 文档
](
https://koishi.js.org/v4/guide/plugin/plugin.html
)
。
`moduleSelection`
的使用见本文
**复用性**
部分。
...
...
@@ -333,6 +335,59 @@ Koishi-Nest 使用一组装饰器进行描述指令的行为。这些装饰器
*
`@PutUserName(useDatabase: boolean = true)`
注入当前用户的用户名。
*
`useDatabase`
是否尝试从数据库获取用户名。
### 指令拦截器
和 Koishi 中的
[
`command.before`
](
https://koishi.js.org/v4/guide/message/command.html#%E4%BD%BF%E7%94%A8%E6%A3%80%E6%9F%A5%E5%99%A8
)
对应,Koishi-Nest 提供了
**指令拦截器**
,便于在指令运行之前进行一些操作,如参数检查,记录日志等。
#### 定义
指令拦截器需要实现
`KoishiCommandInterceptor`
接口,提供
`intercept`
方法。该方法的参数与
`command.before`
的回调函数一致。
> 不要将指令拦截器与 Nest.js 的拦截器混淆。
下面是一个指令拦截器的例子。
```
ts
import
{
KoishiCommandInterceptor
}
from
"
koishi-nestjs
"
;
export
class
MyCommandInterceptor
implements
KoishiCommandInterceptor
{
intercept
(
argv
:
Argv
,
arg1
:
string
)
{
if
(
arg1
.
startsWith
(
'
foo
'
))
{
return
'
Intercepted!
'
;
}
}
}
```
#### 注册
要注册拦截器,只需要在指令对应的提供者方法或提供者本人使用
`@CommandInterceptors`
装饰器即可。也可以指定多个拦截器。
其中,在注册过拦截器的提供者类中,使用
`@InjectContext()`
或类似方法注入的上下文对象,也会应用拦截器。
> 这些上下文内安装的 Koishi 插件不会应用拦截器。
```
ts
import
{
InjectContext
}
from
'
koishi-nestjs
'
;
import
{
Context
}
from
'
koishi
'
;
@
Injectable
()
// 可以在提供者类中指定上下文选择器,等价于 `ctx.guild('111111111')`
@
CommandInterceptors
(
MyGlobalInterceptor
)
export
class
AppService
{
// 这里的 Koishi 上下文注册的任何指令也会应用拦截器
constructor
(@
InjectContext
()
private
ctx
:
Context
)
{}
@
UseCommand
(
'
my-echo <content:string>
'
)
@
CommandInterceptors
(
MyInterceptor1
,
MyInterceptor2
)
// 可以指定多个拦截器。
testEchoCommand
(@
PutArgv
()
argv
:
Argv
,
@
PutArg
(
0
)
content
:
string
)
{
return
content
;
}
}
```
也可以在 Koishi-Nest 启动配置中,使用
`globalInterceptors`
方法注册拦截器。
## 上下文 Service 交互
...
...
src/koishi-interceptor-manager/koishi-interceptor-manager.service.ts
0 → 100644
View file @
7af29461
import
{
Injectable
}
from
'
@nestjs/common
'
;
import
{
ModuleRef
}
from
'
@nestjs/core
'
;
import
{
KoishiCommandInterceptorRegistration
}
from
'
../utility/koishi.interfaces
'
;
import
{
Command
}
from
'
koishi
'
;
@
Injectable
()
export
class
KoishiInterceptorManagerService
{
constructor
(
private
readonly
moduleRef
:
ModuleRef
)
{}
getInterceptor
(
interceptorDef
:
KoishiCommandInterceptorRegistration
)
{
if
(
typeof
interceptorDef
!==
'
object
'
)
{
return
this
.
moduleRef
.
get
(
interceptorDef
,
{
strict
:
false
});
}
return
interceptorDef
;
}
addInterceptor
(
command
:
Command
,
interceptorDef
:
KoishiCommandInterceptorRegistration
,
)
{
const
interceptor
=
this
.
getInterceptor
(
interceptorDef
);
command
.
before
((...
params
)
=>
interceptor
.
intercept
(...
params
));
}
addInterceptors
(
command
:
Command
,
interceptorDefs
:
KoishiCommandInterceptorRegistration
[],
)
{
if
(
!
interceptorDefs
)
{
return
;
}
interceptorDefs
.
forEach
((
interceptorDef
)
=>
this
.
addInterceptor
(
command
,
interceptorDef
),
);
}
}
src/koishi.module.ts
View file @
7af29461
...
...
@@ -27,6 +27,7 @@ import { KoishiContextService } from './providers/koishi-context.service';
import
{
KoishiHttpDiscoveryService
}
from
'
./koishi-http-discovery/koishi-http-discovery.service
'
;
import
{
KoishiWebsocketGateway
}
from
'
./providers/koishi-websocket.gateway
'
;
import
{
KoishiMetadataFetcherService
}
from
'
./koishi-metadata-fetcher/koishi-metadata-fetcher.service
'
;
import
{
KoishiInterceptorManagerService
}
from
'
./koishi-interceptor-manager/koishi-interceptor-manager.service
'
;
const
koishiContextProvider
:
Provider
<
Context
>
=
{
provide
:
KOISHI_CONTEXT
,
...
...
@@ -47,6 +48,7 @@ const koishiContextProvider: Provider<Context> = {
KoishiInjectionService
,
KoishiHttpDiscoveryService
,
KoishiMetadataFetcherService
,
KoishiInterceptorManagerService
,
],
exports
:
[
KoishiService
,
koishiContextProvider
],
})
...
...
src/koishi.service.ts
View file @
7af29461
import
{
App
,
Router
}
from
'
koishi
'
;
import
{
App
,
Command
,
Context
,
Router
}
from
'
koishi
'
;
import
{
Inject
,
Injectable
,
...
...
@@ -6,7 +6,10 @@ import {
OnModuleDestroy
,
OnModuleInit
,
}
from
'
@nestjs/common
'
;
import
{
KoishiModuleOptions
}
from
'
./utility/koishi.interfaces
'
;
import
{
KoishiCommandInterceptorRegistration
,
KoishiModuleOptions
,
}
from
'
./utility/koishi.interfaces
'
;
import
{
Server
}
from
'
http
'
;
import
Koa
from
'
koa
'
;
import
KoaBodyParser
from
'
koa-bodyparser
'
;
...
...
@@ -15,6 +18,7 @@ import { applySelector } from './utility/koishi.utility';
import
{
KOISHI_MODULE_OPTIONS
}
from
'
./utility/koishi.constants
'
;
import
{
KoishiLoggerService
}
from
'
./providers/koishi-logger.service
'
;
import
{
KoishiHttpDiscoveryService
}
from
'
./koishi-http-discovery/koishi-http-discovery.service
'
;
import
{
Filter
,
ReplacedContext
}
from
'
./utility/replaced-context
'
;
@
Injectable
()
export
class
KoishiService
...
...
@@ -71,4 +75,48 @@ export class KoishiService
async
onModuleDestroy
()
{
await
this
.
stop
();
}
addInterceptors
(
command
:
Command
,
interceptorDefs
:
KoishiCommandInterceptorRegistration
[],
)
{
return
this
.
metascan
.
addInterceptors
(
command
,
interceptorDefs
);
}
private
cloneContext
(
filter
:
Filter
,
interceptors
:
KoishiCommandInterceptorRegistration
[]
=
[],
):
Context
{
return
new
ReplacedContext
(
filter
,
this
,
null
,
[
...(
this
.
koishiModuleOptions
.
globalInterceptors
||
[]),
...
interceptors
,
]);
}
withInterceptors
(
interceptors
:
KoishiCommandInterceptorRegistration
[])
{
return
this
.
cloneContext
(()
=>
true
,
interceptors
);
}
any
()
{
return
this
.
cloneContext
(()
=>
true
);
}
never
()
{
return
this
.
cloneContext
(()
=>
false
);
}
union
(
arg
:
Filter
|
Context
)
{
const
filter
=
typeof
arg
===
'
function
'
?
arg
:
arg
.
filter
;
return
this
.
cloneContext
((
s
)
=>
this
.
filter
(
s
)
||
filter
(
s
));
}
intersect
(
arg
:
Filter
|
Context
)
{
const
filter
=
typeof
arg
===
'
function
'
?
arg
:
arg
.
filter
;
return
this
.
cloneContext
((
s
)
=>
this
.
filter
(
s
)
&&
filter
(
s
));
}
except
(
arg
:
Filter
|
Context
)
{
const
filter
=
typeof
arg
===
'
function
'
?
arg
:
arg
.
filter
;
return
this
.
cloneContext
((
s
)
=>
this
.
filter
(
s
)
&&
!
filter
(
s
));
}
}
src/providers/koishi-injection.service.ts
View file @
7af29461
import
{
Injectable
}
from
'
@nestjs/common
'
;
import
{
Inject
,
Inject
able
}
from
'
@nestjs/common
'
;
import
{
KoishiService
}
from
'
../koishi.service
'
;
import
{
KoishiContextService
}
from
'
./koishi-context.service
'
;
import
{
ModulesContainer
}
from
'
@nestjs/core
'
;
import
{
KoishiMetadataFetcherService
}
from
'
../koishi-metadata-fetcher/koishi-metadata-fetcher.service
'
;
import
{
KOISHI_MODULE_OPTIONS
,
KoishiCommandInterceptorDef
,
}
from
'
../utility/koishi.constants
'
;
import
{
KoishiModuleOptions
}
from
'
../utility/koishi.interfaces
'
;
import
{
Context
}
from
'
koishi
'
;
@
Injectable
()
export
class
KoishiInjectionService
{
constructor
(
private
readonly
koishi
:
KoishiService
,
private
readonly
ctxService
:
KoishiContextService
,
private
readonly
metaFetcher
:
KoishiMetadataFetcherService
,
private
moduleContainer
:
ModulesContainer
,
@
Inject
(
KOISHI_MODULE_OPTIONS
)
private
readonly
koishiModuleOptions
:
KoishiModuleOptions
,
)
{}
getInjectContext
(
inquerier
:
string
|
any
)
{
let
ctx
=
this
.
koishi
.
any
();
const
token
=
typeof
inquerier
===
'
string
'
?
inquerier
:
inquerier
.
constructor
;
const
interceptors
=
this
.
metaFetcher
.
getMetadataArray
(
KoishiCommandInterceptorDef
,
token
,
);
let
ctx
:
Context
=
this
.
koishi
.
withInterceptors
(
interceptors
);
for
(
const
module
of
this
.
moduleContainer
.
values
())
{
if
(
module
.
hasProvider
(
token
)
||
module
.
controllers
.
has
(
token
))
{
ctx
=
this
.
ctxService
.
getModuleCtx
(
ctx
,
module
);
...
...
src/providers/koishi-metascan.service.ts
View file @
7af29461
import
{
Injectable
}
from
'
@nestjs/common
'
;
import
{
Inject
,
Inject
able
}
from
'
@nestjs/common
'
;
import
{
MetadataScanner
,
ModuleRef
,
ModulesContainer
}
from
'
@nestjs/core
'
;
import
{
Argv
,
Command
,
Context
,
User
}
from
'
koishi
'
;
import
{
InstanceWrapper
}
from
'
@nestjs/core/injector/instance-wrapper
'
;
import
{
KOISHI_MODULE_OPTIONS
,
KoishiCommandDefinition
,
KoishiCommandInterceptorDef
,
KoishiDoRegister
,
...
...
@@ -16,6 +17,7 @@ import {
DoRegisterConfig
,
EventName
,
KoishiCommandInterceptorRegistration
,
KoishiModuleOptions
,
KoishiModulePlugin
,
}
from
'
../utility/koishi.interfaces
'
;
import
{
applySelector
}
from
'
../utility/koishi.utility
'
;
...
...
@@ -23,6 +25,7 @@ import _ from 'lodash';
import
{
KoishiContextService
}
from
'
./koishi-context.service
'
;
import
{
Module
}
from
'
@nestjs/core/injector/module
'
;
import
{
KoishiMetadataFetcherService
}
from
'
../koishi-metadata-fetcher/koishi-metadata-fetcher.service
'
;
import
{
KoishiInterceptorManagerService
}
from
'
../koishi-interceptor-manager/koishi-interceptor-manager.service
'
;
@
Injectable
()
export
class
KoishiMetascanService
{
...
...
@@ -32,6 +35,9 @@ export class KoishiMetascanService {
private
readonly
moduleContainer
:
ModulesContainer
,
private
readonly
ctxService
:
KoishiContextService
,
private
readonly
metaFetcher
:
KoishiMetadataFetcherService
,
@
Inject
(
KOISHI_MODULE_OPTIONS
)
private
readonly
koishiModuleOptions
:
KoishiModuleOptions
,
private
readonly
intercepterManager
:
KoishiInterceptorManagerService
,
)
{}
private
preRegisterCommandActionArg
(
config
:
CommandPutConfig
,
cmd
:
Command
)
{
...
...
@@ -107,19 +113,11 @@ export class KoishiMetascanService {
}
}
private
getInterceptor
(
interceptorDef
:
KoishiCommandInterceptorRegistration
)
{
if
(
typeof
interceptorDef
!==
'
object
'
)
{
return
this
.
moduleRef
.
get
(
interceptorDef
,
{
strict
:
false
});
}
return
interceptorDef
;
}
private
addInterceptor
(
addInterceptors
(
command
:
Command
,
interceptorDef
:
KoishiCommandInterceptorRegistration
,
interceptorDef
s
:
KoishiCommandInterceptorRegistration
[]
,
)
{
const
interceptor
=
this
.
getInterceptor
(
interceptorDef
);
command
.
before
((...
params
)
=>
interceptor
.
intercept
(...
params
));
return
this
.
intercepterManager
.
addInterceptors
(
command
,
interceptorDefs
);
}
private
async
handleInstanceRegistration
(
...
...
@@ -187,14 +185,14 @@ export class KoishiMetascanService {
for
(
const
commandDef
of
commandDefs
)
{
command
=
commandDef
(
command
)
||
command
;
}
const
interceptorDefs
=
this
.
metaFetcher
.
getPropertyMetadataArray
(
KoishiCommandInterceptorDef
,
instance
,
methodKey
,
const
interceptorDefs
=
_
.
uniq
(
this
.
metaFetcher
.
getPropertyMetadataArray
(
KoishiCommandInterceptorDef
,
instance
,
methodKey
,
),
);
for
(
const
interceptorDef
of
interceptorDefs
)
{
this
.
addInterceptor
(
command
,
interceptorDef
);
}
this
.
addInterceptors
(
command
,
interceptorDefs
);
if
(
!
commandData
.
putOptions
)
{
command
.
action
((
argv
:
Argv
,
...
args
:
any
[])
=>
methodFun
.
call
(
instance
,
argv
,
...
args
),
...
...
src/utility/koishi.interfaces.ts
View file @
7af29461
...
...
@@ -67,6 +67,7 @@ export interface KoishiModuleOptions
loggerPrefix
?:
string
;
loggerColor
?:
number
;
moduleSelection
?:
KoishiModuleSelection
[];
globalInterceptors
?:
KoishiCommandInterceptorRegistration
[];
}
export
interface
KoishiModuleOptionsFactory
{
...
...
src/utility/replaced-context.ts
0 → 100644
View file @
7af29461
import
{
Argv
,
Command
,
Context
,
Plugin
,
Session
}
from
'
koishi
'
;
import
{
KoishiService
}
from
'
../koishi.service
'
;
import
{
KoishiCommandInterceptorRegistration
}
from
'
./koishi.interfaces
'
;
export
type
Filter
=
(
session
:
Session
)
=>
boolean
;
export
class
ReplacedContext
extends
Context
{
constructor
(
private
_filter
:
Filter
,
private
_app
:
KoishiService
,
private
__plugin
:
Plugin
=
null
,
public
_interceptors
:
KoishiCommandInterceptorRegistration
[]
=
[],
)
{
super
(
_filter
,
_app
,
__plugin
);
}
private
cloneContext
(
filter
:
Filter
,
interceptors
:
KoishiCommandInterceptorRegistration
[]
=
[],
):
Context
{
return
new
ReplacedContext
(
filter
,
this
.
_app
,
this
.
__plugin
,
[
...
this
.
_interceptors
,
...
interceptors
,
]);
}
withInterceptors
(
interceptors
:
KoishiCommandInterceptorRegistration
[])
{
return
this
.
cloneContext
(
this
.
filter
,
interceptors
);
}
any
()
{
return
this
.
cloneContext
(()
=>
true
);
}
never
()
{
return
this
.
cloneContext
(()
=>
false
);
}
union
(
arg
:
Filter
|
Context
)
{
const
filter
=
typeof
arg
===
'
function
'
?
arg
:
arg
.
filter
;
return
this
.
cloneContext
((
s
)
=>
this
.
filter
(
s
)
||
filter
(
s
));
}
intersect
(
arg
:
Filter
|
Context
)
{
const
filter
=
typeof
arg
===
'
function
'
?
arg
:
arg
.
filter
;
return
this
.
cloneContext
((
s
)
=>
this
.
filter
(
s
)
&&
filter
(
s
));
}
except
(
arg
:
Filter
|
Context
)
{
const
filter
=
typeof
arg
===
'
function
'
?
arg
:
arg
.
filter
;
return
this
.
cloneContext
((
s
)
=>
this
.
filter
(
s
)
&&
!
filter
(
s
));
}
command
<
D
extends
string
>
(
def
:
D
,
config
?:
Command
.
Config
,
):
Command
<
never
,
never
,
Argv
.
ArgumentType
<
D
>>
;
command
<
D
extends
string
>
(
def
:
D
,
desc
:
string
,
config
?:
Command
.
Config
,
):
Command
<
never
,
never
,
Argv
.
ArgumentType
<
D
>>
;
command
(
def
:
string
,
...
args
:
[
Command
.
Config
?]
|
[
string
,
Command
.
Config
?])
{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const
cmd
=
super
.
command
(
def
,
...
args
);
this
.
_app
.
addInterceptors
(
cmd
,
this
.
_interceptors
);
return
cmd
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment