@wecode-ai/weibo-openclaw-plugin 1.0.0 → 1.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/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/src/accounts.d.ts +10 -0
- package/dist/src/accounts.d.ts.map +1 -0
- package/dist/src/accounts.js +113 -0
- package/dist/src/accounts.js.map +1 -0
- package/dist/src/bot.d.ts +26 -0
- package/dist/src/bot.d.ts.map +1 -0
- package/dist/src/bot.js +385 -0
- package/dist/src/bot.js.map +1 -0
- package/dist/src/channel.d.ts +4 -0
- package/dist/src/channel.d.ts.map +1 -0
- package/dist/src/channel.js +317 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/client.d.ts +51 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +336 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config-schema.d.ts +86 -0
- package/dist/src/config-schema.d.ts.map +1 -0
- package/{src/config-schema.ts → dist/src/config-schema.js} +19 -24
- package/dist/src/config-schema.js.map +1 -0
- package/dist/src/fingerprint.d.ts +4 -0
- package/dist/src/fingerprint.d.ts.map +1 -0
- package/dist/src/fingerprint.js +19 -0
- package/dist/src/fingerprint.js.map +1 -0
- package/dist/src/monitor.d.ts +13 -0
- package/dist/src/monitor.d.ts.map +1 -0
- package/dist/src/monitor.js +169 -0
- package/dist/src/monitor.js.map +1 -0
- package/dist/src/outbound-stream.d.ts +32 -0
- package/dist/src/outbound-stream.d.ts.map +1 -0
- package/dist/src/outbound-stream.js +184 -0
- package/dist/src/outbound-stream.js.map +1 -0
- package/dist/src/outbound.d.ts +3 -0
- package/dist/src/outbound.d.ts.map +1 -0
- package/dist/src/outbound.js +38 -0
- package/dist/src/outbound.js.map +1 -0
- package/dist/src/plugin-sdk-compat.d.ts +21 -0
- package/dist/src/plugin-sdk-compat.d.ts.map +1 -0
- package/dist/src/plugin-sdk-compat.js +47 -0
- package/dist/src/plugin-sdk-compat.js.map +1 -0
- package/dist/src/policy.d.ts +5 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +6 -0
- package/dist/src/policy.js.map +1 -0
- package/dist/src/runtime.d.ts +4 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +11 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/search-schema.d.ts +6 -0
- package/dist/src/search-schema.d.ts.map +1 -0
- package/dist/src/search-schema.js +5 -0
- package/dist/src/search-schema.js.map +1 -0
- package/dist/src/send.d.ts +14 -0
- package/dist/src/send.d.ts.map +1 -0
- package/dist/src/send.js +58 -0
- package/dist/src/send.js.map +1 -0
- package/dist/src/sim-page.d.ts +51 -0
- package/dist/src/sim-page.d.ts.map +1 -0
- package/dist/src/sim-page.js +72 -0
- package/dist/src/sim-page.js.map +1 -0
- package/dist/src/sim-store.d.ts +54 -0
- package/dist/src/sim-store.d.ts.map +1 -0
- package/dist/src/sim-store.js +117 -0
- package/dist/src/sim-store.js.map +1 -0
- package/dist/src/targets.d.ts +4 -0
- package/dist/src/targets.d.ts.map +1 -0
- package/dist/src/targets.js +15 -0
- package/dist/src/targets.js.map +1 -0
- package/dist/src/token.d.ts +27 -0
- package/dist/src/token.d.ts.map +1 -0
- package/dist/src/token.js +144 -0
- package/dist/src/token.js.map +1 -0
- package/dist/src/tools-config.d.ts +21 -0
- package/dist/src/tools-config.d.ts.map +1 -0
- package/dist/src/tools-config.js +42 -0
- package/dist/src/tools-config.js.map +1 -0
- package/dist/src/types.d.ts +76 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/weibo-hot-search.d.ts +44 -0
- package/dist/src/weibo-hot-search.d.ts.map +1 -0
- package/dist/src/weibo-hot-search.js +215 -0
- package/dist/src/weibo-hot-search.js.map +1 -0
- package/dist/src/weibo-search.d.ts +71 -0
- package/dist/src/weibo-search.d.ts.map +1 -0
- package/dist/src/weibo-search.js +182 -0
- package/dist/src/weibo-search.js.map +1 -0
- package/dist/src/weibo-status.d.ts +61 -0
- package/dist/src/weibo-status.d.ts.map +1 -0
- package/dist/src/weibo-status.js +194 -0
- package/dist/src/weibo-status.js.map +1 -0
- package/openclaw.plugin.json +12 -0
- package/package.json +4 -5
- package/skills/weibo-hot-search/SKILL.md +161 -0
- package/skills/weibo-search/SKILL.md +116 -0
- package/skills/weibo-status/SKILL.md +97 -0
- package/index.ts +0 -67
- package/src/accounts.ts +0 -134
- package/src/bot.ts +0 -486
- package/src/channel.ts +0 -391
- package/src/client.ts +0 -435
- package/src/fingerprint.ts +0 -25
- package/src/monitor.ts +0 -206
- package/src/outbound-stream.ts +0 -241
- package/src/outbound.ts +0 -49
- package/src/plugin-sdk-compat.ts +0 -82
- package/src/policy.ts +0 -10
- package/src/runtime.ts +0 -14
- package/src/search-schema.ts +0 -7
- package/src/send.ts +0 -80
- package/src/sim-page.ts +0 -140
- package/src/sim-store.ts +0 -186
- package/src/targets.ts +0 -14
- package/src/token.ts +0 -207
- package/src/tools-config.ts +0 -55
- package/src/types.ts +0 -95
- package/src/weibo-hot-search.ts +0 -345
- package/src/weibo-search.ts +0 -333
- package/src/weibo-status.ts +0 -341
package/src/client.ts
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "module";
|
|
2
|
-
import WebSocket from "ws";
|
|
3
|
-
import type { ResolvedWeiboAccount, WeiboRuntimeStatusPatch } from "./types.js";
|
|
4
|
-
|
|
5
|
-
// Read version from package.json
|
|
6
|
-
const require = createRequire(import.meta.url);
|
|
7
|
-
const { version: PLUGIN_VERSION } = require("../package.json") as { version: string };
|
|
8
|
-
import {
|
|
9
|
-
getValidToken,
|
|
10
|
-
clearTokenCache,
|
|
11
|
-
formatWeiboTokenFetchErrorMessage,
|
|
12
|
-
isRetryableWeiboTokenFetchError,
|
|
13
|
-
} from "./token.js";
|
|
14
|
-
import { getWeiboConnectionFingerprint } from "./fingerprint.js";
|
|
15
|
-
|
|
16
|
-
export type WebSocketMessageHandler = (data: unknown) => void;
|
|
17
|
-
export type WebSocketErrorHandler = (error: Error) => void;
|
|
18
|
-
export type WebSocketCloseHandler = (code: number, reason: string) => void;
|
|
19
|
-
export type WebSocketOpenHandler = () => void;
|
|
20
|
-
export type WebSocketStatusHandler = (patch: WeiboRuntimeStatusPatch) => void;
|
|
21
|
-
|
|
22
|
-
// Ping interval: 30 seconds
|
|
23
|
-
const PING_INTERVAL_MS = 30_000;
|
|
24
|
-
// Initial reconnect delay: 1 second
|
|
25
|
-
const INITIAL_RECONNECT_DELAY_MS = 1_000;
|
|
26
|
-
// Maximum reconnect delay: 60 seconds
|
|
27
|
-
const MAX_RECONNECT_DELAY_MS = 60_000;
|
|
28
|
-
// Maximum reconnect attempts (0 = infinite)
|
|
29
|
-
const MAX_RECONNECT_ATTEMPTS = 0;
|
|
30
|
-
|
|
31
|
-
export type WeiboClientOptions = {
|
|
32
|
-
onMessage?: WebSocketMessageHandler;
|
|
33
|
-
onError?: WebSocketErrorHandler;
|
|
34
|
-
onClose?: WebSocketCloseHandler;
|
|
35
|
-
onOpen?: WebSocketOpenHandler;
|
|
36
|
-
onStatus?: WebSocketStatusHandler;
|
|
37
|
-
autoReconnect?: boolean;
|
|
38
|
-
maxReconnectAttempts?: number;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
function isRetryableConnectError(err: unknown): boolean {
|
|
42
|
-
const retryableTokenError = isRetryableWeiboTokenFetchError(err);
|
|
43
|
-
if (retryableTokenError !== null) {
|
|
44
|
-
return retryableTokenError;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (err instanceof Error) {
|
|
48
|
-
const message = err.message.toLowerCase();
|
|
49
|
-
if (message.includes("not configured")) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export class WeiboWebSocketClient {
|
|
58
|
-
private ws: WebSocket | null = null;
|
|
59
|
-
private messageHandler: WebSocketMessageHandler | null = null;
|
|
60
|
-
private errorHandler: WebSocketErrorHandler | null = null;
|
|
61
|
-
private closeHandler: WebSocketCloseHandler | null = null;
|
|
62
|
-
private openHandler: WebSocketOpenHandler | null = null;
|
|
63
|
-
private statusHandler: WebSocketStatusHandler | null = null;
|
|
64
|
-
|
|
65
|
-
// Heartbeat
|
|
66
|
-
private pingInterval: NodeJS.Timeout | null = null;
|
|
67
|
-
private lastPongTime: number = 0;
|
|
68
|
-
private readonly PING_TIMEOUT_MS = 10_000;
|
|
69
|
-
|
|
70
|
-
// Reconnection
|
|
71
|
-
private reconnectAttempts = 0;
|
|
72
|
-
private reconnectTimeout: NodeJS.Timeout | null = null;
|
|
73
|
-
private isConnecting = false;
|
|
74
|
-
private shouldReconnect = true;
|
|
75
|
-
private autoReconnect: boolean;
|
|
76
|
-
private maxReconnectAttempts: number;
|
|
77
|
-
|
|
78
|
-
constructor(
|
|
79
|
-
private account: ResolvedWeiboAccount,
|
|
80
|
-
private options: WeiboClientOptions = {}
|
|
81
|
-
) {
|
|
82
|
-
this.autoReconnect = options.autoReconnect ?? true;
|
|
83
|
-
this.maxReconnectAttempts = options.maxReconnectAttempts ?? MAX_RECONNECT_ATTEMPTS;
|
|
84
|
-
this.messageHandler = options.onMessage ?? null;
|
|
85
|
-
this.errorHandler = options.onError ?? null;
|
|
86
|
-
this.closeHandler = options.onClose ?? null;
|
|
87
|
-
this.openHandler = options.onOpen ?? null;
|
|
88
|
-
this.statusHandler = options.onStatus ?? null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private emitStatus(patch: WeiboRuntimeStatusPatch): void {
|
|
92
|
-
this.statusHandler?.(patch);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async connect(): Promise<void> {
|
|
96
|
-
if (this.isConnecting || this.ws?.readyState === WebSocket.OPEN) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.isConnecting = true;
|
|
101
|
-
this.shouldReconnect = true;
|
|
102
|
-
this.emitStatus({
|
|
103
|
-
running: true,
|
|
104
|
-
connected: false,
|
|
105
|
-
connectionState: "connecting",
|
|
106
|
-
nextRetryAt: null,
|
|
107
|
-
lastError: null,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const { wsEndpoint, appId, tokenEndpoint } = this.account;
|
|
111
|
-
|
|
112
|
-
if (!wsEndpoint) {
|
|
113
|
-
this.isConnecting = false;
|
|
114
|
-
throw new Error(
|
|
115
|
-
`WebSocket endpoint not configured for account "${this.account.accountId}"`
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!appId) {
|
|
120
|
-
this.isConnecting = false;
|
|
121
|
-
throw new Error(
|
|
122
|
-
`App ID not configured for account "${this.account.accountId}"`
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
// Fetch token from API
|
|
128
|
-
const token = await getValidToken(this.account, tokenEndpoint);
|
|
129
|
-
|
|
130
|
-
const url = new URL(wsEndpoint);
|
|
131
|
-
url.searchParams.set("app_id", appId);
|
|
132
|
-
url.searchParams.set("token", token);
|
|
133
|
-
url.searchParams.set("version", PLUGIN_VERSION);
|
|
134
|
-
|
|
135
|
-
this.ws = new WebSocket(url.toString());
|
|
136
|
-
|
|
137
|
-
this.ws.on("open", () => {
|
|
138
|
-
this.isConnecting = false;
|
|
139
|
-
this.reconnectAttempts = 0;
|
|
140
|
-
this.lastPongTime = Date.now();
|
|
141
|
-
this.startHeartbeat();
|
|
142
|
-
this.emitStatus({
|
|
143
|
-
running: true,
|
|
144
|
-
connected: true,
|
|
145
|
-
connectionState: "connected",
|
|
146
|
-
reconnectAttempts: 0,
|
|
147
|
-
nextRetryAt: null,
|
|
148
|
-
lastConnectedAt: Date.now(),
|
|
149
|
-
lastError: null,
|
|
150
|
-
});
|
|
151
|
-
this.openHandler?.();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
this.ws.on("message", (data) => {
|
|
155
|
-
try {
|
|
156
|
-
const text = data.toString();
|
|
157
|
-
|
|
158
|
-
// Handle pong response
|
|
159
|
-
if (text === "pong" || text === "{\"type\":\"pong\"}") {
|
|
160
|
-
this.lastPongTime = Date.now();
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const parsed = JSON.parse(text);
|
|
165
|
-
this.messageHandler?.(parsed);
|
|
166
|
-
} catch {
|
|
167
|
-
// Ignore invalid JSON
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
this.ws.on("error", (err) => {
|
|
172
|
-
this.emitStatus({
|
|
173
|
-
running: true,
|
|
174
|
-
connected: false,
|
|
175
|
-
connectionState: "error",
|
|
176
|
-
lastError: err.message,
|
|
177
|
-
});
|
|
178
|
-
this.errorHandler?.(err);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
this.ws.on("close", (code, reason) => {
|
|
182
|
-
this.isConnecting = false;
|
|
183
|
-
this.stopHeartbeat();
|
|
184
|
-
const reasonStr = reason.toString() || "unknown";
|
|
185
|
-
if (code === 4002 || /invalid token/i.test(reasonStr)) {
|
|
186
|
-
clearTokenCache(this.account.accountId);
|
|
187
|
-
}
|
|
188
|
-
this.emitStatus({
|
|
189
|
-
running: this.shouldReconnect,
|
|
190
|
-
connected: false,
|
|
191
|
-
connectionState:
|
|
192
|
-
this.shouldReconnect && this.autoReconnect ? "error" : "stopped",
|
|
193
|
-
lastDisconnect: {
|
|
194
|
-
code,
|
|
195
|
-
reason: reasonStr,
|
|
196
|
-
at: Date.now(),
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
this.closeHandler?.(code, reasonStr);
|
|
200
|
-
|
|
201
|
-
// Auto reconnect if enabled
|
|
202
|
-
if (this.shouldReconnect && this.autoReconnect) {
|
|
203
|
-
this.scheduleReconnect(reasonStr);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
this.ws.on("pong", () => {
|
|
208
|
-
this.lastPongTime = Date.now();
|
|
209
|
-
});
|
|
210
|
-
} catch (err) {
|
|
211
|
-
this.isConnecting = false;
|
|
212
|
-
const message =
|
|
213
|
-
formatWeiboTokenFetchErrorMessage(err)
|
|
214
|
-
?? (err instanceof Error ? err.message : String(err));
|
|
215
|
-
this.emitStatus({
|
|
216
|
-
running: true,
|
|
217
|
-
connected: false,
|
|
218
|
-
connectionState: "error",
|
|
219
|
-
lastError: message,
|
|
220
|
-
});
|
|
221
|
-
// Schedule reconnect on connection failure
|
|
222
|
-
if (this.shouldReconnect && this.autoReconnect && isRetryableConnectError(err)) {
|
|
223
|
-
this.scheduleReconnect(message);
|
|
224
|
-
}
|
|
225
|
-
throw err;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private startHeartbeat(): void {
|
|
230
|
-
this.stopHeartbeat();
|
|
231
|
-
|
|
232
|
-
this.pingInterval = setInterval(() => {
|
|
233
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
234
|
-
// Check if we received pong recently
|
|
235
|
-
const timeSinceLastPong = Date.now() - this.lastPongTime;
|
|
236
|
-
if (timeSinceLastPong > PING_INTERVAL_MS + this.PING_TIMEOUT_MS) {
|
|
237
|
-
// Connection may be dead, close and reconnect
|
|
238
|
-
console.warn(
|
|
239
|
-
`weibo[${this.account.accountId}]: pong timeout, closing connection`
|
|
240
|
-
);
|
|
241
|
-
this.ws.terminate();
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Send ping
|
|
246
|
-
try {
|
|
247
|
-
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
248
|
-
} catch (err) {
|
|
249
|
-
console.error(
|
|
250
|
-
`weibo[${this.account.accountId}]: failed to send ping:`,
|
|
251
|
-
err
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}, PING_INTERVAL_MS);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private stopHeartbeat(): void {
|
|
259
|
-
if (this.pingInterval) {
|
|
260
|
-
clearInterval(this.pingInterval);
|
|
261
|
-
this.pingInterval = null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private scheduleReconnect(lastError?: string): void {
|
|
266
|
-
if (this.reconnectTimeout) {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Check max reconnect attempts
|
|
271
|
-
if (
|
|
272
|
-
this.maxReconnectAttempts > 0 &&
|
|
273
|
-
this.reconnectAttempts >= this.maxReconnectAttempts
|
|
274
|
-
) {
|
|
275
|
-
console.error(
|
|
276
|
-
`weibo[${this.account.accountId}]: max reconnect attempts reached`
|
|
277
|
-
);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Calculate delay with exponential backoff
|
|
282
|
-
const delay = Math.min(
|
|
283
|
-
INITIAL_RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),
|
|
284
|
-
MAX_RECONNECT_DELAY_MS
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
this.reconnectAttempts++;
|
|
288
|
-
this.emitStatus({
|
|
289
|
-
running: true,
|
|
290
|
-
connected: false,
|
|
291
|
-
connectionState: "backoff",
|
|
292
|
-
reconnectAttempts: this.reconnectAttempts,
|
|
293
|
-
nextRetryAt: Date.now() + delay,
|
|
294
|
-
lastError: lastError ?? null,
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
console.log(
|
|
298
|
-
`weibo[${this.account.accountId}]: reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
302
|
-
this.reconnectTimeout = null;
|
|
303
|
-
this.connect().catch((err) => {
|
|
304
|
-
console.error(
|
|
305
|
-
`weibo[${this.account.accountId}]: reconnect failed:`,
|
|
306
|
-
err
|
|
307
|
-
);
|
|
308
|
-
});
|
|
309
|
-
}, delay);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
onMessage(handler: WebSocketMessageHandler): void {
|
|
313
|
-
this.messageHandler = handler;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
onError(handler: WebSocketErrorHandler): void {
|
|
317
|
-
this.errorHandler = handler;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
onClose(handler: WebSocketCloseHandler): void {
|
|
321
|
-
this.closeHandler = handler;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
onOpen(handler: WebSocketOpenHandler): void {
|
|
325
|
-
this.openHandler = handler;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
onStatus(handler: WebSocketStatusHandler): void {
|
|
329
|
-
this.statusHandler = handler;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
send(data: unknown): boolean {
|
|
333
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
334
|
-
try {
|
|
335
|
-
this.ws.send(JSON.stringify(data));
|
|
336
|
-
return true;
|
|
337
|
-
} catch (err) {
|
|
338
|
-
console.error(
|
|
339
|
-
`weibo[${this.account.accountId}]: failed to send message:`,
|
|
340
|
-
err
|
|
341
|
-
);
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
isConnected(): boolean {
|
|
349
|
-
return this.ws?.readyState === WebSocket.OPEN;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
close(): void {
|
|
353
|
-
this.shouldReconnect = false;
|
|
354
|
-
this.stopHeartbeat();
|
|
355
|
-
|
|
356
|
-
if (this.reconnectTimeout) {
|
|
357
|
-
clearTimeout(this.reconnectTimeout);
|
|
358
|
-
this.reconnectTimeout = null;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
this.ws?.close();
|
|
362
|
-
this.ws = null;
|
|
363
|
-
this.emitStatus({
|
|
364
|
-
running: false,
|
|
365
|
-
connected: false,
|
|
366
|
-
connectionState: "stopped",
|
|
367
|
-
nextRetryAt: null,
|
|
368
|
-
lastStopAt: Date.now(),
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
type ClientCacheEntry = {
|
|
374
|
-
client: WeiboWebSocketClient;
|
|
375
|
-
fingerprint: string;
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
function applyOptions(client: WeiboWebSocketClient, options?: WeiboClientOptions): void {
|
|
379
|
-
if (!options) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (options.onMessage) {
|
|
383
|
-
client.onMessage(options.onMessage);
|
|
384
|
-
}
|
|
385
|
-
if (options.onError) {
|
|
386
|
-
client.onError(options.onError);
|
|
387
|
-
}
|
|
388
|
-
if (options.onClose) {
|
|
389
|
-
client.onClose(options.onClose);
|
|
390
|
-
}
|
|
391
|
-
if (options.onOpen) {
|
|
392
|
-
client.onOpen(options.onOpen);
|
|
393
|
-
}
|
|
394
|
-
if (options.onStatus) {
|
|
395
|
-
client.onStatus(options.onStatus);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Client cache for reusing connections
|
|
400
|
-
const clientCache = new Map<string, ClientCacheEntry>();
|
|
401
|
-
|
|
402
|
-
export function createWeiboClient(
|
|
403
|
-
account: ResolvedWeiboAccount,
|
|
404
|
-
options?: WeiboClientOptions
|
|
405
|
-
): WeiboWebSocketClient {
|
|
406
|
-
const fingerprint = getWeiboConnectionFingerprint(account);
|
|
407
|
-
const cached = clientCache.get(account.accountId);
|
|
408
|
-
if (cached) {
|
|
409
|
-
if (cached.fingerprint === fingerprint) {
|
|
410
|
-
applyOptions(cached.client, options);
|
|
411
|
-
return cached.client;
|
|
412
|
-
}
|
|
413
|
-
cached.client.close();
|
|
414
|
-
clearTokenCache(account.accountId);
|
|
415
|
-
clientCache.delete(account.accountId);
|
|
416
|
-
}
|
|
417
|
-
const client = new WeiboWebSocketClient(account, options);
|
|
418
|
-
clientCache.set(account.accountId, { client, fingerprint });
|
|
419
|
-
return client;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export function clearClientCache(accountId?: string): void {
|
|
423
|
-
if (accountId) {
|
|
424
|
-
const cached = clientCache.get(accountId);
|
|
425
|
-
if (cached) {
|
|
426
|
-
cached.client.close();
|
|
427
|
-
clientCache.delete(accountId);
|
|
428
|
-
}
|
|
429
|
-
} else {
|
|
430
|
-
for (const cached of clientCache.values()) {
|
|
431
|
-
cached.client.close();
|
|
432
|
-
}
|
|
433
|
-
clientCache.clear();
|
|
434
|
-
}
|
|
435
|
-
}
|
package/src/fingerprint.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ResolvedWeiboAccount } from "./types.js";
|
|
2
|
-
|
|
3
|
-
function normalizePart(value: unknown): string {
|
|
4
|
-
return String(value ?? "").trim();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getWeiboConnectionFingerprint(account: ResolvedWeiboAccount): string {
|
|
8
|
-
return JSON.stringify({
|
|
9
|
-
appId: normalizePart(account.appId),
|
|
10
|
-
appSecret: normalizePart(account.appSecret),
|
|
11
|
-
wsEndpoint: normalizePart(account.wsEndpoint),
|
|
12
|
-
tokenEndpoint: normalizePart(account.tokenEndpoint),
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getWeiboTokenFingerprint(
|
|
17
|
-
account: ResolvedWeiboAccount,
|
|
18
|
-
tokenEndpoint?: string,
|
|
19
|
-
): string {
|
|
20
|
-
return JSON.stringify({
|
|
21
|
-
appId: normalizePart(account.appId),
|
|
22
|
-
appSecret: normalizePart(account.appSecret),
|
|
23
|
-
tokenEndpoint: normalizePart(tokenEndpoint ?? account.tokenEndpoint),
|
|
24
|
-
});
|
|
25
|
-
}
|
package/src/monitor.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
-
import type { ResolvedWeiboAccount, WeiboRuntimeStatusPatch } from "./types.js";
|
|
3
|
-
import { resolveWeiboAccount, listEnabledWeiboAccounts } from "./accounts.js";
|
|
4
|
-
import { clearClientCache, createWeiboClient, WeiboWebSocketClient } from "./client.js";
|
|
5
|
-
import { clearTokenCache, formatWeiboTokenFetchErrorMessage } from "./token.js";
|
|
6
|
-
import { handleWeiboMessage, type WeiboMessageEvent } from "./bot.js";
|
|
7
|
-
import { waitUntilAbortCompat } from "./plugin-sdk-compat.js";
|
|
8
|
-
|
|
9
|
-
export type MonitorWeiboOpts = {
|
|
10
|
-
config?: ClawdbotConfig;
|
|
11
|
-
runtime?: RuntimeEnv;
|
|
12
|
-
abortSignal?: AbortSignal;
|
|
13
|
-
accountId?: string;
|
|
14
|
-
statusSink?: (patch: WeiboRuntimeStatusPatch) => void;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Track connections per account
|
|
18
|
-
const wsClients = new Map<string, WeiboWebSocketClient>();
|
|
19
|
-
|
|
20
|
-
async function monitorSingleAccount(params: {
|
|
21
|
-
cfg: ClawdbotConfig;
|
|
22
|
-
account: ResolvedWeiboAccount;
|
|
23
|
-
runtime?: RuntimeEnv;
|
|
24
|
-
abortSignal?: AbortSignal;
|
|
25
|
-
statusSink?: (patch: WeiboRuntimeStatusPatch) => void;
|
|
26
|
-
}): Promise<void> {
|
|
27
|
-
const { cfg, account, runtime, abortSignal, statusSink } = params;
|
|
28
|
-
const { accountId } = account;
|
|
29
|
-
const log = runtime?.log ?? console.log;
|
|
30
|
-
const error = runtime?.error ?? console.error;
|
|
31
|
-
const emitStatus = (patch: WeiboRuntimeStatusPatch) => statusSink?.(patch);
|
|
32
|
-
|
|
33
|
-
log(`weibo[${accountId}]: connecting WebSocket...`);
|
|
34
|
-
emitStatus({
|
|
35
|
-
running: true,
|
|
36
|
-
connected: false,
|
|
37
|
-
connectionState: "connecting",
|
|
38
|
-
lastStartAt: Date.now(),
|
|
39
|
-
lastStopAt: null,
|
|
40
|
-
lastError: null,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const client = createWeiboClient(account);
|
|
44
|
-
wsClients.set(accountId, client);
|
|
45
|
-
client.onStatus(emitStatus);
|
|
46
|
-
|
|
47
|
-
client.onMessage((data) => {
|
|
48
|
-
try {
|
|
49
|
-
const msg = data as { type?: string; payload?: unknown };
|
|
50
|
-
if (msg.type === "message" && msg.payload) {
|
|
51
|
-
emitStatus({ lastInboundAt: Date.now() });
|
|
52
|
-
const event = msg as WeiboMessageEvent;
|
|
53
|
-
handleWeiboMessage({ cfg, event, accountId, runtime }).catch((err) => {
|
|
54
|
-
error(`weibo[${accountId}]: error handling message: ${String(err)}`);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
} catch (err) {
|
|
58
|
-
error(`weibo[${accountId}]: error processing message: ${String(err)}`);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
client.onError((err) => {
|
|
63
|
-
emitStatus({
|
|
64
|
-
running: true,
|
|
65
|
-
connected: false,
|
|
66
|
-
connectionState: "error",
|
|
67
|
-
lastError: err.message,
|
|
68
|
-
});
|
|
69
|
-
error(`weibo[${accountId}]: WebSocket error: ${err.message}`);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
client.onOpen(() => {
|
|
73
|
-
emitStatus({
|
|
74
|
-
running: true,
|
|
75
|
-
connected: true,
|
|
76
|
-
connectionState: "connected",
|
|
77
|
-
reconnectAttempts: 0,
|
|
78
|
-
nextRetryAt: null,
|
|
79
|
-
lastConnectedAt: Date.now(),
|
|
80
|
-
lastError: null,
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
client.onClose((code, reason) => {
|
|
85
|
-
emitStatus({
|
|
86
|
-
running: !abortSignal?.aborted,
|
|
87
|
-
connected: false,
|
|
88
|
-
connectionState: abortSignal?.aborted ? "stopped" : "error",
|
|
89
|
-
lastDisconnect: {
|
|
90
|
-
code,
|
|
91
|
-
reason,
|
|
92
|
-
at: Date.now(),
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
log(`weibo[${accountId}]: WebSocket closed (code: ${code}, reason: ${reason})`);
|
|
96
|
-
wsClients.delete(accountId);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Handle abort signal
|
|
100
|
-
const handleAbort = () => {
|
|
101
|
-
log(`weibo[${accountId}]: abort signal received, closing connection`);
|
|
102
|
-
client.close();
|
|
103
|
-
wsClients.delete(accountId);
|
|
104
|
-
emitStatus({
|
|
105
|
-
running: false,
|
|
106
|
-
connected: false,
|
|
107
|
-
connectionState: "stopped",
|
|
108
|
-
nextRetryAt: null,
|
|
109
|
-
lastStopAt: Date.now(),
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
if (abortSignal?.aborted) {
|
|
114
|
-
handleAbort();
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
await client.connect();
|
|
122
|
-
log(`weibo[${accountId}]: WebSocket connected`);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
const message = formatWeiboTokenFetchErrorMessage(err) ?? String(err);
|
|
125
|
-
emitStatus({
|
|
126
|
-
running: true,
|
|
127
|
-
connected: false,
|
|
128
|
-
connectionState: "error",
|
|
129
|
-
lastError: message,
|
|
130
|
-
});
|
|
131
|
-
error(`weibo[${accountId}]: failed to connect: ${message}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Keep the channel task alive until the gateway aborts it. Returning early here
|
|
135
|
-
// causes the gateway supervisor to interpret startup as an exit and trigger
|
|
136
|
-
// provider auto-restart loops.
|
|
137
|
-
await waitUntilAbortCompat(abortSignal);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export async function monitorWeiboProvider(opts: MonitorWeiboOpts = {}): Promise<void> {
|
|
141
|
-
const cfg = opts.config;
|
|
142
|
-
if (!cfg) {
|
|
143
|
-
throw new Error("Config is required for Weibo monitor");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const log = opts.runtime?.log ?? console.log;
|
|
147
|
-
|
|
148
|
-
// If accountId is specified, only monitor that account
|
|
149
|
-
if (opts.accountId) {
|
|
150
|
-
const account = resolveWeiboAccount({ cfg, accountId: opts.accountId });
|
|
151
|
-
if (!account.enabled || !account.configured) {
|
|
152
|
-
throw new Error(`Weibo account "${opts.accountId}" not configured or disabled`);
|
|
153
|
-
}
|
|
154
|
-
return monitorSingleAccount({
|
|
155
|
-
cfg,
|
|
156
|
-
account,
|
|
157
|
-
runtime: opts.runtime,
|
|
158
|
-
abortSignal: opts.abortSignal,
|
|
159
|
-
statusSink: opts.statusSink,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Otherwise, start all enabled accounts
|
|
164
|
-
const accounts = listEnabledWeiboAccounts(cfg);
|
|
165
|
-
if (accounts.length === 0) {
|
|
166
|
-
throw new Error("No enabled Weibo accounts configured");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
log(`weibo: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`);
|
|
170
|
-
|
|
171
|
-
// Start all accounts in parallel
|
|
172
|
-
await Promise.all(
|
|
173
|
-
accounts.map((account) =>
|
|
174
|
-
monitorSingleAccount({
|
|
175
|
-
cfg,
|
|
176
|
-
account,
|
|
177
|
-
runtime: opts.runtime,
|
|
178
|
-
abortSignal: opts.abortSignal,
|
|
179
|
-
statusSink: opts.statusSink,
|
|
180
|
-
}),
|
|
181
|
-
),
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function stopWeiboMonitor(accountId?: string): void {
|
|
186
|
-
if (accountId) {
|
|
187
|
-
const client = wsClients.get(accountId);
|
|
188
|
-
if (client) {
|
|
189
|
-
client.close();
|
|
190
|
-
wsClients.delete(accountId);
|
|
191
|
-
}
|
|
192
|
-
clearClientCache(accountId);
|
|
193
|
-
clearTokenCache(accountId);
|
|
194
|
-
} else {
|
|
195
|
-
for (const client of wsClients.values()) {
|
|
196
|
-
client.close();
|
|
197
|
-
}
|
|
198
|
-
wsClients.clear();
|
|
199
|
-
clearClientCache();
|
|
200
|
-
clearTokenCache();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export async function reconnectWeiboMonitor(accountId?: string): Promise<void> {
|
|
205
|
-
stopWeiboMonitor(accountId);
|
|
206
|
-
}
|