@zhin.js/core 1.5.11 → 1.5.13
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/README.md +2 -2
- package/lib/adapter.d.ts +3 -0
- package/lib/built/ai-trigger.d.ts +0 -2
- package/lib/built/ai-trigger.js +0 -1
- package/lib/built/inbound-pipeline.js +0 -1
- package/lib/built/inbound-runner.js +0 -2
- package/lib/built/login-assist.d.ts +22 -4
- package/lib/built/login-assist.js +71 -7
- package/lib/built/message-enrich.d.ts +0 -1
- package/lib/built/message-enrich.js +0 -1
- package/lib/built/rich-segments/markdown-to-text.js +2 -2
- package/lib/built/schema-endpoint-manager.js +6 -6
- package/lib/built/segment-contract/types.d.ts +4 -45
- package/lib/built/segment-contract/types.js +0 -4
- package/lib/built/user-interaction.d.ts +30 -0
- package/lib/built/user-interaction.js +248 -0
- package/lib/feature/handler.d.ts +1 -1
- package/lib/feature/handler.js +1 -1
- package/lib/im-scene.js +0 -2
- package/lib/index.d.ts +4 -3
- package/lib/index.js +3 -3
- package/lib/message.d.ts +0 -5
- package/lib/message.js +0 -13
- package/lib/plugin-runtime/im/im-runtime.d.ts +30 -8
- package/lib/plugin-runtime/im/im-runtime.js +471 -145
- package/lib/plugin-runtime/im/index.d.ts +2 -0
- package/lib/plugin-runtime/im/index.js +2 -0
- package/lib/plugin-runtime/im/login-assist-host.d.ts +6 -0
- package/lib/plugin-runtime/im/login-assist-host.js +6 -0
- package/lib/plugin-runtime/im/message-dispatcher.d.ts +2 -2
- package/lib/plugin-runtime/im/message-dispatcher.js +2 -2
- package/lib/plugin-runtime/im/outbound-segments.d.ts +4 -0
- package/lib/plugin-runtime/im/outbound-segments.js +26 -0
- package/lib/plugin-runtime/im/side-event-gateway.d.ts +13 -0
- package/lib/plugin-runtime/im/side-event-gateway.js +2 -0
- package/lib/plugin.d.ts +1 -0
- package/lib/{prompt.d.ts → schema-interaction.d.ts} +6 -6
- package/lib/{prompt.js → schema-interaction.js} +12 -12
- package/lib/side-event/base.d.ts +7 -0
- package/lib/side-event/dispatch.d.ts +15 -0
- package/lib/side-event/dispatch.js +140 -0
- package/lib/side-event/index.d.ts +1 -0
- package/lib/side-event/index.js +1 -0
- package/lib/side-event/normalize.d.ts +2 -0
- package/lib/side-event/normalize.js +4 -0
- package/lib/side-event/types.d.ts +4 -2
- package/lib/side-event/types.js +1 -1
- package/lib/system-event.d.ts +15 -0
- package/lib/system-event.js +7 -0
- package/lib/types.d.ts +0 -15
- package/package.json +11 -10
- package/lib/built/prepend-quote-context.d.ts +0 -34
- package/lib/built/prepend-quote-context.js +0 -130
- package/lib/message-quote.d.ts +0 -10
- package/lib/message-quote.js +0 -90
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './contracts.js';
|
|
2
2
|
export * from './im-runtime.js';
|
|
3
3
|
export * from './interactive.js';
|
|
4
|
+
export * from './login-assist-host.js';
|
|
4
5
|
export * from './message-bus.js';
|
|
5
6
|
export * from './message-dispatcher.js';
|
|
6
7
|
export * from './outbound-renderer.js';
|
|
7
8
|
export * from './outbound-segments.js';
|
|
8
9
|
export * from './service-tokens.js';
|
|
10
|
+
export * from './side-event-gateway.js';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './contracts.js';
|
|
2
2
|
export * from './im-runtime.js';
|
|
3
3
|
export * from './interactive.js';
|
|
4
|
+
export * from './login-assist-host.js';
|
|
4
5
|
export * from './message-bus.js';
|
|
5
6
|
export * from './message-dispatcher.js';
|
|
6
7
|
export * from './outbound-renderer.js';
|
|
7
8
|
export * from './outbound-segments.js';
|
|
8
9
|
export * from './service-tokens.js';
|
|
10
|
+
export * from './side-event-gateway.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { LoginAssist } from '../../built/login-assist.js';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Runtime login-assist port(与 messageGatewayToken / sideEventGatewayToken 并列)。
|
|
4
|
+
* 适配器 waitForInput;Console / stdin 经 listPending + submit 消费。
|
|
5
|
+
*/
|
|
6
|
+
export declare const loginAssistToken: import("@zhin.js/plugin-runtime").Token<LoginAssist>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createToken } from '@zhin.js/plugin-runtime';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Runtime login-assist port(与 messageGatewayToken / sideEventGatewayToken 并列)。
|
|
4
|
+
* 适配器 waitForInput;Console / stdin 经 listPending + submit 消费。
|
|
5
|
+
*/
|
|
6
|
+
export const loginAssistToken = createToken('zhin.im.login-assist');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { UserInteractionFactory } from '@zhin.js/interaction';
|
|
2
2
|
import type { RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
3
3
|
import type { Message, MessageDispatchResult } from './contracts.js';
|
|
4
4
|
/**
|
|
@@ -14,5 +14,5 @@ export declare const defaultCommandPrefixResolver: CommandPrefixResolver;
|
|
|
14
14
|
export declare class MessageDispatcher {
|
|
15
15
|
private readonly resolvePrefix;
|
|
16
16
|
constructor(resolvePrefix?: CommandPrefixResolver);
|
|
17
|
-
dispatch(message: Message, snapshot: RuntimeSnapshot,
|
|
17
|
+
dispatch(message: Message, snapshot: RuntimeSnapshot, interactionFactory?: UserInteractionFactory): Promise<MessageDispatchResult>;
|
|
18
18
|
}
|
|
@@ -26,7 +26,7 @@ export class MessageDispatcher {
|
|
|
26
26
|
constructor(resolvePrefix = defaultCommandPrefixResolver) {
|
|
27
27
|
this.resolvePrefix = resolvePrefix;
|
|
28
28
|
}
|
|
29
|
-
async dispatch(message, snapshot,
|
|
29
|
+
async dispatch(message, snapshot, interactionFactory) {
|
|
30
30
|
const prefix = this.resolvePrefix(message, snapshot);
|
|
31
31
|
let input = message.content.trim();
|
|
32
32
|
if (prefix && !input.startsWith(prefix)) {
|
|
@@ -43,7 +43,7 @@ export class MessageDispatcher {
|
|
|
43
43
|
? stripCommandPrefix(message.segments, prefix)
|
|
44
44
|
: undefined;
|
|
45
45
|
const matchInput = structuredInput ?? input;
|
|
46
|
-
const result = await commands.dispatch(matchInput, message,
|
|
46
|
+
const result = await commands.dispatch(matchInput, message, interactionFactory, prefix);
|
|
47
47
|
if (result.matched && result.value !== undefined) {
|
|
48
48
|
if (!result.owner)
|
|
49
49
|
throw new Error('Matched Command is missing its owner');
|
|
@@ -35,6 +35,7 @@ export type OutboundMediaPolicy = 'base64' | 'url-or-text' | 'passthrough';
|
|
|
35
35
|
export interface OutboundSegmentsPolicy {
|
|
36
36
|
readonly outboundMedia: readonly ('url' | 'path' | 'base64' | 'upload')[];
|
|
37
37
|
readonly interactive?: InteractivePolicy;
|
|
38
|
+
readonly markdown?: 'native' | 'text';
|
|
38
39
|
}
|
|
39
40
|
export interface NormalizeOutboundOptions {
|
|
40
41
|
/** 缺省 `url-or-text`(未声明 adapter 的保守兜底:仅 URL 媒体可达)。 */
|
|
@@ -52,6 +53,9 @@ export declare function isOutboundSegment(value: unknown): value is OutboundSegm
|
|
|
52
53
|
* `segments.interactive`,否则按平台名查内置表,未知平台回退 'text'。
|
|
53
54
|
*/
|
|
54
55
|
export declare function resolveOutboundInteractivePolicy(adapter: CapabilityId, snapshot: RuntimeSnapshot): InteractivePolicy;
|
|
56
|
+
/** Preserve semantic Markdown only when the endpoint explicitly declares native consumption. */
|
|
57
|
+
export declare function resolveOutboundMarkdownPolicy(adapter: CapabilityId, snapshot: RuntimeSnapshot): 'native' | 'text';
|
|
58
|
+
export declare function applyOutboundMarkdownPolicy(payload: unknown, policy: 'native' | 'text'): unknown;
|
|
55
59
|
/**
|
|
56
60
|
* keyboard 段中央降级:'text' 端点把 keyboard 渲染为编号文本(复用旧轨
|
|
57
61
|
* `renderKeyboardAsText`),并把有效 fallback 映射经 `remember` 回调写入
|
|
@@ -3,6 +3,7 @@ import { toCanonicalSegments } from '../../built/generic-segment-mapper.js';
|
|
|
3
3
|
import { effectiveKeyboardFallbackMap, renderKeyboardAsText, } from '../../built/interactive-segments/resolve.js';
|
|
4
4
|
import { DEFAULT_INTERACTIVE_POLICY, isKeyboardSegment, } from '../../built/interactive-segments/types.js';
|
|
5
5
|
import { isMediaRef } from '../../built/segment-contract/index.js';
|
|
6
|
+
import { markdownToPlainText } from '../../built/rich-segments/markdown-to-text.js';
|
|
6
7
|
const DEFAULT_CARD_WIDTH = 540;
|
|
7
8
|
const DEFAULT_CARD_FILENAME = 'card.png';
|
|
8
9
|
const DEFAULT_MEDIA_POLICY = 'url-or-text';
|
|
@@ -63,6 +64,31 @@ export function resolveOutboundInteractivePolicy(adapter, snapshot) {
|
|
|
63
64
|
return (adapterType ? OUTBOUND_INTERACTIVE_POLICY_BY_ADAPTER[adapterType] : undefined)
|
|
64
65
|
?? DEFAULT_INTERACTIVE_POLICY;
|
|
65
66
|
}
|
|
67
|
+
/** Preserve semantic Markdown only when the endpoint explicitly declares native consumption. */
|
|
68
|
+
export function resolveOutboundMarkdownPolicy(adapter, snapshot) {
|
|
69
|
+
const slot = snapshot.capabilities.get(adapter)
|
|
70
|
+
?? snapshot.capabilities.get(baseSlotCapabilityId(adapter));
|
|
71
|
+
if (!slot?.definition || typeof slot.definition !== 'object')
|
|
72
|
+
return 'text';
|
|
73
|
+
const segments = slot.definition.segments;
|
|
74
|
+
if (!segments || typeof segments !== 'object')
|
|
75
|
+
return 'text';
|
|
76
|
+
return segments.markdown === 'native' ? 'native' : 'text';
|
|
77
|
+
}
|
|
78
|
+
export function applyOutboundMarkdownPolicy(payload, policy) {
|
|
79
|
+
if (policy === 'native')
|
|
80
|
+
return payload;
|
|
81
|
+
const convert = (item) => {
|
|
82
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
83
|
+
return item;
|
|
84
|
+
const segment = item;
|
|
85
|
+
if (segment.type !== 'markdown')
|
|
86
|
+
return item;
|
|
87
|
+
const content = String(segment.data?.content ?? segment.data?.text ?? '');
|
|
88
|
+
return { type: 'text', data: { text: markdownToPlainText(content) } };
|
|
89
|
+
};
|
|
90
|
+
return Array.isArray(payload) ? payload.map(convert) : convert(payload);
|
|
91
|
+
}
|
|
66
92
|
function readDeclaredInteractivePolicy(definition) {
|
|
67
93
|
if (!definition || typeof definition !== 'object')
|
|
68
94
|
return undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Notice } from '../../notice.js';
|
|
2
|
+
import type { Request } from '../../request.js';
|
|
3
|
+
import type { SystemEvent } from '../../system-event.js';
|
|
4
|
+
/**
|
|
5
|
+
* Plugin Runtime 侧事件入站口(与 messageGatewayToken 并列)。
|
|
6
|
+
* 适配器归一 Notice / Request / SystemEvent 后调用,由 ImRuntime 分发给 HandlerIndex。
|
|
7
|
+
*/
|
|
8
|
+
export interface SideEventGateway {
|
|
9
|
+
receiveNotice(notice: Notice): Promise<void>;
|
|
10
|
+
receiveRequest(request: Request): Promise<void>;
|
|
11
|
+
receiveSystem(event: SystemEvent): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare const sideEventGatewayToken: import("@zhin.js/plugin-runtime").Token<SideEventGateway>;
|
package/lib/plugin.d.ts
CHANGED
|
@@ -185,6 +185,7 @@ export declare namespace Plugin {
|
|
|
185
185
|
'endpoint.error': [import('./built/endpoint-lifecycle.js').EndpointLifecyclePayload];
|
|
186
186
|
"request.receive": [import('./request.js').Request];
|
|
187
187
|
"notice.receive": [import('./notice.js').Notice];
|
|
188
|
+
"system.receive": [import('./system-event.js').SystemEvent];
|
|
188
189
|
"ai.processing.start": [Plugin.AIEventPayload];
|
|
189
190
|
"ai.processing.finish": [Plugin.AIEventPayload];
|
|
190
191
|
"ai.processing.error": [Plugin.AIEventPayload];
|
|
@@ -5,10 +5,10 @@ import { Schema } from '@zhin.js/schema';
|
|
|
5
5
|
/**
|
|
6
6
|
* Prompt类:用于实现机器人与用户的交互式提问与输入收集。
|
|
7
7
|
* 支持文本、数字、确认、列表、选项、Schema等多种输入类型,自动处理超时、默认值、格式化等。
|
|
8
|
-
* 典型用法:await new
|
|
8
|
+
* 典型用法:await new SchemaInteraction(plugin, event).text('请输入内容')
|
|
9
9
|
* @template P 适配器类型
|
|
10
10
|
*/
|
|
11
|
-
export declare class
|
|
11
|
+
export declare class SchemaInteraction<P extends RegisteredAdapter> {
|
|
12
12
|
private plugin;
|
|
13
13
|
private event;
|
|
14
14
|
/**
|
|
@@ -25,7 +25,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
|
|
|
25
25
|
* 通用提问方法,支持自定义格式化、超时、默认值等
|
|
26
26
|
* @param config 提问配置
|
|
27
27
|
*/
|
|
28
|
-
private
|
|
28
|
+
private collect;
|
|
29
29
|
/**
|
|
30
30
|
* 注册一次性消息中间件,等待用户输入或超时
|
|
31
31
|
* @param callback 输入回调
|
|
@@ -52,7 +52,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
|
|
|
52
52
|
/**
|
|
53
53
|
* 列表输入,支持多值分隔
|
|
54
54
|
*/
|
|
55
|
-
list<T extends
|
|
55
|
+
list<T extends SchemaInteraction.SingleType = 'text'>(tips: string, config?: SchemaInteraction.ListConfig<T>, timeoutText?: string): Promise<SchemaInteraction.Result<T>[]>;
|
|
56
56
|
/**
|
|
57
57
|
* 返回常量值(用于Schema)
|
|
58
58
|
*/
|
|
@@ -60,7 +60,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
|
|
|
60
60
|
/**
|
|
61
61
|
* 选项选择,支持单选/多选
|
|
62
62
|
*/
|
|
63
|
-
pick<T extends
|
|
63
|
+
pick<T extends SchemaInteraction.SingleType, M extends boolean = false>(tips: string, config: SchemaInteraction.PickConfig<T, M>, timeoutText?: string): Promise<SchemaInteraction.PickResult<T, M>>;
|
|
64
64
|
/**
|
|
65
65
|
* 基于Schema的选项选择
|
|
66
66
|
*/
|
|
@@ -79,7 +79,7 @@ export declare class Prompt<P extends RegisteredAdapter> {
|
|
|
79
79
|
/**
|
|
80
80
|
* Prompt命名空间:类型辅助定义
|
|
81
81
|
*/
|
|
82
|
-
export declare namespace
|
|
82
|
+
export declare namespace SchemaInteraction {
|
|
83
83
|
interface SingleMap {
|
|
84
84
|
text: string;
|
|
85
85
|
number: number;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Prompt类:用于实现机器人与用户的交互式提问与输入收集。
|
|
3
3
|
* 支持文本、数字、确认、列表、选项、Schema等多种输入类型,自动处理超时、默认值、格式化等。
|
|
4
|
-
* 典型用法:await new
|
|
4
|
+
* 典型用法:await new SchemaInteraction(plugin, event).text('请输入内容')
|
|
5
5
|
* @template P 适配器类型
|
|
6
6
|
*/
|
|
7
|
-
export class
|
|
7
|
+
export class SchemaInteraction {
|
|
8
8
|
plugin;
|
|
9
9
|
event;
|
|
10
10
|
/**
|
|
@@ -26,10 +26,10 @@ export class Prompt {
|
|
|
26
26
|
* 通用提问方法,支持自定义格式化、超时、默认值等
|
|
27
27
|
* @param config 提问配置
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
collect(config) {
|
|
30
30
|
return new Promise(async (resolve, reject) => {
|
|
31
31
|
if (!this.event.$reply) {
|
|
32
|
-
reject(new Error('
|
|
32
|
+
reject(new Error('SchemaInteraction requires outbound capability on the current endpoint'));
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
const id = await this.event.$reply(config.tips);
|
|
@@ -72,7 +72,7 @@ export class Prompt {
|
|
|
72
72
|
* 文本输入
|
|
73
73
|
*/
|
|
74
74
|
async text(tips, timeout, defaultValue = '', timeoutText) {
|
|
75
|
-
return this.
|
|
75
|
+
return this.collect({
|
|
76
76
|
tips,
|
|
77
77
|
defaultValue,
|
|
78
78
|
timeoutText,
|
|
@@ -84,7 +84,7 @@ export class Prompt {
|
|
|
84
84
|
* 任意输入
|
|
85
85
|
*/
|
|
86
86
|
async any(tips, timeout, defaultValue = '', timeoutText) {
|
|
87
|
-
return this.
|
|
87
|
+
return this.collect({
|
|
88
88
|
tips,
|
|
89
89
|
defaultValue,
|
|
90
90
|
timeoutText,
|
|
@@ -96,7 +96,7 @@ export class Prompt {
|
|
|
96
96
|
* 数字输入
|
|
97
97
|
*/
|
|
98
98
|
async number(tips, timeout, defaultValue = 0, timeoutText) {
|
|
99
|
-
return this.
|
|
99
|
+
return this.collect({
|
|
100
100
|
tips,
|
|
101
101
|
defaultValue,
|
|
102
102
|
timeoutText,
|
|
@@ -108,7 +108,7 @@ export class Prompt {
|
|
|
108
108
|
* 确认输入(如 yes/no)
|
|
109
109
|
*/
|
|
110
110
|
async confirm(tips, condition = 'yes', timeout, defaultValue = false, timeoutText) {
|
|
111
|
-
return this.
|
|
111
|
+
return this.collect({
|
|
112
112
|
tips: `${tips}\n输入“${condition}”以确认`,
|
|
113
113
|
defaultValue,
|
|
114
114
|
timeout,
|
|
@@ -121,7 +121,7 @@ export class Prompt {
|
|
|
121
121
|
*/
|
|
122
122
|
async list(tips, config = { type: 'text' }, timeoutText) {
|
|
123
123
|
const separator = config.separator || ',';
|
|
124
|
-
return this.
|
|
124
|
+
return this.collect({
|
|
125
125
|
tips: `${tips}\n值之间使用“${separator}”分隔`,
|
|
126
126
|
defaultValue: config.defaultValue || [],
|
|
127
127
|
timeout: config.timeout,
|
|
@@ -154,7 +154,7 @@ export class Prompt {
|
|
|
154
154
|
const separator = config.separator || ',';
|
|
155
155
|
if (config.multiple)
|
|
156
156
|
moreTextArr.push(`多选请用“${separator}”分隔`);
|
|
157
|
-
return this.
|
|
157
|
+
return this.collect({
|
|
158
158
|
tips: `${tips}\n${moreTextArr.join('\n')}`,
|
|
159
159
|
defaultValue: config.defaultValue,
|
|
160
160
|
timeout: config.timeout,
|
|
@@ -218,13 +218,13 @@ export class Prompt {
|
|
|
218
218
|
throw new Error('Object schema missing object definition');
|
|
219
219
|
return (await this.getValueWithSchemas(schema.options.object));
|
|
220
220
|
case 'date':
|
|
221
|
-
return await this.
|
|
221
|
+
return await this.collect({
|
|
222
222
|
tips: schema.meta.description || schema.meta.key || 'Enter a date',
|
|
223
223
|
defaultValue: schema.meta.default || new Date(),
|
|
224
224
|
format: (input) => new Date(input),
|
|
225
225
|
});
|
|
226
226
|
case 'regexp':
|
|
227
|
-
return await this.
|
|
227
|
+
return await this.collect({
|
|
228
228
|
tips: schema.meta.description || schema.meta.key || 'Enter a regex pattern',
|
|
229
229
|
defaultValue: schema.meta.default || '',
|
|
230
230
|
format: (input) => new RegExp(input),
|
package/lib/side-event/base.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export interface SideEventBase {
|
|
|
25
25
|
$target?: MessageSender;
|
|
26
26
|
/** 毫秒时间戳 */
|
|
27
27
|
$timestamp: number;
|
|
28
|
+
/** Canonical relation fields populated by the Adapter boundary. */
|
|
29
|
+
$message_id?: string;
|
|
30
|
+
$reaction?: string;
|
|
31
|
+
$operation?: 'added' | 'removed';
|
|
32
|
+
$duration_seconds?: number;
|
|
33
|
+
$role?: string;
|
|
34
|
+
$enabled?: boolean;
|
|
28
35
|
}
|
|
29
36
|
/** 组合 Side Event 完整类型名 */
|
|
30
37
|
export declare function composeSideEventName(type: string, sceneType: string | undefined, subType: string | undefined): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SideEventGateway } from '../plugin-runtime/im/side-event-gateway.js';
|
|
2
|
+
import { type OneBotLikeRawEvent, type SideEventPlatform } from './normalize.js';
|
|
3
|
+
export interface OneBotLikeSideEventInput {
|
|
4
|
+
readonly adapter: string;
|
|
5
|
+
readonly endpointKey: string;
|
|
6
|
+
readonly platform?: SideEventPlatform;
|
|
7
|
+
readonly raw: OneBotLikeRawEvent;
|
|
8
|
+
readonly approve?: (flag: string, remark?: string) => void | Promise<void>;
|
|
9
|
+
readonly reject?: (flag: string, reason?: string) => void | Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalize OneBot / icqq-style notice|request|meta payloads and forward to SideEventGateway.
|
|
13
|
+
* Returns which kind was dispatched, or null when the payload is not a side event.
|
|
14
|
+
*/
|
|
15
|
+
export declare function receiveOneBotLikeSideEvent(gateway: SideEventGateway, input: OneBotLikeSideEventInput): Promise<'notice' | 'request' | 'system' | null>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { buildNotice, buildRequest, buildSystem, mapNoticeParts, mapRequestParts, resolveSideEventDedupeKey, senderFromId, } from './normalize.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize OneBot / icqq-style notice|request|meta payloads and forward to SideEventGateway.
|
|
4
|
+
* Returns which kind was dispatched, or null when the payload is not a side event.
|
|
5
|
+
*/
|
|
6
|
+
export async function receiveOneBotLikeSideEvent(gateway, input) {
|
|
7
|
+
const raw = input.raw;
|
|
8
|
+
const platform = input.platform ?? 'onebot';
|
|
9
|
+
const postType = String(raw.post_type ?? raw.type ?? '');
|
|
10
|
+
if (postType === 'notice' || postType.startsWith('notice.')) {
|
|
11
|
+
const noticeType = String(raw.notice_type ?? (postType.replace(/^notice\.?/, '') || 'unknown'));
|
|
12
|
+
const parts = mapNoticeParts(platform, noticeType, {
|
|
13
|
+
sub_type: raw.sub_type != null ? String(raw.sub_type) : undefined,
|
|
14
|
+
is_group: raw.group_id != null,
|
|
15
|
+
});
|
|
16
|
+
const id = resolveSideEventDedupeKey(raw, 'notice');
|
|
17
|
+
const memberEvent = ['member_increase', 'member_decrease', 'ban', 'admin_change'].includes(parts.sub_type);
|
|
18
|
+
const actorId = raw.operator_id
|
|
19
|
+
?? (!memberEvent ? raw.user_id : undefined);
|
|
20
|
+
const targetId = memberEvent
|
|
21
|
+
? raw.user_id
|
|
22
|
+
: parts.sub_type === 'poke'
|
|
23
|
+
? raw.target_id ?? raw.self_id
|
|
24
|
+
: raw.user_id != null && String(raw.user_id) !== String(raw.operator_id ?? '')
|
|
25
|
+
? raw.user_id
|
|
26
|
+
: undefined;
|
|
27
|
+
const notice = buildNotice(raw, {
|
|
28
|
+
$id: id,
|
|
29
|
+
$adapter: input.adapter,
|
|
30
|
+
$endpoint: input.endpointKey,
|
|
31
|
+
$type: 'notice',
|
|
32
|
+
$scene_id: String(raw.group_id ?? raw.user_id ?? input.endpointKey),
|
|
33
|
+
$scene_type: parts.scene_type,
|
|
34
|
+
$sub_type: parts.sub_type,
|
|
35
|
+
$actor: senderFromId(actorId),
|
|
36
|
+
$target: senderFromId(targetId),
|
|
37
|
+
$timestamp: toMillis(raw.time),
|
|
38
|
+
...(raw.message_id != null ? { $message_id: String(raw.message_id) } : {}),
|
|
39
|
+
...(raw.emoji_id != null || raw.code != null
|
|
40
|
+
? { $reaction: String(raw.emoji_id ?? raw.code) }
|
|
41
|
+
: {}),
|
|
42
|
+
...(parts.sub_type === 'emoji_reaction'
|
|
43
|
+
? { $operation: /delete|remove/i.test(String(raw.sub_type ?? noticeType)) ? 'removed' : 'added' }
|
|
44
|
+
: {}),
|
|
45
|
+
...(raw.duration != null ? { $duration_seconds: Math.max(0, Number(raw.duration) || 0) } : {}),
|
|
46
|
+
...(parts.sub_type === 'admin_change'
|
|
47
|
+
? { $role: 'admin', $enabled: String(raw.sub_type ?? '') === 'set' }
|
|
48
|
+
: {}),
|
|
49
|
+
});
|
|
50
|
+
await gateway.receiveNotice(notice);
|
|
51
|
+
return 'notice';
|
|
52
|
+
}
|
|
53
|
+
if (postType === 'request' || postType.startsWith('request.')) {
|
|
54
|
+
const requestType = String(raw.request_type ?? (postType.replace(/^request\.?/, '') || 'friend'));
|
|
55
|
+
const parts = mapRequestParts(platform, requestType, raw.sub_type != null ? String(raw.sub_type) : undefined);
|
|
56
|
+
const flag = raw.flag != null ? String(raw.flag) : '';
|
|
57
|
+
const id = flag || resolveSideEventDedupeKey(raw, 'request');
|
|
58
|
+
const actor = senderFromId(raw.user_id);
|
|
59
|
+
if (!actor)
|
|
60
|
+
return null;
|
|
61
|
+
const approve = input.approve;
|
|
62
|
+
const reject = input.reject;
|
|
63
|
+
let active = true;
|
|
64
|
+
const actions = new Set();
|
|
65
|
+
const requireActiveAction = () => {
|
|
66
|
+
if (!active)
|
|
67
|
+
throw new Error('Request action port expired with its generation operation');
|
|
68
|
+
};
|
|
69
|
+
const runAction = (action) => {
|
|
70
|
+
requireActiveAction();
|
|
71
|
+
const operation = Promise.resolve().then(action);
|
|
72
|
+
actions.add(operation);
|
|
73
|
+
void operation.then(() => actions.delete(operation), () => actions.delete(operation));
|
|
74
|
+
return operation;
|
|
75
|
+
};
|
|
76
|
+
const request = buildRequest(raw, {
|
|
77
|
+
$id: id,
|
|
78
|
+
$adapter: input.adapter,
|
|
79
|
+
$endpoint: input.endpointKey,
|
|
80
|
+
$type: 'request',
|
|
81
|
+
$scene_id: String(raw.group_id ?? raw.user_id ?? input.endpointKey),
|
|
82
|
+
$scene_type: parts.scene_type,
|
|
83
|
+
$sub_type: parts.sub_type,
|
|
84
|
+
$actor: actor,
|
|
85
|
+
$comment: raw.comment != null ? String(raw.comment) : undefined,
|
|
86
|
+
$timestamp: toMillis(raw.time),
|
|
87
|
+
async $approve(remark) {
|
|
88
|
+
if (!approve || !flag)
|
|
89
|
+
throw new Error('Request approve is not available');
|
|
90
|
+
await runAction(() => approve(flag, remark));
|
|
91
|
+
},
|
|
92
|
+
async $reject(reason) {
|
|
93
|
+
if (!reject || !flag)
|
|
94
|
+
throw new Error('Request reject is not available');
|
|
95
|
+
await runAction(() => reject(flag, reason));
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
await gateway.receiveRequest(request);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
active = false;
|
|
103
|
+
await Promise.allSettled([...actions]);
|
|
104
|
+
}
|
|
105
|
+
return 'request';
|
|
106
|
+
}
|
|
107
|
+
if (postType === 'meta_event'
|
|
108
|
+
|| postType === 'meta'
|
|
109
|
+
|| postType.startsWith('meta.')
|
|
110
|
+
|| postType.startsWith('system.')) {
|
|
111
|
+
const metaType = String(raw.meta_event_type
|
|
112
|
+
?? raw.detail_type
|
|
113
|
+
?? (postType.replace(/^(meta_event|meta|system)\.?/, '') || 'unknown'));
|
|
114
|
+
const subType = raw.sub_type != null ? String(raw.sub_type) : undefined;
|
|
115
|
+
const sceneType = postType.startsWith('system.')
|
|
116
|
+
? (postType.split('.')[1] ?? 'system')
|
|
117
|
+
: metaType === 'lifecycle' || metaType === 'heartbeat'
|
|
118
|
+
? metaType
|
|
119
|
+
: 'meta';
|
|
120
|
+
const system = buildSystem(raw, {
|
|
121
|
+
$id: `system:${toMillis(raw.time)}_${metaType}_${subType ?? ''}`,
|
|
122
|
+
$adapter: input.adapter,
|
|
123
|
+
$endpoint: input.endpointKey,
|
|
124
|
+
$type: 'system',
|
|
125
|
+
$scene_id: String(raw.self_id ?? input.endpointKey),
|
|
126
|
+
$scene_type: sceneType,
|
|
127
|
+
$sub_type: subType ?? metaType,
|
|
128
|
+
$timestamp: toMillis(raw.time),
|
|
129
|
+
});
|
|
130
|
+
await gateway.receiveSystem(system);
|
|
131
|
+
return 'system';
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function toMillis(time) {
|
|
136
|
+
const seconds = Number(time);
|
|
137
|
+
return Number.isFinite(seconds) && seconds > 0
|
|
138
|
+
? (seconds < 1e12 ? seconds * 1000 : seconds)
|
|
139
|
+
: Date.now();
|
|
140
|
+
}
|
package/lib/side-event/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Notice, type NoticeBase } from '../notice.js';
|
|
2
2
|
import { Request, type RequestBase } from '../request.js';
|
|
3
|
+
import { SystemEvent, type SystemEventBase } from '../system-event.js';
|
|
3
4
|
import { type ComposedNoticeName, type ComposedRequestName, type SideEventParts } from './types.js';
|
|
4
5
|
export type SideEventPlatform = 'onebot' | 'icqq' | 'napcat' | 'kook' | (string & {});
|
|
5
6
|
export interface OneBotLikeRawEvent {
|
|
@@ -38,3 +39,4 @@ export declare function senderFromId(id: unknown, name?: string): {
|
|
|
38
39
|
} | undefined;
|
|
39
40
|
export declare function buildNotice<T extends object>(input: T, format: NoticeBase): Notice<T>;
|
|
40
41
|
export declare function buildRequest<T extends object>(input: T, format: RequestBase): Request<T>;
|
|
42
|
+
export declare function buildSystem<T extends object>(input: T, format: SystemEventBase): SystemEvent<T>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Notice } from '../notice.js';
|
|
2
2
|
import { Request } from '../request.js';
|
|
3
|
+
import { SystemEvent } from '../system-event.js';
|
|
3
4
|
import { composeSideEventName, parseSideEventName, } from './base.js';
|
|
4
5
|
import { KOOK_NOTICE_PARTS_MAP, ONEBOT_NOTICE_PARTS_MAP, SLACK_NOTICE_PARTS_MAP } from './types.js';
|
|
5
6
|
/** 去重用:notice / request 无稳定 id 时组合键 */
|
|
@@ -115,3 +116,6 @@ export function buildNotice(input, format) {
|
|
|
115
116
|
export function buildRequest(input, format) {
|
|
116
117
|
return Request.from(input, format);
|
|
117
118
|
}
|
|
119
|
+
export function buildSystem(input, format) {
|
|
120
|
+
return SystemEvent.from(input, format);
|
|
121
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Side Event 标准类型。
|
|
3
|
-
* 运行时 `$type` 仅存命名空间(`notice` / `request`);
|
|
3
|
+
* 运行时 `$type` 仅存命名空间(`notice` / `request` / `system`);
|
|
4
4
|
* 完整名由 `${$type}.${$scene_type}.${$sub_type}` 组合。
|
|
5
5
|
*/
|
|
6
|
-
export type SideEventKind = 'notice' | 'request';
|
|
6
|
+
export type SideEventKind = 'notice' | 'request' | 'system';
|
|
7
7
|
export type NoticeKind = 'notice';
|
|
8
8
|
export type RequestKind = 'request';
|
|
9
|
+
export type SystemKind = 'system';
|
|
9
10
|
/** 完整组合名(测试 / 文档 / 消费者匹配用) */
|
|
10
11
|
export type ComposedNoticeName = 'notice.group.member_increase' | 'notice.group.member_decrease' | 'notice.group.admin_change' | 'notice.group.ban' | 'notice.group.recall' | 'notice.group.poke' | 'notice.group.transfer' | 'notice.group.upload' | 'notice.group.emoji_reaction' | 'notice.group.card_change' | 'notice.group.essence_add' | 'notice.group.essence_delete' | 'notice.friend.recall' | 'notice.friend.increase' | 'notice.friend.poke' | 'notice.endpoint.lifecycle' | (string & {});
|
|
11
12
|
export type ComposedRequestName = 'request.friend.add' | 'request.group.add' | 'request.group.invite' | (string & {});
|
|
13
|
+
export type ComposedSystemName = 'system.login.qrcode' | 'system.login.slider' | 'system.login.device' | 'system.login.error' | 'system.login.auth' | 'system.offline' | 'system.offline.network' | 'system.offline.kickoff' | 'system.online' | (string & {});
|
|
12
14
|
export interface SideEventParts {
|
|
13
15
|
scene_type: string;
|
|
14
16
|
sub_type: string;
|
package/lib/side-event/types.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Adapters } from './adapter.js';
|
|
2
|
+
import type { SideEventBase } from './side-event/base.js';
|
|
3
|
+
/**
|
|
4
|
+
* 系统/登录侧事件(扫码、滑块、掉线等)。
|
|
5
|
+
* `$scene_type`:`login` / `offline` / `online` 等;
|
|
6
|
+
* `$sub_type`:`qrcode` / `slider` / `device` / `kickoff` / `network` 等。
|
|
7
|
+
*/
|
|
8
|
+
export interface SystemEventBase extends SideEventBase {
|
|
9
|
+
$adapter: keyof Adapters;
|
|
10
|
+
$type: 'system';
|
|
11
|
+
}
|
|
12
|
+
export type SystemEvent<T extends object = {}> = SystemEventBase & T;
|
|
13
|
+
export declare namespace SystemEvent {
|
|
14
|
+
function from<T extends object>(input: T, format: SystemEventBase): SystemEvent<T>;
|
|
15
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -61,21 +61,6 @@ export type MaybeArray<T> = T | T[];
|
|
|
61
61
|
* 消息发送内容类型
|
|
62
62
|
*/
|
|
63
63
|
export type SendContent = MaybeArray<string | MessageElement>;
|
|
64
|
-
/** $getMsg 返回的被引用消息载荷 */
|
|
65
|
-
export interface QuotedMessagePayload {
|
|
66
|
-
messageId: string;
|
|
67
|
-
sender?: {
|
|
68
|
-
id?: string;
|
|
69
|
-
name?: string;
|
|
70
|
-
};
|
|
71
|
-
content: MessageElement[] | string;
|
|
72
|
-
raw?: string;
|
|
73
|
-
time?: number;
|
|
74
|
-
}
|
|
75
|
-
/** 支持按 message_id 拉取历史消息的 Bot(可选实现) */
|
|
76
|
-
export interface QuotableEndpoint {
|
|
77
|
-
$getMsg(messageId: string): Promise<QuotedMessagePayload>;
|
|
78
|
-
}
|
|
79
64
|
/** 可选:编辑已发送消息(交互式棋盘更新) */
|
|
80
65
|
export interface EditMessageOptions {
|
|
81
66
|
messageId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/core",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.13",
|
|
4
4
|
"description": "Zhin机器人核心框架",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -75,17 +75,18 @@
|
|
|
75
75
|
"segment-matcher": "^1.0.5",
|
|
76
76
|
"smol-toml": "^1.7.1",
|
|
77
77
|
"yaml": "^2.9.0",
|
|
78
|
-
"@zhin.js/adapter": "1.
|
|
79
|
-
"@zhin.js/
|
|
80
|
-
"@zhin.js/
|
|
78
|
+
"@zhin.js/adapter": "1.2.0",
|
|
79
|
+
"@zhin.js/component": "1.0.12",
|
|
80
|
+
"@zhin.js/command": "1.0.15",
|
|
81
81
|
"@zhin.js/database": "1.0.79",
|
|
82
|
-
"@zhin.js/handler": "1.0.
|
|
83
|
-
"@zhin.js/im-contract": "1.0.
|
|
82
|
+
"@zhin.js/handler": "1.0.3",
|
|
83
|
+
"@zhin.js/im-contract": "1.0.4",
|
|
84
|
+
"@zhin.js/interaction": "1.0.1",
|
|
84
85
|
"@zhin.js/kernel": "1.0.7",
|
|
85
86
|
"@zhin.js/logger": "1.0.76",
|
|
86
|
-
"@zhin.js/middleware": "1.0.
|
|
87
|
-
"@zhin.js/
|
|
88
|
-
"@zhin.js/
|
|
87
|
+
"@zhin.js/middleware": "1.0.12",
|
|
88
|
+
"@zhin.js/plugin-runtime": "1.1.7",
|
|
89
|
+
"@zhin.js/permission": "1.0.3",
|
|
89
90
|
"@zhin.js/schema": "1.0.73"
|
|
90
91
|
},
|
|
91
92
|
"peerDependencies": {
|
|
@@ -101,7 +102,7 @@
|
|
|
101
102
|
"@types/qrcode": "^1.5.6",
|
|
102
103
|
"ajv": "8.18.0",
|
|
103
104
|
"typescript": "^6.0.3",
|
|
104
|
-
"@zhin.js/ai": "1.5.
|
|
105
|
+
"@zhin.js/ai": "1.5.6"
|
|
105
106
|
},
|
|
106
107
|
"repository": {
|
|
107
108
|
"type": "git",
|