claw_messenger 1.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/LICENSE +21 -0
- package/README.md +577 -0
- package/bin/auto-init.js +104 -0
- package/bin/cli.js +5 -0
- package/bin/diagnose-plugin.js +174 -0
- package/bin/dm-bridge.cjs +12 -0
- package/bin/install.js +452 -0
- package/bin/postinstall.js +23 -0
- package/bin/qr-crypto-node.js +186 -0
- package/bin/setup.js +262 -0
- package/dist/auto-register.d.ts +49 -0
- package/dist/auto-register.js +328 -0
- package/dist/bridge-runner.d.ts +1 -0
- package/dist/bridge-runner.js +107 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +164 -0
- package/dist/device-status.d.ts +30 -0
- package/dist/device-status.js +109 -0
- package/dist/env-polyfill.d.ts +3 -0
- package/dist/env-polyfill.js +166 -0
- package/dist/group-config-manager.d.ts +22 -0
- package/dist/group-config-manager.js +130 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +36 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +103 -0
- package/dist/mac-address.d.ts +1 -0
- package/dist/mac-address.js +46 -0
- package/dist/openclaw-client.d.ts +41 -0
- package/dist/openclaw-client.js +530 -0
- package/dist/openclaw-config.d.ts +41 -0
- package/dist/openclaw-config.js +359 -0
- package/dist/openclaw.plugin.json +40 -0
- package/dist/package.json +112 -0
- package/dist/plugin-entry.d.ts +54 -0
- package/dist/plugin-entry.js +772 -0
- package/dist/postinstall.js +23 -0
- package/dist/rongcloud-client.d.ts +16 -0
- package/dist/rongcloud-client.js +274 -0
- package/dist/rongcloud-server-api.d.ts +53 -0
- package/dist/rongcloud-server-api.js +221 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +97 -0
- package/openclaw.plugin.json +40 -0
- package/package.json +112 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from 'fs'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { dirname, join } from 'path'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
// 自动识别当前在 bin/ 还是 dist/ 目录
|
|
9
|
+
const distDir = existsSync(join(__dirname, 'openclaw-config.js'))
|
|
10
|
+
? __dirname
|
|
11
|
+
: join(__dirname, '..', 'dist')
|
|
12
|
+
|
|
13
|
+
const distFile = join(distDir, 'openclaw-config.js')
|
|
14
|
+
|
|
15
|
+
// 开发模式(dist 不存在)跳过
|
|
16
|
+
if (!existsSync(distFile)) {
|
|
17
|
+
console.log('[clawmessenger] 开发模式,跳过 postinstall')
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('[clawmessenger] 插件已安装')
|
|
22
|
+
console.log('[clawmessenger] 核心功能: 融云 IM 桥接、OpenClaw 对话、流式消息')
|
|
23
|
+
console.log('[clawmessenger] 运行 npx claw_messenger 完成节点注册和配置')
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface RongCloudConfig {
|
|
2
|
+
appKey: string;
|
|
3
|
+
token: string;
|
|
4
|
+
onMessage: (msg: any) => void;
|
|
5
|
+
onConnectSuccess?: (userId: string) => void;
|
|
6
|
+
onError?: (code: number) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare class RongCloudClient {
|
|
9
|
+
private config;
|
|
10
|
+
private isConnected;
|
|
11
|
+
private messageHandler;
|
|
12
|
+
constructor(config: RongCloudConfig);
|
|
13
|
+
setOnMessageHandler(handler: (msg: any) => void): void;
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
sendMessage(targetId: string, content: string, conversationType?: number): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
require("./env-polyfill.js");
|
|
3
|
+
|
|
4
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
14
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
15
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
16
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
17
|
+
function step(op) {
|
|
18
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
19
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
20
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
21
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
22
|
+
switch (op[0]) {
|
|
23
|
+
case 0: case 1: t = op; break;
|
|
24
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
25
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
26
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
27
|
+
default:
|
|
28
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
29
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
30
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
31
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
32
|
+
if (t[2]) _.ops.pop();
|
|
33
|
+
_.trys.pop(); continue;
|
|
34
|
+
}
|
|
35
|
+
op = body.call(thisArg, _);
|
|
36
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
37
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.RongCloudClient = void 0;
|
|
42
|
+
var RongIMLibModule = require("@rongcloud/imlib-next");
|
|
43
|
+
var RongIMLib = RongIMLibModule.default || RongIMLibModule;
|
|
44
|
+
// SDK loaded
|
|
45
|
+
var ConversationType = {
|
|
46
|
+
PRIVATE: 1,
|
|
47
|
+
GROUP: 3
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
var RongCloudClient = /** @class */ (function () {
|
|
51
|
+
function RongCloudClient(config) {
|
|
52
|
+
this.isConnected = false;
|
|
53
|
+
this.messageHandler = null;
|
|
54
|
+
this.config = config;
|
|
55
|
+
this._listenersRegistered = false;
|
|
56
|
+
this.processingQueue = Promise.resolve();
|
|
57
|
+
this.processedMessageUIds = new Set();
|
|
58
|
+
this.messageDedupMaxSize = 1000;
|
|
59
|
+
// 发送侧短期缓存:防止融云 SDK 回传自己发送的消息导致机器人自言自语
|
|
60
|
+
this.sentMessageUIds = new Set();
|
|
61
|
+
this.sentMessageDedupMaxSize = 100;
|
|
62
|
+
}
|
|
63
|
+
RongCloudClient.prototype.setOnMessageHandler = function (handler) {
|
|
64
|
+
this.messageHandler = handler;
|
|
65
|
+
};
|
|
66
|
+
RongCloudClient.prototype.connect = function () {
|
|
67
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
68
|
+
var _this = this;
|
|
69
|
+
return __generator(this, function (_a) {
|
|
70
|
+
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
|
71
|
+
var result, userId, error_1;
|
|
72
|
+
var _this = this;
|
|
73
|
+
var _a, _b, _c, _d, _e;
|
|
74
|
+
return __generator(this, function (_f) {
|
|
75
|
+
switch (_f.label) {
|
|
76
|
+
case 0:
|
|
77
|
+
_f.trys.push([0, 2, , 3]);
|
|
78
|
+
if (!RongIMLib || typeof RongIMLib.init !== 'function') {
|
|
79
|
+
throw new Error(' SDK 未正确加载');
|
|
80
|
+
}
|
|
81
|
+
if (!this._listenersRegistered) {
|
|
82
|
+
RongIMLib.init({ appkey: this.config.appKey });
|
|
83
|
+
RongIMLib.addEventListener(RongIMLib.Events.MESSAGES, function (event) {
|
|
84
|
+
var _a;
|
|
85
|
+
(_a = event.messages) === null || _a === void 0 ? void 0 : _a.forEach(function (msg) {
|
|
86
|
+
_this.config.onMessage(msg);
|
|
87
|
+
if (_this.messageHandler) {
|
|
88
|
+
_this.messageHandler(msg);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
RongIMLib.addEventListener(RongIMLib.Events.CONNECTED, function () {
|
|
93
|
+
_this.isConnected = true;
|
|
94
|
+
});
|
|
95
|
+
RongIMLib.addEventListener(RongIMLib.Events.DISCONNECT, function (code) {
|
|
96
|
+
_this.isConnected = false;
|
|
97
|
+
});
|
|
98
|
+
this._listenersRegistered = true;
|
|
99
|
+
}
|
|
100
|
+
return [4 /*yield*/, RongIMLib.connect(this.config.token)];
|
|
101
|
+
case 1:
|
|
102
|
+
result = _f.sent();
|
|
103
|
+
if (result.code === 0 || result.code === 200) {
|
|
104
|
+
userId = ((_a = result.data) === null || _a === void 0 ? void 0 : _a.userId) || 'unknown';
|
|
105
|
+
this.isConnected = true;
|
|
106
|
+
(_c = (_b = this.config).onConnectSuccess) === null || _c === void 0 ? void 0 : _c.call(_b, userId);
|
|
107
|
+
resolve();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
(_e = (_d = this.config).onError) === null || _e === void 0 ? void 0 : _e.call(_d, result.code);
|
|
111
|
+
reject(new Error("Connect failed: ".concat(result.code)));
|
|
112
|
+
}
|
|
113
|
+
return [3 /*break*/, 3];
|
|
114
|
+
case 2:
|
|
115
|
+
error_1 = _f.sent();
|
|
116
|
+
reject(error_1);
|
|
117
|
+
return [3 /*break*/, 3];
|
|
118
|
+
case 3: return [2 /*return*/];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}); })];
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
RongCloudClient.prototype.sendMessage = function (targetId_1, content_1) {
|
|
126
|
+
return __awaiter(this, arguments, void 0, function (targetId, content, conversationType) {
|
|
127
|
+
var convType, result, sentUId;
|
|
128
|
+
var _a, _b;
|
|
129
|
+
if (conversationType === void 0) { conversationType = 1; }
|
|
130
|
+
return __generator(this, function (_c) {
|
|
131
|
+
switch (_c.label) {
|
|
132
|
+
case 0:
|
|
133
|
+
if (!this.isConnected) {
|
|
134
|
+
this.log?.error('[RongCloudClient] 未连接,无法发送消息');
|
|
135
|
+
return [2 /*return*/, false];
|
|
136
|
+
}
|
|
137
|
+
convType = conversationType === 3
|
|
138
|
+
? ((_a = RongIMLib.ConversationType) === null || _a === void 0 ? void 0 : _a.GROUP) || 3
|
|
139
|
+
: ((_b = RongIMLib.ConversationType) === null || _b === void 0 ? void 0 : _b.PRIVATE) || 1;
|
|
140
|
+
return [4 /*yield*/, RongIMLib.sendTextMessage({ conversationType: convType, targetId: targetId }, { content: content })];
|
|
141
|
+
case 1:
|
|
142
|
+
result = _c.sent();
|
|
143
|
+
if (result.code === 0 || result.code === 200) {
|
|
144
|
+
sentUId = result.data?.messageUId;
|
|
145
|
+
// 将发送成功的 messageUId 加入短期缓存,用于过滤 SDK 回传的自己消息
|
|
146
|
+
if (sentUId) {
|
|
147
|
+
this.sentMessageUIds.add(sentUId);
|
|
148
|
+
if (this.sentMessageUIds.size > this.sentMessageDedupMaxSize) {
|
|
149
|
+
var first = this.sentMessageUIds.values().next().value;
|
|
150
|
+
this.sentMessageUIds.delete(first);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [2 /*return*/, true];
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
this.log?.error("[RongCloudClient] \u53d1\u9001\u5931\u8d25, code: ".concat(result.code));
|
|
157
|
+
return [2 /*return*/, false];
|
|
158
|
+
}
|
|
159
|
+
return [2 /*return*/];
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
RongCloudClient.prototype.sendReadReceipt = function (msg) {
|
|
166
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
167
|
+
var conversationType, senderUserId, targetId, messageUId, sentTime, result, result, result, error_2;
|
|
168
|
+
return __generator(this, function (_a) {
|
|
169
|
+
switch (_a.label) {
|
|
170
|
+
case 0:
|
|
171
|
+
if (!this.isConnected) {
|
|
172
|
+
this.log?.warn('[RongCloudClient] 未连接,无法发送已读回执');
|
|
173
|
+
return [2 /*return*/, false];
|
|
174
|
+
}
|
|
175
|
+
conversationType = msg.conversationType;
|
|
176
|
+
senderUserId = msg.senderUserId;
|
|
177
|
+
targetId = msg.targetId;
|
|
178
|
+
messageUId = msg.messageUId;
|
|
179
|
+
sentTime = msg.sentTime;
|
|
180
|
+
if (!messageUId || !sentTime) {
|
|
181
|
+
this.log?.warn('[RongCloudClient] 消息缺少 messageUId 或 sentTime,无法发送已读回执');
|
|
182
|
+
return [2 /*return*/, false];
|
|
183
|
+
}
|
|
184
|
+
if (String(messageUId).startsWith('local-')) {
|
|
185
|
+
this.log?.warn('[RongCloudClient] messageUId 为本地生成,跳过已读回执');
|
|
186
|
+
return [2 /*return*/, false];
|
|
187
|
+
}
|
|
188
|
+
if (msg.messageDirection === 1) {
|
|
189
|
+
this.log?.info('[RongCloudClient] 自己发送的消息,跳过已读回执');
|
|
190
|
+
return [2 /*return*/, false];
|
|
191
|
+
}
|
|
192
|
+
if (senderUserId === this.config.accountId) {
|
|
193
|
+
this.log?.info('[RongCloudClient] 发送者为本账号,跳过已读回执');
|
|
194
|
+
return [2 /*return*/, false];
|
|
195
|
+
}
|
|
196
|
+
if (messageUId && this.sentMessageUIds.has(messageUId)) {
|
|
197
|
+
this.log?.info("[RongCloudClient] \u81ea\u5df1\u53d1\u9001\u7684\u6d88\u606f(messageUId\u5728\u7f13\u5b58\u4e2d)\uff0c\u8df3\u8fc7\u5df2\u8bfb\u56de\u6267");
|
|
198
|
+
return [2 /*return*/, false];
|
|
199
|
+
}
|
|
200
|
+
this.log?.info("[RongCloudClient] \u51c6\u5907\u53d1\u9001\u5df2\u8bfb\u56de\u6267: conversationType=".concat(conversationType, ", targetId=").concat(targetId, ", messageUId=").concat(messageUId));
|
|
201
|
+
_a.label = 1;
|
|
202
|
+
case 1:
|
|
203
|
+
_a.trys.push([1, 8, , 9]);
|
|
204
|
+
if (!(typeof RongIMLib.sendReadReceiptResponseV5 === 'function')) return [3 /*break*/, 3];
|
|
205
|
+
return [4 /*yield*/, RongIMLib.sendReadReceiptResponseV5(
|
|
206
|
+
{ conversationType: conversationType, targetId: targetId },
|
|
207
|
+
[messageUId]
|
|
208
|
+
)];
|
|
209
|
+
case 2:
|
|
210
|
+
result = _a.sent();
|
|
211
|
+
this.log?.info("[RongCloudClient] V5 \u5df2\u8bfb\u56de\u6267\u7ed3\u679c: code=".concat(result.code));
|
|
212
|
+
return [2 /*return*/, result.code === 0 || result.code === 200];
|
|
213
|
+
case 3:
|
|
214
|
+
if (!(conversationType === ConversationType.GROUP)) return [3 /*break*/, 5];
|
|
215
|
+
if (typeof RongIMLib.sendReadReceiptResponseV2 !== 'function') {
|
|
216
|
+
this.log?.warn('[RongCloudClient] SDK 不支持群聊已读回执');
|
|
217
|
+
return [2 /*return*/, false];
|
|
218
|
+
}
|
|
219
|
+
return [4 /*yield*/, RongIMLib.sendReadReceiptResponseV2(targetId, {
|
|
220
|
+
[senderUserId]: [messageUId]
|
|
221
|
+
})];
|
|
222
|
+
case 4:
|
|
223
|
+
result = _a.sent();
|
|
224
|
+
this.log?.info("[RongCloudClient] \u7fa4\u804a\u5df2\u8bfb\u56de\u6267\u7ed3\u679c: code=".concat(result.code));
|
|
225
|
+
return [2 /*return*/, result.code === 0 || result.code === 200];
|
|
226
|
+
case 5:
|
|
227
|
+
if (typeof RongIMLib.sendReadReceiptMessage !== 'function') {
|
|
228
|
+
this.log?.warn('[RongCloudClient] SDK 不支持单聊已读回执');
|
|
229
|
+
return [2 /*return*/, false];
|
|
230
|
+
}
|
|
231
|
+
return [4 /*yield*/, RongIMLib.sendReadReceiptMessage(senderUserId, messageUId, sentTime)];
|
|
232
|
+
case 6:
|
|
233
|
+
result = _a.sent();
|
|
234
|
+
this.log?.info("[RongCloudClient] \u5355\u804a\u5df2\u8bfb\u56de\u6267\u7ed3\u679c: code=".concat(result.code));
|
|
235
|
+
return [2 /*return*/, result.code === 0 || result.code === 200];
|
|
236
|
+
case 7: return [3 /*break*/, 9];
|
|
237
|
+
case 8:
|
|
238
|
+
error_2 = _a.sent();
|
|
239
|
+
this.log?.error("[RongCloudClient] \u53d1\u9001\u5df2\u8bfb\u56de\u6267\u5f02\u5e38: ".concat(error_2.message));
|
|
240
|
+
return [2 /*return*/, false];
|
|
241
|
+
case 9: return [2 /*return*/];
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
RongCloudClient.prototype.disconnect = function () {
|
|
247
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
248
|
+
var err_3;
|
|
249
|
+
return __generator(this, function (_a) {
|
|
250
|
+
switch (_a.label) {
|
|
251
|
+
case 0:
|
|
252
|
+
this.log?.info('[RongCloudClient] 断开连接...');
|
|
253
|
+
this.isConnected = false;
|
|
254
|
+
_a.label = 1;
|
|
255
|
+
case 1:
|
|
256
|
+
_a.trys.push([1, 3, , 4]);
|
|
257
|
+
return [4 /*yield*/, RongIMLib.disconnect()];
|
|
258
|
+
case 2:
|
|
259
|
+
_a.sent();
|
|
260
|
+
this.log?.info('[RongCloudClient] 已断开');
|
|
261
|
+
return [3 /*break*/, 4];
|
|
262
|
+
case 3:
|
|
263
|
+
err_3 = _a.sent();
|
|
264
|
+
this.log?.error("[RongCloudClient] 断开异常: ".concat(err_3.message));
|
|
265
|
+
return [3 /*break*/, 4];
|
|
266
|
+
case 4: return [2 /*return*/];
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
return RongCloudClient;
|
|
272
|
+
}());
|
|
273
|
+
exports.RongCloudClient = RongCloudClient;
|
|
274
|
+
exports.ConversationType = ConversationType;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
info: (msg: string, ...args: any[]) => void;
|
|
3
|
+
warn: (msg: string, ...args: any[]) => void;
|
|
4
|
+
error: (msg: string, ...args: any[]) => void;
|
|
5
|
+
}
|
|
6
|
+
export interface SendStreamOptions {
|
|
7
|
+
fromUserId: string;
|
|
8
|
+
toUserId?: string;
|
|
9
|
+
toGroupId?: string;
|
|
10
|
+
content: string;
|
|
11
|
+
streamId: string;
|
|
12
|
+
isFirstChunk?: boolean;
|
|
13
|
+
isLastChunk?: boolean;
|
|
14
|
+
seq?: number;
|
|
15
|
+
streamType?: string;
|
|
16
|
+
messageUID?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export declare class RongCloudServerAPI {
|
|
19
|
+
private appKey;
|
|
20
|
+
private appSecret;
|
|
21
|
+
private log;
|
|
22
|
+
private hosts;
|
|
23
|
+
private currentHostIndex;
|
|
24
|
+
private timeout;
|
|
25
|
+
constructor(appKey: string, appSecret: string, log?: Logger);
|
|
26
|
+
private get currentHost();
|
|
27
|
+
private _switchHost;
|
|
28
|
+
private _generateNonce;
|
|
29
|
+
private _generateSignature;
|
|
30
|
+
private _getHeaders;
|
|
31
|
+
private _getFormHeaders;
|
|
32
|
+
private request;
|
|
33
|
+
private requestForm;
|
|
34
|
+
sendStreamPrivate({ fromUserId, toUserId, content, streamId, isFirstChunk, isLastChunk, seq, streamType, messageUID, }: SendStreamOptions): Promise<any>;
|
|
35
|
+
sendStreamGroup({ fromUserId, toGroupId, content, streamId, isFirstChunk, isLastChunk, seq, streamType, messageUID, }: SendStreamOptions): Promise<any>;
|
|
36
|
+
sendTypingStatus({ fromUserId, toUserId, conversationType, }: {
|
|
37
|
+
fromUserId: string;
|
|
38
|
+
toUserId: string;
|
|
39
|
+
conversationType?: number;
|
|
40
|
+
}): Promise<any>;
|
|
41
|
+
sendPrivateMessage({ fromUserId, toUserId, objectName, content, }: {
|
|
42
|
+
fromUserId: string;
|
|
43
|
+
toUserId: string;
|
|
44
|
+
objectName: string;
|
|
45
|
+
content: Record<string, any>;
|
|
46
|
+
}): Promise<any>;
|
|
47
|
+
sendGroupMessage({ fromUserId, toGroupId, objectName, content, }: {
|
|
48
|
+
fromUserId: string;
|
|
49
|
+
toGroupId: string;
|
|
50
|
+
objectName: string;
|
|
51
|
+
content: Record<string, any>;
|
|
52
|
+
}): Promise<any>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
const API_HOSTS_CN = ['api.rong-api.com', 'api-b.rong-api.com'];
|
|
4
|
+
export class RongCloudServerAPI {
|
|
5
|
+
constructor(appKey, appSecret, log) {
|
|
6
|
+
this.hosts = API_HOSTS_CN;
|
|
7
|
+
this.currentHostIndex = 0;
|
|
8
|
+
this.timeout = 10000;
|
|
9
|
+
this.appKey = appKey;
|
|
10
|
+
this.appSecret = appSecret;
|
|
11
|
+
this.log = log;
|
|
12
|
+
}
|
|
13
|
+
get currentHost() {
|
|
14
|
+
return this.hosts[this.currentHostIndex];
|
|
15
|
+
}
|
|
16
|
+
_switchHost() {
|
|
17
|
+
var _a;
|
|
18
|
+
if (this.currentHostIndex < this.hosts.length - 1) {
|
|
19
|
+
this.currentHostIndex++;
|
|
20
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 切换到备用域名: ${this.currentHost}`);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
_generateNonce(length = 18) {
|
|
26
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
27
|
+
let result = '';
|
|
28
|
+
for (let i = 0; i < length; i++) {
|
|
29
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
_generateSignature() {
|
|
34
|
+
const nonce = this._generateNonce();
|
|
35
|
+
const timestamp = Date.now();
|
|
36
|
+
const source = this.appSecret + nonce + timestamp;
|
|
37
|
+
const signature = crypto.createHash('sha1').update(source).digest('hex');
|
|
38
|
+
return { nonce, timestamp, signature };
|
|
39
|
+
}
|
|
40
|
+
_getHeaders() {
|
|
41
|
+
const sign = this._generateSignature();
|
|
42
|
+
return {
|
|
43
|
+
'App-Key': this.appKey,
|
|
44
|
+
'Nonce': sign.nonce,
|
|
45
|
+
'Timestamp': String(sign.timestamp),
|
|
46
|
+
'Signature': sign.signature,
|
|
47
|
+
'Content-Type': 'application/json; charset=UTF-8',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
_getFormHeaders() {
|
|
51
|
+
const sign = this._generateSignature();
|
|
52
|
+
return {
|
|
53
|
+
'App-Key': this.appKey,
|
|
54
|
+
'Nonce': sign.nonce,
|
|
55
|
+
'Timestamp': String(sign.timestamp),
|
|
56
|
+
'Signature': sign.signature,
|
|
57
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async request(path, data, retry = true) {
|
|
61
|
+
var _a, _b, _c, _d, _e;
|
|
62
|
+
const url = `https://${this.currentHost}${path}`;
|
|
63
|
+
const headers = this._getHeaders();
|
|
64
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 请求: POST ${url}`);
|
|
65
|
+
try {
|
|
66
|
+
const response = await axios.post(url, data, {
|
|
67
|
+
headers,
|
|
68
|
+
timeout: this.timeout,
|
|
69
|
+
responseType: 'json',
|
|
70
|
+
});
|
|
71
|
+
const result = response.data;
|
|
72
|
+
if (result.code && result.code !== 200) {
|
|
73
|
+
throw new Error(`[${result.code}] ${result.errorMessage || 'Unknown error'}`);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
|
|
79
|
+
(_c = this.log) === null || _c === void 0 ? void 0 : _c.error('[RongCloudServerAPI] 签名验证失败,请检查 App Key 和 App Secret');
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
if (retry && this._switchHost()) {
|
|
83
|
+
(_d = this.log) === null || _d === void 0 ? void 0 : _d.warn(`[RongCloudServerAPI] 请求失败,使用备用域名重试: ${err.message}`);
|
|
84
|
+
return this.request(path, data, false);
|
|
85
|
+
}
|
|
86
|
+
(_e = this.log) === null || _e === void 0 ? void 0 : _e.error(`[RongCloudServerAPI] 请求失败: ${err.message}`);
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async requestForm(path, data, retry = true) {
|
|
91
|
+
var _a, _b, _c, _d, _e;
|
|
92
|
+
const url = `https://${this.currentHost}${path}`;
|
|
93
|
+
const headers = this._getFormHeaders();
|
|
94
|
+
const params = new globalThis.URLSearchParams();
|
|
95
|
+
for (const [key, value] of Object.entries(data)) {
|
|
96
|
+
if (value !== undefined && value !== null) {
|
|
97
|
+
params.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 请求: POST ${url} (Form)`);
|
|
101
|
+
try {
|
|
102
|
+
const response = await axios.post(url, params.toString(), {
|
|
103
|
+
headers,
|
|
104
|
+
timeout: this.timeout,
|
|
105
|
+
responseType: 'json',
|
|
106
|
+
});
|
|
107
|
+
const result = response.data;
|
|
108
|
+
if (result.code && result.code !== 200) {
|
|
109
|
+
throw new Error(`[${result.code}] ${result.errorMessage || 'Unknown error'}`);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 401) {
|
|
115
|
+
(_c = this.log) === null || _c === void 0 ? void 0 : _c.error('[RongCloudServerAPI] 签名验证失败,请检查 App Key 和 App Secret');
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
if (retry && this._switchHost()) {
|
|
119
|
+
(_d = this.log) === null || _d === void 0 ? void 0 : _d.warn(`[RongCloudServerAPI] 请求失败,使用备用域名重试: ${err.message}`);
|
|
120
|
+
return this.requestForm(path, data, false);
|
|
121
|
+
}
|
|
122
|
+
(_e = this.log) === null || _e === void 0 ? void 0 : _e.error(`[RongCloudServerAPI] 请求失败: ${err.message}`);
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async sendStreamPrivate({ fromUserId, toUserId, content, streamId, isFirstChunk = false, isLastChunk = false, seq = 1, streamType = 'markdown', messageUID = null, }) {
|
|
127
|
+
var _a;
|
|
128
|
+
const contentBody = {
|
|
129
|
+
content,
|
|
130
|
+
complete: isLastChunk,
|
|
131
|
+
seq,
|
|
132
|
+
};
|
|
133
|
+
if (isFirstChunk) {
|
|
134
|
+
contentBody.type = streamType;
|
|
135
|
+
}
|
|
136
|
+
if (!isFirstChunk && messageUID) {
|
|
137
|
+
contentBody.messageUID = messageUID;
|
|
138
|
+
}
|
|
139
|
+
const data = {
|
|
140
|
+
fromUserId,
|
|
141
|
+
toUserId,
|
|
142
|
+
objectName: 'RC:StreamMsg',
|
|
143
|
+
content: contentBody,
|
|
144
|
+
isPersisted: 1,
|
|
145
|
+
isCounted: isFirstChunk ? 1 : 0,
|
|
146
|
+
disableUpdateLastMsg: !isLastChunk,
|
|
147
|
+
};
|
|
148
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 发送单聊流式消息: to=${toUserId}, streamId=${streamId}, seq=${seq}`);
|
|
149
|
+
return this.request('/v3/message/private/publish_stream.json', data);
|
|
150
|
+
}
|
|
151
|
+
async sendStreamGroup({ fromUserId, toGroupId, content, streamId, isFirstChunk = false, isLastChunk = false, seq = 1, streamType = 'markdown', messageUID = null, }) {
|
|
152
|
+
var _a;
|
|
153
|
+
const contentBody = {
|
|
154
|
+
content,
|
|
155
|
+
complete: isLastChunk,
|
|
156
|
+
seq,
|
|
157
|
+
};
|
|
158
|
+
if (isFirstChunk) {
|
|
159
|
+
contentBody.type = streamType;
|
|
160
|
+
}
|
|
161
|
+
if (!isFirstChunk && messageUID) {
|
|
162
|
+
contentBody.messageUID = messageUID;
|
|
163
|
+
}
|
|
164
|
+
const data = {
|
|
165
|
+
fromUserId,
|
|
166
|
+
toGroupId,
|
|
167
|
+
objectName: 'RC:StreamMsg',
|
|
168
|
+
content: contentBody,
|
|
169
|
+
isPersisted: 1,
|
|
170
|
+
isCounted: isFirstChunk ? 1 : 0,
|
|
171
|
+
isIncludeSender: 1,
|
|
172
|
+
disableUpdateLastMsg: !isLastChunk,
|
|
173
|
+
};
|
|
174
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 发送群聊流式消息: to=${toGroupId}, streamId=${streamId}, seq=${seq}`);
|
|
175
|
+
return this.request('/v3/message/group/publish_stream.json', data);
|
|
176
|
+
}
|
|
177
|
+
async sendTypingStatus({ fromUserId, toUserId, conversationType = 1, }) {
|
|
178
|
+
var _a;
|
|
179
|
+
const content = JSON.stringify({ typingContentType: 'RC:TxtMsg' });
|
|
180
|
+
const data = {
|
|
181
|
+
fromUserId,
|
|
182
|
+
toUserId,
|
|
183
|
+
objectName: 'RC:TypSts',
|
|
184
|
+
content,
|
|
185
|
+
isPersisted: 0,
|
|
186
|
+
isCounted: 0,
|
|
187
|
+
};
|
|
188
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 发送 typing 状态: ${fromUserId} -> ${toUserId}`);
|
|
189
|
+
if (conversationType === 3) {
|
|
190
|
+
return this.requestForm('/message/group/publish.json', data);
|
|
191
|
+
}
|
|
192
|
+
return this.requestForm('/message/private/publish.json', data);
|
|
193
|
+
}
|
|
194
|
+
async sendPrivateMessage({ fromUserId, toUserId, objectName, content, }) {
|
|
195
|
+
var _a;
|
|
196
|
+
const data = {
|
|
197
|
+
fromUserId,
|
|
198
|
+
toUserId,
|
|
199
|
+
objectName,
|
|
200
|
+
content: JSON.stringify(content),
|
|
201
|
+
isPersisted: 1,
|
|
202
|
+
isCounted: 1,
|
|
203
|
+
};
|
|
204
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 发送单聊媒体消息: ${objectName} -> ${toUserId}`);
|
|
205
|
+
return this.requestForm('/message/private/publish.json', data);
|
|
206
|
+
}
|
|
207
|
+
async sendGroupMessage({ fromUserId, toGroupId, objectName, content, }) {
|
|
208
|
+
var _a;
|
|
209
|
+
const data = {
|
|
210
|
+
fromUserId,
|
|
211
|
+
toGroupId,
|
|
212
|
+
objectName,
|
|
213
|
+
content: JSON.stringify(content),
|
|
214
|
+
isPersisted: 1,
|
|
215
|
+
isCounted: 1,
|
|
216
|
+
isIncludeSender: 0,
|
|
217
|
+
};
|
|
218
|
+
(_a = this.log) === null || _a === void 0 ? void 0 : _a.info(`[RongCloudServerAPI] 发送群聊媒体消息: ${objectName} -> ${toGroupId}`);
|
|
219
|
+
return this.requestForm('/message/group/publish.json', data);
|
|
220
|
+
}
|
|
221
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 清洗 OpenClaw CLI 输出:去除 ANSI 转义码、过滤配置/插件日志噪音
|
|
3
|
+
*/
|
|
4
|
+
export declare function cleanOpenClawOutput(output: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* 使用 spawn + 参数数组调用 openclaw CLI,避免 shell 注入风险
|
|
7
|
+
* @returns 清洗后的输出字符串;出错时返回可读错误信息,不会抛异常
|
|
8
|
+
*/
|
|
9
|
+
export declare function spawnOpenClaw(args: string[], timeoutMs?: number): Promise<string>;
|