@43world/43chat-openclaw-plugin 0.1.6
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 +101 -0
- package/REQUIREMENTS.md +573 -0
- package/index.ts +22 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +48 -0
- package/src/accounts.ts +137 -0
- package/src/bot.ts +484 -0
- package/src/channel.ts +415 -0
- package/src/client.ts +433 -0
- package/src/config-schema.ts +37 -0
- package/src/monitor.ts +277 -0
- package/src/outbound.ts +59 -0
- package/src/plugin-sdk-compat.ts +27 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +35 -0
- package/src/targets.ts +58 -0
- package/src/types.ts +182 -0
package/src/monitor.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
import { listEnabled43ChatAccounts, resolve43ChatAccount } from "./accounts.js";
|
|
3
|
+
import { create43ChatClient, Chat43ApiError } from "./client.js";
|
|
4
|
+
import { handle43ChatEvent } from "./bot.js";
|
|
5
|
+
import { waitUntilAbortCompat } from "./plugin-sdk-compat.js";
|
|
6
|
+
import type { Chat43AnySSEEvent, Chat43RuntimeStatusPatch, Resolved43ChatAccount } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export type Monitor43ChatOpts = {
|
|
9
|
+
config?: ClawdbotConfig;
|
|
10
|
+
runtime?: RuntimeEnv;
|
|
11
|
+
abortSignal?: AbortSignal;
|
|
12
|
+
accountId?: string;
|
|
13
|
+
statusSink?: (patch: Chat43RuntimeStatusPatch) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const monitorControllers = new Map<string, AbortController>();
|
|
17
|
+
|
|
18
|
+
function waitForDelay(delayMs: number, signal?: AbortSignal): Promise<boolean> {
|
|
19
|
+
if (!signal) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(() => resolve(false), delayMs));
|
|
21
|
+
}
|
|
22
|
+
if (signal.aborted) {
|
|
23
|
+
return Promise.resolve(true);
|
|
24
|
+
}
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const timeout = setTimeout(() => {
|
|
27
|
+
signal.removeEventListener("abort", onAbort);
|
|
28
|
+
resolve(false);
|
|
29
|
+
}, delayMs);
|
|
30
|
+
const onAbort = () => {
|
|
31
|
+
clearTimeout(timeout);
|
|
32
|
+
resolve(true);
|
|
33
|
+
};
|
|
34
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function combineSignals(signals: Array<AbortSignal | undefined>): AbortSignal | undefined {
|
|
39
|
+
const filtered = signals.filter((signal): signal is AbortSignal => Boolean(signal));
|
|
40
|
+
if (filtered.length === 0) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
if (filtered.length === 1) {
|
|
44
|
+
return filtered[0];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const onAbort = (event: Event) => {
|
|
49
|
+
const source = event.target as AbortSignal | null;
|
|
50
|
+
controller.abort(source?.reason);
|
|
51
|
+
for (const signal of filtered) {
|
|
52
|
+
signal.removeEventListener("abort", onAbort);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for (const signal of filtered) {
|
|
57
|
+
if (signal.aborted) {
|
|
58
|
+
controller.abort(signal.reason);
|
|
59
|
+
return controller.signal;
|
|
60
|
+
}
|
|
61
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return controller.signal;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function monitorSingleAccount(params: {
|
|
68
|
+
cfg: ClawdbotConfig;
|
|
69
|
+
account: Resolved43ChatAccount;
|
|
70
|
+
runtime?: RuntimeEnv;
|
|
71
|
+
abortSignal?: AbortSignal;
|
|
72
|
+
statusSink?: (patch: Chat43RuntimeStatusPatch) => void;
|
|
73
|
+
}): Promise<void> {
|
|
74
|
+
const { cfg, account, runtime, abortSignal, statusSink } = params;
|
|
75
|
+
const { accountId } = account;
|
|
76
|
+
const log = runtime?.log ?? console.log;
|
|
77
|
+
const error = runtime?.error ?? console.error;
|
|
78
|
+
const emitStatus = (patch: Chat43RuntimeStatusPatch) => statusSink?.(patch);
|
|
79
|
+
|
|
80
|
+
const localController = new AbortController();
|
|
81
|
+
monitorControllers.set(accountId, localController);
|
|
82
|
+
const combinedSignal = combineSignals([abortSignal, localController.signal]);
|
|
83
|
+
|
|
84
|
+
let reconnectAttempts = 0;
|
|
85
|
+
const reconnectBaseDelay = account.config.sseReconnectDelayMs ?? 1_000;
|
|
86
|
+
const reconnectMaxDelay = account.config.sseMaxReconnectDelayMs ?? 60_000;
|
|
87
|
+
|
|
88
|
+
const stopStatus = () => {
|
|
89
|
+
emitStatus({
|
|
90
|
+
running: false,
|
|
91
|
+
connected: false,
|
|
92
|
+
connectionState: "stopped",
|
|
93
|
+
nextRetryAt: null,
|
|
94
|
+
lastStopAt: Date.now(),
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (combinedSignal?.aborted) {
|
|
99
|
+
stopStatus();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
combinedSignal?.addEventListener("abort", stopStatus, { once: true });
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
while (!combinedSignal?.aborted) {
|
|
107
|
+
emitStatus({
|
|
108
|
+
running: true,
|
|
109
|
+
connected: false,
|
|
110
|
+
connectionState: "connecting",
|
|
111
|
+
lastStartAt: Date.now(),
|
|
112
|
+
nextRetryAt: null,
|
|
113
|
+
lastError: null,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
log(`43chat[${accountId}]: connecting SSE stream...`);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const client = create43ChatClient(account);
|
|
120
|
+
await client.connectSSE<Chat43AnySSEEvent>({
|
|
121
|
+
signal: combinedSignal,
|
|
122
|
+
onOpen: () => {
|
|
123
|
+
reconnectAttempts = 0;
|
|
124
|
+
emitStatus({
|
|
125
|
+
running: true,
|
|
126
|
+
connected: true,
|
|
127
|
+
connectionState: "connected",
|
|
128
|
+
reconnectAttempts: 0,
|
|
129
|
+
nextRetryAt: null,
|
|
130
|
+
lastConnectedAt: Date.now(),
|
|
131
|
+
lastError: null,
|
|
132
|
+
});
|
|
133
|
+
log(`43chat[${accountId}]: SSE connected`);
|
|
134
|
+
},
|
|
135
|
+
onHeartbeat: () => {
|
|
136
|
+
emitStatus({
|
|
137
|
+
running: true,
|
|
138
|
+
connected: true,
|
|
139
|
+
connectionState: "connected",
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
onInvalidFrame: (frame, reason) => {
|
|
143
|
+
error(`43chat[${accountId}]: invalid SSE frame (${reason}): ${JSON.stringify(frame)}`);
|
|
144
|
+
},
|
|
145
|
+
onEvent: async (event) => {
|
|
146
|
+
emitStatus({ lastInboundAt: Date.now() });
|
|
147
|
+
await handle43ChatEvent({
|
|
148
|
+
cfg,
|
|
149
|
+
event,
|
|
150
|
+
accountId,
|
|
151
|
+
runtime,
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (combinedSignal?.aborted) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw new Chat43ApiError({
|
|
161
|
+
message: "43Chat SSE stream closed unexpectedly",
|
|
162
|
+
retryable: true,
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (combinedSignal?.aborted) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
reconnectAttempts += 1;
|
|
170
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
171
|
+
const retryable = err instanceof Chat43ApiError ? err.retryable : true;
|
|
172
|
+
|
|
173
|
+
if (!retryable) {
|
|
174
|
+
emitStatus({
|
|
175
|
+
running: true,
|
|
176
|
+
connected: false,
|
|
177
|
+
connectionState: "error",
|
|
178
|
+
reconnectAttempts,
|
|
179
|
+
lastError: message,
|
|
180
|
+
nextRetryAt: null,
|
|
181
|
+
lastDisconnect: {
|
|
182
|
+
code: err instanceof Chat43ApiError ? err.status ?? 0 : 0,
|
|
183
|
+
reason: message,
|
|
184
|
+
at: Date.now(),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
error(`43chat[${accountId}]: fatal SSE error: ${message}`);
|
|
188
|
+
await waitUntilAbortCompat(combinedSignal);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const delay = Math.min(
|
|
193
|
+
reconnectBaseDelay * Math.max(1, 2 ** (reconnectAttempts - 1)),
|
|
194
|
+
reconnectMaxDelay,
|
|
195
|
+
);
|
|
196
|
+
const nextRetryAt = Date.now() + delay;
|
|
197
|
+
|
|
198
|
+
emitStatus({
|
|
199
|
+
running: true,
|
|
200
|
+
connected: false,
|
|
201
|
+
connectionState: "backoff",
|
|
202
|
+
reconnectAttempts,
|
|
203
|
+
nextRetryAt,
|
|
204
|
+
lastError: message,
|
|
205
|
+
lastDisconnect: {
|
|
206
|
+
code: err instanceof Chat43ApiError ? err.status ?? 0 : 0,
|
|
207
|
+
reason: message,
|
|
208
|
+
at: Date.now(),
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
error(`43chat[${accountId}]: SSE error, retrying in ${delay}ms: ${message}`);
|
|
213
|
+
|
|
214
|
+
const aborted = await waitForDelay(delay, combinedSignal);
|
|
215
|
+
if (aborted) {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
if (monitorControllers.get(accountId) === localController) {
|
|
222
|
+
monitorControllers.delete(accountId);
|
|
223
|
+
}
|
|
224
|
+
stopStatus();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function monitor43ChatProvider(opts: Monitor43ChatOpts = {}): Promise<void> {
|
|
229
|
+
const cfg = opts.config;
|
|
230
|
+
if (!cfg) {
|
|
231
|
+
throw new Error("Config is required for 43Chat monitor");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (opts.accountId) {
|
|
235
|
+
const account = resolve43ChatAccount({ cfg, accountId: opts.accountId });
|
|
236
|
+
if (!account.enabled || !account.configured) {
|
|
237
|
+
throw new Error(`43Chat account "${opts.accountId}" not configured or disabled`);
|
|
238
|
+
}
|
|
239
|
+
return monitorSingleAccount({
|
|
240
|
+
cfg,
|
|
241
|
+
account,
|
|
242
|
+
runtime: opts.runtime,
|
|
243
|
+
abortSignal: opts.abortSignal,
|
|
244
|
+
statusSink: opts.statusSink,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const accounts = listEnabled43ChatAccounts(cfg);
|
|
249
|
+
if (accounts.length === 0) {
|
|
250
|
+
throw new Error("No enabled 43Chat accounts configured");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await Promise.all(
|
|
254
|
+
accounts.map((account) =>
|
|
255
|
+
monitorSingleAccount({
|
|
256
|
+
cfg,
|
|
257
|
+
account,
|
|
258
|
+
runtime: opts.runtime,
|
|
259
|
+
abortSignal: opts.abortSignal,
|
|
260
|
+
statusSink: opts.statusSink,
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function stop43ChatMonitor(accountId?: string): void {
|
|
267
|
+
if (accountId) {
|
|
268
|
+
monitorControllers.get(accountId)?.abort();
|
|
269
|
+
monitorControllers.delete(accountId);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const controller of monitorControllers.values()) {
|
|
274
|
+
controller.abort();
|
|
275
|
+
}
|
|
276
|
+
monitorControllers.clear();
|
|
277
|
+
}
|
package/src/outbound.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ChannelOutboundAdapter, ChannelOutboundContext, ReplyPayload } from "openclaw/plugin-sdk";
|
|
2
|
+
import { sendMessage43Chat } from "./send.js";
|
|
3
|
+
import { log } from "node:console";
|
|
4
|
+
|
|
5
|
+
function chunkText(text: string, limit: number): string[] {
|
|
6
|
+
if (text.length <= limit) {
|
|
7
|
+
return [text];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const chunks: string[] = [];
|
|
11
|
+
for (let index = 0; index < text.length; index += limit) {
|
|
12
|
+
chunks.push(text.slice(index, index + limit));
|
|
13
|
+
}
|
|
14
|
+
return chunks;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const chat43Outbound: ChannelOutboundAdapter = {
|
|
18
|
+
deliveryMode: "direct",
|
|
19
|
+
chunker: chunkText,
|
|
20
|
+
chunkerMode: "text",
|
|
21
|
+
textChunkLimit: 4000,
|
|
22
|
+
|
|
23
|
+
sendText: async (ctx: ChannelOutboundContext) => {
|
|
24
|
+
log(`43chat[${ctx.accountId}]: send text ${ctx.text}`);
|
|
25
|
+
const result = await sendMessage43Chat({
|
|
26
|
+
cfg: ctx.cfg,
|
|
27
|
+
to: ctx.to ?? "",
|
|
28
|
+
text: ctx.text ?? "",
|
|
29
|
+
accountId: ctx.accountId ?? undefined,
|
|
30
|
+
});
|
|
31
|
+
log(`43chat[${ctx.accountId}]: send text result ${result.messageId} ${result.chatId}`);
|
|
32
|
+
return {
|
|
33
|
+
channel: "43chat" as const,
|
|
34
|
+
messageId: result.messageId,
|
|
35
|
+
chatId: result.chatId,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
sendPayload: async (ctx: ChannelOutboundContext & { payload: ReplyPayload }) => {
|
|
40
|
+
const text = ctx.payload.text ?? ctx.text ?? "";
|
|
41
|
+
log(`43chat[${ctx.accountId}]: send payload ${text}`);
|
|
42
|
+
const result = await sendMessage43Chat({
|
|
43
|
+
cfg: ctx.cfg,
|
|
44
|
+
to: ctx.to ?? "",
|
|
45
|
+
text,
|
|
46
|
+
accountId: ctx.accountId ?? undefined,
|
|
47
|
+
});
|
|
48
|
+
log(`43chat[${ctx.accountId}]: send payload result ${result.messageId} ${result.chatId}`);
|
|
49
|
+
return {
|
|
50
|
+
channel: "43chat" as const,
|
|
51
|
+
messageId: result.messageId,
|
|
52
|
+
chatId: result.chatId,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
sendMedia: async () => {
|
|
57
|
+
throw new Error("43Chat channel does not support media messages");
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as pluginSdk from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
type PluginSdkCompat = {
|
|
4
|
+
waitUntilAbort?: (abortSignal?: AbortSignal) => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function waitUntilAbortFallback(abortSignal?: AbortSignal): Promise<void> {
|
|
8
|
+
if (!abortSignal) {
|
|
9
|
+
return new Promise<void>(() => undefined);
|
|
10
|
+
}
|
|
11
|
+
if (abortSignal.aborted) {
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
}
|
|
14
|
+
return new Promise<void>((resolve) => {
|
|
15
|
+
abortSignal.addEventListener("abort", () => resolve(), { once: true });
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function waitUntilAbortCompat(
|
|
20
|
+
abortSignal?: AbortSignal,
|
|
21
|
+
sdk: PluginSdkCompat = pluginSdk as PluginSdkCompat,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
if (typeof sdk.waitUntilAbort === "function") {
|
|
24
|
+
return sdk.waitUntilAbort(abortSignal);
|
|
25
|
+
}
|
|
26
|
+
return waitUntilAbortFallback(abortSignal);
|
|
27
|
+
}
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
let chat43Runtime: PluginRuntime | undefined;
|
|
4
|
+
|
|
5
|
+
export function set43ChatRuntime(runtime: PluginRuntime): void {
|
|
6
|
+
chat43Runtime = runtime;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function get43ChatRuntime(): PluginRuntime {
|
|
10
|
+
if (!chat43Runtime) {
|
|
11
|
+
throw new Error("43Chat runtime not initialized");
|
|
12
|
+
}
|
|
13
|
+
return chat43Runtime;
|
|
14
|
+
}
|
package/src/send.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { Chat43SendResult } from "./types.js";
|
|
3
|
+
import { resolve43ChatAccount } from "./accounts.js";
|
|
4
|
+
import { create43ChatClient } from "./client.js";
|
|
5
|
+
import { parse43ChatTarget } from "./targets.js";
|
|
6
|
+
|
|
7
|
+
export type Send43ChatMessageParams = {
|
|
8
|
+
cfg: ClawdbotConfig;
|
|
9
|
+
to: string;
|
|
10
|
+
text: string;
|
|
11
|
+
accountId?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function sendMessage43Chat(
|
|
15
|
+
params: Send43ChatMessageParams,
|
|
16
|
+
): Promise<Chat43SendResult> {
|
|
17
|
+
const { cfg, to, text, accountId } = params;
|
|
18
|
+
const account = resolve43ChatAccount({ cfg, accountId });
|
|
19
|
+
|
|
20
|
+
if (!account.configured) {
|
|
21
|
+
throw new Error(`43Chat account "${account.accountId}" not configured`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const target = parse43ChatTarget(to);
|
|
25
|
+
if (!target) {
|
|
26
|
+
throw new Error(`Invalid 43Chat target: ${to}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const client = create43ChatClient(account);
|
|
30
|
+
return client.sendText({
|
|
31
|
+
targetType: target.kind,
|
|
32
|
+
targetId: target.id,
|
|
33
|
+
text: text ?? "",
|
|
34
|
+
});
|
|
35
|
+
}
|
package/src/targets.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type Chat43TargetKind = "user" | "group";
|
|
2
|
+
|
|
3
|
+
export type Parsed43ChatTarget = {
|
|
4
|
+
kind: Chat43TargetKind;
|
|
5
|
+
id: string;
|
|
6
|
+
normalized: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function stripChannelPrefix(raw: string): string {
|
|
10
|
+
return raw.replace(/^43chat:/i, "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parse43ChatTarget(raw: string): Parsed43ChatTarget | null {
|
|
14
|
+
const trimmed = stripChannelPrefix(raw.trim());
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const match = /^(user|group):(.+)$/i.exec(trimmed);
|
|
20
|
+
if (match) {
|
|
21
|
+
const kind = match[1].toLowerCase() as Chat43TargetKind;
|
|
22
|
+
const id = match[2]?.trim();
|
|
23
|
+
if (!id) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
kind,
|
|
28
|
+
id,
|
|
29
|
+
normalized: `${kind}:${id}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (/^\d+$/.test(trimmed)) {
|
|
34
|
+
return {
|
|
35
|
+
kind: "user",
|
|
36
|
+
id: trimmed,
|
|
37
|
+
normalized: `user:${trimmed}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalize43ChatTarget(raw: string): string | null {
|
|
45
|
+
return parse43ChatTarget(raw)?.normalized ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function looksLike43ChatId(raw: string): boolean {
|
|
49
|
+
return parse43ChatTarget(raw) !== null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function to43ChatAddress(raw: string): string | null {
|
|
53
|
+
const normalized = normalize43ChatTarget(raw);
|
|
54
|
+
if (!normalized) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return `43chat:${normalized}`;
|
|
58
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { Chat43AccountConfigSchema, Chat43ConfigSchema } from "./config-schema.js";
|
|
3
|
+
|
|
4
|
+
export type Chat43Config = z.infer<typeof Chat43ConfigSchema>;
|
|
5
|
+
export type Chat43AccountConfig = z.infer<typeof Chat43AccountConfigSchema>;
|
|
6
|
+
|
|
7
|
+
export type Resolved43ChatAccount = {
|
|
8
|
+
accountId: string;
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
configured: boolean;
|
|
11
|
+
name?: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
config: Chat43AccountConfig;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type Chat43ConnectionState =
|
|
18
|
+
| "idle"
|
|
19
|
+
| "connecting"
|
|
20
|
+
| "connected"
|
|
21
|
+
| "backoff"
|
|
22
|
+
| "error"
|
|
23
|
+
| "stopped";
|
|
24
|
+
|
|
25
|
+
export type Chat43DisconnectInfo = {
|
|
26
|
+
code: number;
|
|
27
|
+
reason: string;
|
|
28
|
+
at: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type Chat43RuntimeStatusPatch = {
|
|
32
|
+
running?: boolean;
|
|
33
|
+
connected?: boolean;
|
|
34
|
+
connectionState?: Chat43ConnectionState;
|
|
35
|
+
reconnectAttempts?: number;
|
|
36
|
+
nextRetryAt?: number | null;
|
|
37
|
+
lastConnectedAt?: number | null;
|
|
38
|
+
lastDisconnect?: Chat43DisconnectInfo | null;
|
|
39
|
+
lastError?: string | null;
|
|
40
|
+
lastStartAt?: number | null;
|
|
41
|
+
lastStopAt?: number | null;
|
|
42
|
+
lastInboundAt?: number | null;
|
|
43
|
+
lastOutboundAt?: number | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type Chat43EventType =
|
|
47
|
+
| "private_message"
|
|
48
|
+
| "group_message"
|
|
49
|
+
| "friend_request"
|
|
50
|
+
| "friend_accepted"
|
|
51
|
+
| "group_invitation"
|
|
52
|
+
| "group_member_joined"
|
|
53
|
+
| "system_notice"
|
|
54
|
+
| "heartbeat";
|
|
55
|
+
|
|
56
|
+
export type Chat43SSEEventEnvelope<T = unknown> = {
|
|
57
|
+
id: string;
|
|
58
|
+
event_type: Chat43EventType;
|
|
59
|
+
data: T;
|
|
60
|
+
timestamp: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type Chat43PrivateMessageEventData = {
|
|
64
|
+
message_id: string;
|
|
65
|
+
from_user_id: number;
|
|
66
|
+
from_nickname: string;
|
|
67
|
+
to_user_id: number;
|
|
68
|
+
content: string;
|
|
69
|
+
content_type: string;
|
|
70
|
+
timestamp: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type Chat43GroupMessageEventData = {
|
|
74
|
+
message_id: string;
|
|
75
|
+
group_id: number;
|
|
76
|
+
from_user_id: number;
|
|
77
|
+
from_nickname: string;
|
|
78
|
+
content: string;
|
|
79
|
+
content_type: string;
|
|
80
|
+
timestamp: number;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type Chat43FriendRequestEventData = {
|
|
84
|
+
request_id: number;
|
|
85
|
+
from_user_id: number;
|
|
86
|
+
from_nickname: string;
|
|
87
|
+
from_avatar: string;
|
|
88
|
+
request_msg: string;
|
|
89
|
+
timestamp: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type Chat43FriendAcceptedEventData = {
|
|
93
|
+
request_id: number;
|
|
94
|
+
from_user_id: number;
|
|
95
|
+
from_nickname: string;
|
|
96
|
+
timestamp: number;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type Chat43GroupInvitationEventData = {
|
|
100
|
+
invitation_id: number;
|
|
101
|
+
group_id: number;
|
|
102
|
+
group_name: string;
|
|
103
|
+
inviter_id: number;
|
|
104
|
+
inviter_name: string;
|
|
105
|
+
invite_msg: string;
|
|
106
|
+
timestamp: number;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type Chat43GroupMemberJoinedEventData = {
|
|
110
|
+
group_id: number;
|
|
111
|
+
group_name: string;
|
|
112
|
+
user_id: number;
|
|
113
|
+
nickname: string;
|
|
114
|
+
join_method: string;
|
|
115
|
+
timestamp: number;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type Chat43SystemNoticeEventData = {
|
|
119
|
+
notice_id: string;
|
|
120
|
+
title: string;
|
|
121
|
+
content: string;
|
|
122
|
+
level: string;
|
|
123
|
+
timestamp: number;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type Chat43AnySSEEvent =
|
|
127
|
+
| Chat43SSEEventEnvelope<Chat43PrivateMessageEventData>
|
|
128
|
+
| Chat43SSEEventEnvelope<Chat43GroupMessageEventData>
|
|
129
|
+
| Chat43SSEEventEnvelope<Chat43FriendRequestEventData>
|
|
130
|
+
| Chat43SSEEventEnvelope<Chat43FriendAcceptedEventData>
|
|
131
|
+
| Chat43SSEEventEnvelope<Chat43GroupInvitationEventData>
|
|
132
|
+
| Chat43SSEEventEnvelope<Chat43GroupMemberJoinedEventData>
|
|
133
|
+
| Chat43SSEEventEnvelope<Chat43SystemNoticeEventData>;
|
|
134
|
+
|
|
135
|
+
export type Chat43OpenApiResponse<T> = {
|
|
136
|
+
code: number;
|
|
137
|
+
message: string;
|
|
138
|
+
timestamp: number;
|
|
139
|
+
data?: T;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export type Chat43AgentProfile = {
|
|
143
|
+
agent_id: string;
|
|
144
|
+
name: string;
|
|
145
|
+
avatar: string;
|
|
146
|
+
thumbnail_url: string;
|
|
147
|
+
description: string;
|
|
148
|
+
user_id: number;
|
|
149
|
+
im_user_id: string;
|
|
150
|
+
developer_name: string;
|
|
151
|
+
developer_email: string;
|
|
152
|
+
gender: number;
|
|
153
|
+
birthday: string;
|
|
154
|
+
city: string;
|
|
155
|
+
status: number;
|
|
156
|
+
created_at: number;
|
|
157
|
+
claim_url?: string;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export type Chat43Probe = {
|
|
161
|
+
ok: boolean;
|
|
162
|
+
agentId?: string;
|
|
163
|
+
userId?: number;
|
|
164
|
+
name?: string;
|
|
165
|
+
status?: number;
|
|
166
|
+
error?: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type Chat43SendResult = {
|
|
170
|
+
messageId: string;
|
|
171
|
+
chatId: string;
|
|
172
|
+
targetType: "user" | "group";
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type Chat43MessageContext = {
|
|
176
|
+
messageId: string;
|
|
177
|
+
senderId: string;
|
|
178
|
+
text: string;
|
|
179
|
+
timestamp: number;
|
|
180
|
+
target: string;
|
|
181
|
+
chatType: "direct" | "group";
|
|
182
|
+
};
|