@zhin.js/adapter-satori 3.0.2 → 4.0.0
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/CHANGELOG.md +460 -0
- package/README.md +38 -40
- package/adapters/satori.ts +41 -0
- package/lib/endpoint.d.ts +61 -0
- package/lib/endpoint.js +379 -0
- package/lib/index.d.ts +4 -18
- package/lib/index.js +4 -25
- package/lib/protocol.d.ts +150 -0
- package/lib/protocol.js +196 -0
- package/lib/webhook.d.ts +17 -0
- package/lib/webhook.js +79 -0
- package/lib/ws.d.ts +13 -0
- package/lib/ws.js +5 -0
- package/package.json +41 -15
- package/plugin.ts +8 -0
- package/schema.json +59 -0
- package/src/endpoint.ts +462 -0
- package/src/index.ts +47 -35
- package/src/protocol.ts +315 -0
- package/src/webhook.ts +104 -0
- package/src/ws.ts +22 -0
- package/lib/adapter.d.ts +0 -19
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -37
- package/lib/adapter.js.map +0 -1
- package/lib/api.d.ts +0 -15
- package/lib/api.d.ts.map +0 -1
- package/lib/api.js +0 -37
- package/lib/api.js.map +0 -1
- package/lib/endpoint-webhook.d.ts +0 -27
- package/lib/endpoint-webhook.d.ts.map +0 -1
- package/lib/endpoint-webhook.js +0 -99
- package/lib/endpoint-webhook.js.map +0 -1
- package/lib/endpoint-ws.d.ts +0 -30
- package/lib/endpoint-ws.d.ts.map +0 -1
- package/lib/endpoint-ws.js +0 -195
- package/lib/endpoint-ws.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/types.d.ts +0 -91
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -13
- package/lib/types.js.map +0 -1
- package/lib/utils.d.ts +0 -40
- package/lib/utils.d.ts.map +0 -1
- package/lib/utils.js +0 -34
- package/lib/utils.js.map +0 -1
- package/src/adapter.ts +0 -45
- package/src/api.ts +0 -48
- package/src/endpoint-webhook.ts +0 -117
- package/src/endpoint-ws.ts +0 -214
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -91
- package/src/utils.ts +0 -65
package/lib/protocol.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Satori protocol helpers (no legacy Adapter/Endpoint / segment-mapper).
|
|
3
|
+
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
|
+
* Spec: https://satori.chat/zh-CN/protocol/overview.html
|
|
5
|
+
*/
|
|
6
|
+
import { pickCredential } from '@zhin.js/adapter';
|
|
7
|
+
/** Opcode:EVENT=0, PING=1, PONG=2, IDENTIFY=3, READY=4, META=5 */
|
|
8
|
+
export const SatoriOpcode = {
|
|
9
|
+
EVENT: 0,
|
|
10
|
+
PING: 1,
|
|
11
|
+
PONG: 2,
|
|
12
|
+
IDENTIFY: 3,
|
|
13
|
+
READY: 4,
|
|
14
|
+
META: 5,
|
|
15
|
+
};
|
|
16
|
+
export function resolveSatoriConfig(config = {}) {
|
|
17
|
+
const entry = config.endpoints?.find((item) => item.context === 'satori');
|
|
18
|
+
const connection = config.connection ?? entry?.connection ?? 'ws';
|
|
19
|
+
const baseUrl = pickCredential(config.baseUrl, entry?.baseUrl, process.env.SATORI_BASE_URL);
|
|
20
|
+
if (!baseUrl) {
|
|
21
|
+
throw new TypeError('Satori adapter requires baseUrl (plugins.<key>.baseUrl or endpoints with context: satori)');
|
|
22
|
+
}
|
|
23
|
+
const name = (typeof config.name === 'string' && config.name)
|
|
24
|
+
|| (typeof entry?.name === 'string' && entry.name)
|
|
25
|
+
|| process.env.SATORI_BOT_NAME
|
|
26
|
+
|| 'satori-bot';
|
|
27
|
+
const token = (typeof config.token === 'string' && config.token)
|
|
28
|
+
|| (typeof entry?.token === 'string' && entry.token)
|
|
29
|
+
|| process.env.SATORI_TOKEN
|
|
30
|
+
|| undefined;
|
|
31
|
+
if (connection === 'webhook') {
|
|
32
|
+
const path = config.path ?? entry?.path;
|
|
33
|
+
if (!path) {
|
|
34
|
+
throw new TypeError('Satori connection:webhook requires path');
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
context: 'satori',
|
|
38
|
+
connection: 'webhook',
|
|
39
|
+
name,
|
|
40
|
+
baseUrl,
|
|
41
|
+
token,
|
|
42
|
+
path,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const heartbeat = config.heartbeat_interval
|
|
46
|
+
?? entry?.heartbeat_interval
|
|
47
|
+
?? 10_000;
|
|
48
|
+
return {
|
|
49
|
+
context: 'satori',
|
|
50
|
+
connection: 'ws',
|
|
51
|
+
name,
|
|
52
|
+
baseUrl,
|
|
53
|
+
token,
|
|
54
|
+
heartbeat_interval: heartbeat,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Channel.type 1 = DIRECT (private). */
|
|
58
|
+
export function isPrivateChannel(channel) {
|
|
59
|
+
return channel?.type === 1;
|
|
60
|
+
}
|
|
61
|
+
export function isMessageEvent(body) {
|
|
62
|
+
return (body.type === 'message-created' || body.type === 'message-updated')
|
|
63
|
+
&& !!body.message?.id;
|
|
64
|
+
}
|
|
65
|
+
export function buildWsUrl(baseUrl, token) {
|
|
66
|
+
const url = new URL(baseUrl.replace(/\/$/, ''));
|
|
67
|
+
if (token)
|
|
68
|
+
url.searchParams.set('access_token', token);
|
|
69
|
+
return url.toString();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Call Satori HTTP API: POST {baseUrl}/v1/{resource}.{method}
|
|
73
|
+
* @see https://satori.chat/en-US/protocol/api.html
|
|
74
|
+
*/
|
|
75
|
+
export async function callSatoriApi(options, resource, method, params = {}) {
|
|
76
|
+
const { baseUrl, platform, userId, token } = options;
|
|
77
|
+
const url = `${baseUrl.replace(/\/$/, '')}/v1/${resource}.${method}`;
|
|
78
|
+
const headers = {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
'Satori-Platform': platform,
|
|
81
|
+
'Satori-User-ID': userId,
|
|
82
|
+
};
|
|
83
|
+
if (token)
|
|
84
|
+
headers.Authorization = `Bearer ${token}`;
|
|
85
|
+
const res = await fetch(url, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers,
|
|
88
|
+
body: JSON.stringify(params),
|
|
89
|
+
});
|
|
90
|
+
const text = await res.text();
|
|
91
|
+
if (res.status === 401)
|
|
92
|
+
throw new Error(`Satori API 认证失败: ${text}`);
|
|
93
|
+
if (res.status === 403)
|
|
94
|
+
throw new Error(`Satori API 权限不足: ${text}`);
|
|
95
|
+
if (res.status === 404)
|
|
96
|
+
throw new Error(`Satori API 不存在: ${resource}.${method}`);
|
|
97
|
+
if (res.status >= 400)
|
|
98
|
+
throw new Error(`Satori API 错误 ${res.status}: ${text}`);
|
|
99
|
+
if (!text || text.trim() === '')
|
|
100
|
+
return undefined;
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(text);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new Error(`Satori API 无效 JSON: ${text.slice(0, 200)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Build inbound text for MessageGateway.receive. */
|
|
109
|
+
export function formatInboundContent(body) {
|
|
110
|
+
const content = body.message.content ?? '';
|
|
111
|
+
return typeof content === 'string' ? content : String(content);
|
|
112
|
+
}
|
|
113
|
+
export function resolveInboundTarget(body) {
|
|
114
|
+
const channel = body.channel ?? body.message.channel;
|
|
115
|
+
return channel?.id ?? '';
|
|
116
|
+
}
|
|
117
|
+
export function resolveInboundSender(body) {
|
|
118
|
+
const user = body.user ?? body.message.user ?? body.message.member?.user;
|
|
119
|
+
return user?.name ?? body.message.member?.nick ?? user?.id ?? '';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Detect `<at id="…"/>` elements in message content targeting the bot selfId.
|
|
123
|
+
* selfId 来源:READY/事件携带的 `login.user.id`。
|
|
124
|
+
*/
|
|
125
|
+
export function isSelfMentioned(body, selfId) {
|
|
126
|
+
if (!selfId)
|
|
127
|
+
return false;
|
|
128
|
+
const content = body.message.content;
|
|
129
|
+
if (typeof content !== 'string' || !content.includes('<at'))
|
|
130
|
+
return false;
|
|
131
|
+
const tags = content.match(/<at\b[^>]*>/gi) ?? [];
|
|
132
|
+
return tags.some((tag) => /\bid\s*=\s*["']([^"']+)["']/i.exec(tag)?.[1] === selfId);
|
|
133
|
+
}
|
|
134
|
+
export function formatMessageId(channelId, messageId) {
|
|
135
|
+
return `${channelId}:${messageId}`;
|
|
136
|
+
}
|
|
137
|
+
export function parseMessageRef(id) {
|
|
138
|
+
if (id.includes(':')) {
|
|
139
|
+
const [channelId, messageId] = id.split(':');
|
|
140
|
+
return { channelId: channelId ?? '', messageId: messageId ?? '' };
|
|
141
|
+
}
|
|
142
|
+
return { channelId: '', messageId: id };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Wire-encode an already-rendered outbound payload into Satori message content.
|
|
146
|
+
* Segment canonicalization is intentionally not done here.
|
|
147
|
+
*/
|
|
148
|
+
export function formatSatoriOutbound(payload) {
|
|
149
|
+
if (typeof payload === 'string')
|
|
150
|
+
return payload;
|
|
151
|
+
if (payload == null)
|
|
152
|
+
return '';
|
|
153
|
+
const segments = Array.isArray(payload)
|
|
154
|
+
? payload
|
|
155
|
+
: payload && typeof payload === 'object' && 'type' in payload
|
|
156
|
+
? [payload]
|
|
157
|
+
: [];
|
|
158
|
+
if (segments.length === 0) {
|
|
159
|
+
return typeof payload === 'object' ? JSON.stringify(payload) : String(payload);
|
|
160
|
+
}
|
|
161
|
+
const parts = [];
|
|
162
|
+
for (const item of segments) {
|
|
163
|
+
if (typeof item === 'string') {
|
|
164
|
+
parts.push(item);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const data = item.data ?? {};
|
|
168
|
+
switch (item.type) {
|
|
169
|
+
case 'text':
|
|
170
|
+
parts.push(String(data.text ?? data.content ?? ''));
|
|
171
|
+
break;
|
|
172
|
+
case 'at':
|
|
173
|
+
case 'mention':
|
|
174
|
+
parts.push(`@${String(data.name ?? data.id ?? data.target ?? '')}`);
|
|
175
|
+
break;
|
|
176
|
+
case 'image':
|
|
177
|
+
if (typeof data.url === 'string')
|
|
178
|
+
parts.push(`[image:${data.url}]`);
|
|
179
|
+
break;
|
|
180
|
+
case 'file':
|
|
181
|
+
if (typeof data.url === 'string')
|
|
182
|
+
parts.push(`[file:${data.url}]`);
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return parts.join('');
|
|
189
|
+
}
|
|
190
|
+
export function extractCreatedMessageId(result) {
|
|
191
|
+
const list = Array.isArray(result)
|
|
192
|
+
? result
|
|
193
|
+
: result?.data;
|
|
194
|
+
const msg = list?.[0];
|
|
195
|
+
return msg?.id ?? '';
|
|
196
|
+
}
|
package/lib/webhook.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Satori webhook HTTP: token → opcode → parse → admit.
|
|
3
|
+
*/
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
+
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
6
|
+
import { type ResolvedSatoriWebhookConfig, type SatoriEventBody, type SatoriLogin } from './protocol.js';
|
|
7
|
+
export interface SatoriWebhookHandler {
|
|
8
|
+
readonly config: ResolvedSatoriWebhookConfig;
|
|
9
|
+
readonly isOpen: boolean;
|
|
10
|
+
admit(body: SatoriEventBody): void;
|
|
11
|
+
setLogin(login: SatoriLogin): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function registerSatoriWebhookRoutes(http: HttpHost, handler: SatoriWebhookHandler): HttpRouteRegistration[];
|
|
14
|
+
export declare function handleSatoriWebhookRequest(request: IncomingMessage, response: ServerResponse, handler: SatoriWebhookHandler): Promise<void>;
|
|
15
|
+
export declare function resolveSatoriOpcode(request: IncomingMessage): number | undefined;
|
|
16
|
+
export declare function verifySatoriToken(token: string | undefined, request: IncomingMessage): boolean;
|
|
17
|
+
export declare function readRequestBody(request: IncomingMessage): Promise<string>;
|
package/lib/webhook.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getLogger } from '@zhin.js/logger';
|
|
2
|
+
import { SatoriOpcode, } from './protocol.js';
|
|
3
|
+
const logger = getLogger('satori');
|
|
4
|
+
export function registerSatoriWebhookRoutes(http, handler) {
|
|
5
|
+
const path = handler.config.path;
|
|
6
|
+
return [
|
|
7
|
+
http.route('POST', path, async (request, response) => {
|
|
8
|
+
await handleSatoriWebhookRequest(request, response, handler);
|
|
9
|
+
}, { summary: 'Satori webhook callback', tags: ['satori'] }),
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
export async function handleSatoriWebhookRequest(request, response, handler) {
|
|
13
|
+
try {
|
|
14
|
+
if (!verifySatoriToken(handler.config.token, request)) {
|
|
15
|
+
response.writeHead(403, { 'Content-Type': 'application/json' });
|
|
16
|
+
response.end(JSON.stringify({ message: 'Unauthorized' }));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const opcode = resolveSatoriOpcode(request);
|
|
20
|
+
if (opcode !== SatoriOpcode.EVENT && opcode !== SatoriOpcode.META) {
|
|
21
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
22
|
+
response.end(JSON.stringify({ message: 'OK' }));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const raw = await readRequestBody(request);
|
|
26
|
+
let body;
|
|
27
|
+
try {
|
|
28
|
+
body = JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
response.writeHead(400, { 'Content-Type': 'application/json' });
|
|
32
|
+
response.end(JSON.stringify({ message: 'Invalid JSON' }));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (opcode === SatoriOpcode.EVENT && handler.isOpen) {
|
|
36
|
+
handler.admit(body);
|
|
37
|
+
}
|
|
38
|
+
else if (opcode === SatoriOpcode.META && body.login && handler.isOpen) {
|
|
39
|
+
handler.setLogin(body.login);
|
|
40
|
+
}
|
|
41
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
42
|
+
response.end(JSON.stringify({ message: 'OK' }));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.error('Satori webhook error:', error);
|
|
46
|
+
if (!response.headersSent) {
|
|
47
|
+
response.writeHead(500, { 'Content-Type': 'application/json' });
|
|
48
|
+
response.end(JSON.stringify({ message: 'Internal Server Error' }));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function resolveSatoriOpcode(request) {
|
|
53
|
+
const raw = request.headers['satori-opcode'] ?? request.headers['Satori-Opcode'];
|
|
54
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
55
|
+
if (value == null || value === '')
|
|
56
|
+
return undefined;
|
|
57
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
58
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
59
|
+
}
|
|
60
|
+
export function verifySatoriToken(token, request) {
|
|
61
|
+
if (!token)
|
|
62
|
+
return true;
|
|
63
|
+
const auth = request.headers.authorization ?? '';
|
|
64
|
+
return auth === `Bearer ${token}`;
|
|
65
|
+
}
|
|
66
|
+
export async function readRequestBody(request) {
|
|
67
|
+
const chunks = [];
|
|
68
|
+
let size = 0;
|
|
69
|
+
for await (const chunk of request) {
|
|
70
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
71
|
+
size += buffer.length;
|
|
72
|
+
if (size > 1_048_576) {
|
|
73
|
+
request.destroy();
|
|
74
|
+
throw new Error('Request body exceeds 1MB');
|
|
75
|
+
}
|
|
76
|
+
chunks.push(buffer);
|
|
77
|
+
}
|
|
78
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
79
|
+
}
|
package/lib/ws.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const WS_OPEN = 1;
|
|
2
|
+
export interface SatoriWsSocket {
|
|
3
|
+
readonly readyState: number;
|
|
4
|
+
send(data: string): void;
|
|
5
|
+
close(code?: number, reason?: string): void;
|
|
6
|
+
on(event: 'open' | 'message' | 'close' | 'error', listener: (...args: unknown[]) => void): void;
|
|
7
|
+
}
|
|
8
|
+
export type CreateSatoriWebSocket = (url: string, options?: {
|
|
9
|
+
readonly headers?: Record<string, string>;
|
|
10
|
+
}) => SatoriWsSocket;
|
|
11
|
+
export declare function defaultCreateWebSocket(url: string, options?: {
|
|
12
|
+
readonly headers?: Record<string, string>;
|
|
13
|
+
}): SatoriWsSocket;
|
package/lib/ws.js
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-satori",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Zhin.js adapter for
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Zhin.js Satori adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
+
"adapters",
|
|
17
|
+
"plugin.ts",
|
|
18
|
+
"schema.json",
|
|
16
19
|
"src",
|
|
17
20
|
"lib",
|
|
18
|
-
"client",
|
|
19
|
-
"dist",
|
|
20
21
|
"agent",
|
|
21
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
22
24
|
],
|
|
23
25
|
"keywords": [
|
|
24
26
|
"zhin",
|
|
@@ -35,23 +37,28 @@
|
|
|
35
37
|
"directory": "plugins/adapters/satori"
|
|
36
38
|
},
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"ws": "^8.21.0"
|
|
40
|
+
"ws": "^8.21.0",
|
|
41
|
+
"@zhin.js/adapter": "1.1.0",
|
|
42
|
+
"@zhin.js/core": "1.4.0",
|
|
43
|
+
"@zhin.js/host-http": "1.0.2",
|
|
44
|
+
"@zhin.js/logger": "1.0.75",
|
|
45
|
+
"@zhin.js/plugin-runtime": "1.1.0"
|
|
39
46
|
},
|
|
40
47
|
"devDependencies": {
|
|
41
48
|
"@types/node": "^26.1.0",
|
|
42
49
|
"@types/ws": "^8.18.1",
|
|
43
50
|
"typescript": "^6.0.3",
|
|
44
|
-
"
|
|
45
|
-
"@zhin.js/
|
|
46
|
-
"@zhin.js/host-router": "2.0.3",
|
|
47
|
-
"zhin.js": "4.1.2"
|
|
51
|
+
"vitest": "^4.1.10",
|
|
52
|
+
"@zhin.js/host-http": "1.0.2"
|
|
48
53
|
},
|
|
49
54
|
"peerDependencies": {
|
|
50
|
-
"zhin.js": "
|
|
51
|
-
"@zhin.js/
|
|
55
|
+
"@zhin.js/adapter": "1.1.0",
|
|
56
|
+
"@zhin.js/core": "1.4.0",
|
|
57
|
+
"@zhin.js/plugin-runtime": "1.1.0",
|
|
58
|
+
"zhin.js": "5.0.0"
|
|
52
59
|
},
|
|
53
60
|
"peerDependenciesMeta": {
|
|
54
|
-
"
|
|
61
|
+
"zhin.js": {
|
|
55
62
|
"optional": true
|
|
56
63
|
}
|
|
57
64
|
},
|
|
@@ -63,8 +70,27 @@
|
|
|
63
70
|
"engines": {
|
|
64
71
|
"node": "^20.19.0 || >=22.12.0"
|
|
65
72
|
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"registry": "https://registry.npmjs.org"
|
|
76
|
+
},
|
|
77
|
+
"zhin": {
|
|
78
|
+
"protocol": 1,
|
|
79
|
+
"type": "plugin",
|
|
80
|
+
"entry": "./plugin.ts",
|
|
81
|
+
"engine": "^1.0.0",
|
|
82
|
+
"runtime": "trusted",
|
|
83
|
+
"features": [
|
|
84
|
+
{
|
|
85
|
+
"package": "@zhin.js/adapter",
|
|
86
|
+
"api": "^1.0.0"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"plugins": []
|
|
90
|
+
},
|
|
66
91
|
"scripts": {
|
|
67
|
-
"build": "
|
|
68
|
-
"clean": "rimraf lib"
|
|
92
|
+
"build": "tsc",
|
|
93
|
+
"clean": "rimraf lib",
|
|
94
|
+
"test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/satori/tests"
|
|
69
95
|
}
|
|
70
96
|
}
|
package/plugin.ts
ADDED
package/schema.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"properties": {
|
|
6
|
+
"connection": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"enum": [
|
|
9
|
+
"ws",
|
|
10
|
+
"webhook"
|
|
11
|
+
],
|
|
12
|
+
"default": "ws",
|
|
13
|
+
"description": "ws (default) or webhook (httpHostToken POST route)"
|
|
14
|
+
},
|
|
15
|
+
"heartbeat_interval": {
|
|
16
|
+
"type": "number",
|
|
17
|
+
"default": 10000,
|
|
18
|
+
"description": "WS PING interval in milliseconds"
|
|
19
|
+
},
|
|
20
|
+
"endpoints": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": true,
|
|
26
|
+
"properties": {
|
|
27
|
+
"name": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Satori bot name"
|
|
30
|
+
},
|
|
31
|
+
"baseUrl": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Satori SDK HTTP/WS base URL (e.g. http://127.0.0.1:5140)"
|
|
34
|
+
},
|
|
35
|
+
"token": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Bearer token for API and WS IDENTIFY"
|
|
38
|
+
},
|
|
39
|
+
"path": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Webhook POST path (connection: webhook)"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"name",
|
|
46
|
+
"baseUrl"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"commandPrefix": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"default": "",
|
|
53
|
+
"description": "命令前缀(默认 '' 无前缀,任意文本按命令匹配;如 '/' 要求 / 开头)。endpoints[i] 可逐项覆盖"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"required": [
|
|
57
|
+
"endpoints"
|
|
58
|
+
]
|
|
59
|
+
}
|