claw-subagent-service 0.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/README.md +44 -0
- package/cli.js +254 -0
- package/command/linux/restart.sh +98 -0
- package/command/linux/start.sh +101 -0
- package/command/linux/status.sh +140 -0
- package/command/linux/stop.sh +112 -0
- package/command/win/restart.bat +39 -0
- package/command/win/start.bat +65 -0
- package/command/win/status.bat +52 -0
- package/command/win/stop.bat +55 -0
- package/command/win/windows/345/220/257/345/212/250/350/204/232/346/234/254 +0 -0
- package/package.json +37 -0
- package/scripts/install-silent.js +167 -0
- package/scripts/uninstall.js +61 -0
- package/service/daemon.js +189 -0
- package/service/logger.js +31 -0
- package/service/modules/auth.js +17 -0
- package/service/modules/business-message-handler.js +118 -0
- package/service/modules/command-handler.js +152 -0
- package/service/modules/config.js +44 -0
- package/service/modules/dashboard-collector.js +588 -0
- package/service/modules/heartbeat-dashboard.js +153 -0
- package/service/modules/mac-address.js +15 -0
- package/service/modules/message-processor-example.js +72 -0
- package/service/modules/message-processor.js +62 -0
- package/service/modules/normal-message-handler.js +60 -0
- package/service/modules/openclaw-control.js +128 -0
- package/service/modules/openclaw-enum.js +48 -0
- package/service/modules/opencode-service.js +199 -0
- package/service/modules/opencode-starter.js +194 -0
- package/service/modules/port-checker.js +31 -0
- package/service/modules/rongyun-message-handler.js +250 -0
- package/service/modules/rongyun-message-sender.js +157 -0
- package/service/modules/rongyun-message-types.js +28 -0
- package/service/modules/script-executor.js +550 -0
- package/service/modules/service-manager.js +319 -0
- package/service/modules/structured-message-router.js +118 -0
- package/service/rongcloud/env-polyfill.js +95 -0
- package/service/rongcloud/index.js +19 -0
- package/service/rongcloud/message-handler.js +147 -0
- package/service/rongcloud/message-types.js +22 -0
- package/service/rongcloud/openclaw-client.js +98 -0
- package/service/rongcloud/openclaw-config.js +108 -0
- package/service/rongcloud/rongcloud-client.js +273 -0
- package/service/rongcloud/types.js +9 -0
- package/service/updater.js +348 -0
- package/service/worker.js +376 -0
- package/version.json +4 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const RongyunMessageTypeEnum = {
|
|
2
|
+
CLIENT_CONNECTED: "client_connected",
|
|
3
|
+
CLIENT_DISCONNECTED: "client_disconnected",
|
|
4
|
+
HEARTBEAT: "heartbeat",
|
|
5
|
+
HEARTBEAT_ACK: "heartbeat_ack",
|
|
6
|
+
DASHBOARD_REPORT: "dashboard_report",
|
|
7
|
+
DASHBOARD_REPORT_ACK: "dashboard_report_ack",
|
|
8
|
+
DASHBOARD_SESSIONS: "dashboard_sessions",
|
|
9
|
+
DASHBOARD_JOBS: "dashboard_jobs",
|
|
10
|
+
DASHBOARD_PROJECTS: "dashboard_projects",
|
|
11
|
+
DASHBOARD_SUMMARIES: "dashboard_summaries",
|
|
12
|
+
DASHBOARD_SESSIONS_CONTEXTS: "dashboard_sessions_contexts",
|
|
13
|
+
DASHBOARD_USAGE_EVENTS: "dashboard_usage_events",
|
|
14
|
+
COMMAND: "command",
|
|
15
|
+
COMMAND_RESULT: "command_result",
|
|
16
|
+
CHAT_MESSAGE: "chat_message",
|
|
17
|
+
CREATE_OPENCODE_SESSION: "create_opencode_session",
|
|
18
|
+
OPENCODE_SESSION_CREATED: "opencode_session_created",
|
|
19
|
+
DELETE_OPENCODE_SESSION: "delete_opencode_session"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = { RongyunMessageTypeEnum };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { exec } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
class OpenClawClient {
|
|
7
|
+
constructor(log) {
|
|
8
|
+
this.log = log;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async chat(message, fromUser) {
|
|
12
|
+
if (!message || !message.trim()) {
|
|
13
|
+
return '消息内容为空';
|
|
14
|
+
}
|
|
15
|
+
this.log?.info(`[OpenClawClient] 准备发送消息到 OpenClaw,from=${fromUser}, message=${message.substring(0, 50)}`);
|
|
16
|
+
// 固定 session-id 前缀,确保静默服务的会话与桌面客户端完全隔离
|
|
17
|
+
const sessionId = `clawmessenger-${fromUser}`;
|
|
18
|
+
// 转义消息中的特殊字符:双引号和换行符
|
|
19
|
+
const escapedMessage = message
|
|
20
|
+
.replace(/\\/g, '\\\\')
|
|
21
|
+
.replace(/"/g, '\\"')
|
|
22
|
+
.replace(/\r\n/g, ' ')
|
|
23
|
+
.replace(/\n/g, ' ')
|
|
24
|
+
.replace(/\r/g, ' ');
|
|
25
|
+
// 使用默认 agent (main) 处理消息,不指定 --agent 参数
|
|
26
|
+
const gatewayUrl = 'http://127.0.0.1:5678';
|
|
27
|
+
const cmd = `openclaw agent -m "${escapedMessage}" --session-id ${sessionId}`;
|
|
28
|
+
|
|
29
|
+
this.log?.info(`[OpenClawClient] 执行命令: ${cmd}`);
|
|
30
|
+
this.log?.info(`[OpenClawClient] Gateway URL: ${gatewayUrl}`);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
34
|
+
timeout: 1200000,
|
|
35
|
+
maxBuffer: 1024 * 1024,
|
|
36
|
+
shell: true,
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
env: {
|
|
39
|
+
...process.env,
|
|
40
|
+
OPENCLAW_GATEWAY_URL: gatewayUrl,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.log?.info(`[OpenClawClient] stdout: ${stdout?.substring(0, 200) || '(empty)'}`);
|
|
45
|
+
if (stderr) this.log?.error(`[OpenClawClient] stderr: ${stderr?.substring(0, 200) || '(empty)'}`);
|
|
46
|
+
|
|
47
|
+
const output = stdout || stderr || '';
|
|
48
|
+
return this.cleanOutput(output);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
this.log?.error(`[OpenClawClient] 执行异常: ${err.message}`);
|
|
51
|
+
this.log?.error(`[OpenClawClient] 错误码: ${err.code}`);
|
|
52
|
+
if (err.killed) {
|
|
53
|
+
return 'OpenClaw 响应超时';
|
|
54
|
+
}
|
|
55
|
+
if (err.code === 'ENOENT') {
|
|
56
|
+
return '找不到 openclaw 命令';
|
|
57
|
+
}
|
|
58
|
+
return `OpenClaw 调用失败: ${err.message}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cleanOutput(output) {
|
|
63
|
+
const lines = output.split('\n');
|
|
64
|
+
const cleanLines = [];
|
|
65
|
+
|
|
66
|
+
for (const line of lines) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (!trimmed) continue;
|
|
69
|
+
|
|
70
|
+
// 移除 ANSI 颜色代码
|
|
71
|
+
const cleanLine = trimmed.replace(/\x1B\[[0-9;]*m/g, '');
|
|
72
|
+
|
|
73
|
+
// 跳过所有调试/配置日志行
|
|
74
|
+
if (cleanLine.startsWith('[ws]')) continue;
|
|
75
|
+
if (cleanLine.startsWith('[health-monitor]')) continue;
|
|
76
|
+
if (cleanLine.startsWith('[OpenClawConfig]')) continue;
|
|
77
|
+
if (cleanLine.startsWith('[plugins]')) continue;
|
|
78
|
+
if (cleanLine.includes('龙虾信使插件已注册')) continue;
|
|
79
|
+
if (cleanLine.includes('龙虾信使')) continue;
|
|
80
|
+
if (cleanLine.includes('已加载配置文件')) continue;
|
|
81
|
+
if (cleanLine.includes('plugins.allow')) continue;
|
|
82
|
+
if (cleanLine.includes('Config warnings')) continue;
|
|
83
|
+
if (cleanLine.includes('stale config')) continue;
|
|
84
|
+
if (cleanLine.includes('plugin not found')) continue;
|
|
85
|
+
if (cleanLine.includes('⇄ res')) continue;
|
|
86
|
+
if (cleanLine.includes('chat.history')) continue;
|
|
87
|
+
if (cleanLine.includes('models.list')) continue;
|
|
88
|
+
if (cleanLine.includes('node.list')) continue;
|
|
89
|
+
if (/^\d{2}:\d{2}:\d{2}/.test(cleanLine)) continue; // 时间戳开头的日志
|
|
90
|
+
|
|
91
|
+
cleanLines.push(cleanLine);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return cleanLines.join('\n').trim() || 'OpenClaw 未返回有效响应';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { OpenClawClient };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { homedir } = require('os');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const { mkdirSync, readFileSync, writeFileSync, existsSync, renameSync } = require('fs');
|
|
4
|
+
|
|
5
|
+
const OPENCLAW_DIR = join(homedir(), '.openclaw');
|
|
6
|
+
const SETTINGS_FILE = join(OPENCLAW_DIR, 'openclaw.json');
|
|
7
|
+
|
|
8
|
+
// 静默服务不需要强制启用任何 openclaw 插件
|
|
9
|
+
const DEFAULT_PLUGINS = [];
|
|
10
|
+
const REMOVED_PLUGINS = ['openclaw-weixin', 'openclaw-wechat'];
|
|
11
|
+
|
|
12
|
+
function isPluginDirExists(pluginName) {
|
|
13
|
+
const pluginDir = join(OPENCLAW_DIR, 'extensions', pluginName);
|
|
14
|
+
return existsSync(pluginDir);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function disableBrokenPlugin(pluginName, log) {
|
|
18
|
+
const pluginDir = join(OPENCLAW_DIR, 'extensions', pluginName);
|
|
19
|
+
const disabledDir = pluginDir + '.bak.disabled';
|
|
20
|
+
if (!existsSync(pluginDir)) return;
|
|
21
|
+
try {
|
|
22
|
+
if (existsSync(disabledDir)) {
|
|
23
|
+
const { rmSync } = require('fs');
|
|
24
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
25
|
+
} else {
|
|
26
|
+
renameSync(pluginDir, disabledDir);
|
|
27
|
+
}
|
|
28
|
+
log?.warn(`[OpenClawConfig] ${pluginName} 已禁用(目录重命名为 .bak.disabled)`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
log?.error(`[OpenClawConfig] 禁用 ${pluginName} 失败: ${err.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ensurePluginsAllow(log) {
|
|
35
|
+
try {
|
|
36
|
+
if (!existsSync(OPENCLAW_DIR)) {
|
|
37
|
+
mkdirSync(OPENCLAW_DIR, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
|
|
41
|
+
let settings = {};
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const content = readFileSync(SETTINGS_FILE, 'utf-8');
|
|
45
|
+
settings = JSON.parse(content);
|
|
46
|
+
log?.info('[OpenClawConfig] 已加载配置');
|
|
47
|
+
} catch {
|
|
48
|
+
log?.info('[OpenClawConfig] 创建新配置');
|
|
49
|
+
settings = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!settings.plugins) settings.plugins = {};
|
|
53
|
+
|
|
54
|
+
const existing = settings.plugins.allow || [];
|
|
55
|
+
const cleaned = existing.filter(p => !REMOVED_PLUGINS.includes(p));
|
|
56
|
+
const removedCount = existing.length - cleaned.length;
|
|
57
|
+
|
|
58
|
+
const brokenPlugins = [];
|
|
59
|
+
const healthyPlugins = cleaned.filter(p => {
|
|
60
|
+
if (!isPluginDirExists(p)) return false;
|
|
61
|
+
if (p === 'claw_messenger') {
|
|
62
|
+
const enginePath = join(OPENCLAW_DIR, 'extensions', 'claw_messenger', 'node_modules', '@rongcloud', 'engine');
|
|
63
|
+
if (!existsSync(enginePath)) {
|
|
64
|
+
log?.warn(`[OpenClawConfig] claw_messenger 缺少 @rongcloud/engine,自动移除`);
|
|
65
|
+
brokenPlugins.push(p);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
const removedBroken = cleaned.length - healthyPlugins.length;
|
|
72
|
+
|
|
73
|
+
for (const p of brokenPlugins) {
|
|
74
|
+
disableBrokenPlugin(p, log);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (settings.channels) {
|
|
78
|
+
for (const p of brokenPlugins) {
|
|
79
|
+
if (settings.channels[p] !== undefined) {
|
|
80
|
+
delete settings.channels[p];
|
|
81
|
+
log?.warn(`[OpenClawConfig] 已从 channels 移除 ${p}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (removedCount === 0 && removedBroken === 0) {
|
|
87
|
+
log?.info('[OpenClawConfig] plugins.allow 已完整');
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
settings.plugins.allow = healthyPlugins;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
|
|
95
|
+
if (removedCount > 0) {
|
|
96
|
+
log?.info(`[OpenClawConfig] 已清理无效插件: ${REMOVED_PLUGINS.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
if (removedBroken > 0) {
|
|
99
|
+
log?.info(`[OpenClawConfig] 已清理损坏插件: ${removedBroken} 个`);
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
log?.error(`[OpenClawConfig] 写入失败: ${err.message}`);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { ensurePluginsAllow };
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
require("./env-polyfill");
|
|
3
|
+
|
|
4
|
+
const RongIMLibModule = require("@rongcloud/imlib-next");
|
|
5
|
+
const RongIMLib = RongIMLibModule.default || RongIMLibModule;
|
|
6
|
+
|
|
7
|
+
const ConversationType = {
|
|
8
|
+
PRIVATE: 1,
|
|
9
|
+
GROUP: 3
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const SYSTEM_MSG_TYPES = new Set([
|
|
13
|
+
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
class RongCloudClient {
|
|
17
|
+
constructor(config, log) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.log = log;
|
|
20
|
+
this.isConnected = false;
|
|
21
|
+
this.handler = null;
|
|
22
|
+
this.processingQueue = Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async connect(handler) {
|
|
26
|
+
this.handler = handler;
|
|
27
|
+
this.log?.info('[RongCloudClient] 开始连接融云...');
|
|
28
|
+
|
|
29
|
+
if (!RongIMLib || typeof RongIMLib.init !== 'function') {
|
|
30
|
+
this.log?.error('[RongCloudClient] SDK 未正确加载');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.log?.info('[RongCloudClient] 初始化 SDK...');
|
|
35
|
+
RongIMLib.init({ appkey: this.config.appKey });
|
|
36
|
+
|
|
37
|
+
this.log?.info(`[RongCloudClient] SDK Events: ${JSON.stringify(Object.keys(RongIMLib.Events || {}))}`);
|
|
38
|
+
this.log?.info(`[RongCloudClient] has addEventListener: ${typeof RongIMLib.addEventListener === 'function'}`);
|
|
39
|
+
this.log?.info(`[RongCloudClient] has sendReadReceiptMessage: ${typeof RongIMLib.sendReadReceiptMessage === 'function'}`);
|
|
40
|
+
this.log?.info(`[RongCloudClient] has sendReadReceiptResponseV2: ${typeof RongIMLib.sendReadReceiptResponseV2 === 'function'}`);
|
|
41
|
+
this.log?.info(`[RongCloudClient] has sendReadReceiptResponseV5: ${typeof RongIMLib.sendReadReceiptResponseV5 === 'function'}`);
|
|
42
|
+
|
|
43
|
+
if (RongIMLib.addEventListener) {
|
|
44
|
+
this.log?.info('[RongCloudClient] 使用 addEventListener 模式');
|
|
45
|
+
|
|
46
|
+
RongIMLib.addEventListener(RongIMLib.Events?.MESSAGES || 'MESSAGES', (event) => {
|
|
47
|
+
// this.log?.info(`[RongCloudClient] 收到消息事件: ${JSON.stringify(event).substring(0, 200)}`);
|
|
48
|
+
event.messages?.forEach(msg => {
|
|
49
|
+
this.handleReceivedMessage(msg);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
RongIMLib.addEventListener(RongIMLib.Events?.CONNECTED || 'CONNECTED', () => {
|
|
54
|
+
this.log?.info('[RongCloudClient] 连接成功事件');
|
|
55
|
+
this.isConnected = true;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
RongIMLib.addEventListener(RongIMLib.Events?.DISCONNECT || 'DISCONNECT', (code) => {
|
|
59
|
+
this.log?.warn(`[RongCloudClient] 断开连接, code: ${code}`);
|
|
60
|
+
this.isConnected = false;
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
this.log?.info('[RongCloudClient] 使用 setOnReceiveMessageListener 模式');
|
|
64
|
+
|
|
65
|
+
RongIMLib.setConnectionStatusListener({
|
|
66
|
+
onChanged: (status) => {
|
|
67
|
+
this.log?.info(`[RongCloudClient] 连接状态变化: ${status}`);
|
|
68
|
+
this.isConnected = status === 3 || status === 'Connected';
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
RongIMLib.setOnReceiveMessageListener({
|
|
73
|
+
onReceived: (message) => {
|
|
74
|
+
this.log?.info(`[RongCloudClient] onReceived: messageType=${message.messageType}, senderUserId=${message.senderUserId}`);
|
|
75
|
+
this.handleReceivedMessage(message);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
this.log?.info('[RongCloudClient] 正在连接...');
|
|
82
|
+
const result = await RongIMLib.connect(this.config.token);
|
|
83
|
+
this.log?.info(`[RongCloudClient] connect 结果: code=${result.code}`);
|
|
84
|
+
|
|
85
|
+
if (result.code === 0 || result.code === 200) {
|
|
86
|
+
const userId = result.data?.userId || 'unknown';
|
|
87
|
+
this.log?.info(`[RongCloudClient] 登录成功, userId: ${userId}`);
|
|
88
|
+
this.isConnected = true;
|
|
89
|
+
return true;
|
|
90
|
+
} else {
|
|
91
|
+
this.log?.error(`[RongCloudClient] 登录失败, code: ${result.code}`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
this.log?.error(`[RongCloudClient] 连接异常: ${err.message}`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
handleReceivedMessage(message) {
|
|
101
|
+
if (message.isOffLineMessage) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const msgType = message.messageType;
|
|
107
|
+
let rawContent = message.content;
|
|
108
|
+
|
|
109
|
+
// 自定义消息 content 可能是对象,提取文本内容
|
|
110
|
+
if (rawContent && typeof rawContent === 'object') {
|
|
111
|
+
rawContent = rawContent.content || rawContent.text || JSON.stringify(rawContent);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const content = rawContent || '';
|
|
115
|
+
|
|
116
|
+
this.log?.info(`[RongCloudClient] 收到消息: type=${msgType}, from=${message.senderUserId}`);
|
|
117
|
+
|
|
118
|
+
if (!content || !content.trim || !content.trim()) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let parsed = null;
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(content);
|
|
125
|
+
} catch { }
|
|
126
|
+
|
|
127
|
+
if (parsed && parsed.msg_type) {
|
|
128
|
+
if (SYSTEM_MSG_TYPES.has(parsed.msg_type)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (parsed.source_im_id === this.config.accountId) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const userContent = parsed && !parsed.msg_type
|
|
137
|
+
? (parsed.content || parsed.text || JSON.stringify(parsed))
|
|
138
|
+
: content;
|
|
139
|
+
|
|
140
|
+
if (!userContent || !userContent.trim || !userContent.trim()) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const senderUserId = parsed?.source_im_id || message.senderUserId || 'unknown';
|
|
145
|
+
|
|
146
|
+
const rongCloudMsg = {
|
|
147
|
+
senderUserId,
|
|
148
|
+
targetId: message.targetId || senderUserId,
|
|
149
|
+
conversationType: message.conversationType || ConversationType.PRIVATE,
|
|
150
|
+
content: userContent,
|
|
151
|
+
messageType: msgType || 'RC:TxtMsg',
|
|
152
|
+
isOffLineMessage: message.isOffLineMessage || false,
|
|
153
|
+
messageUId: message.messageUId || `local-${Date.now()}`,
|
|
154
|
+
sentTime: message.sentTime || Date.now()
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
this.processingQueue = this.processingQueue.then(async () => {
|
|
158
|
+
if (this.handler) {
|
|
159
|
+
await this.handler.handleMessage(rongCloudMsg);
|
|
160
|
+
}
|
|
161
|
+
}).catch(err => {
|
|
162
|
+
this.log?.error(`[RongCloudClient] 消息处理异常: ${err.message}`);
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
this.log?.error(`[RongCloudClient] 解析消息失败: ${err.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async sendMessage(targetId, content, conversationType) {
|
|
170
|
+
if (!this.isConnected) {
|
|
171
|
+
this.log?.error('[RongCloudClient] 未连接,无法发送消息');
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const convType = conversationType === ConversationType.GROUP
|
|
177
|
+
? (RongIMLib.ConversationType?.GROUP || ConversationType.GROUP)
|
|
178
|
+
: (RongIMLib.ConversationType?.PRIVATE || ConversationType.PRIVATE);
|
|
179
|
+
|
|
180
|
+
this.log?.info(`[RongCloudClient] 发送消息 -> ${targetId} (Type: ${convType}): ${content.substring(0, 50)}`);
|
|
181
|
+
|
|
182
|
+
const result = await RongIMLib.sendTextMessage(
|
|
183
|
+
{ conversationType: convType, targetId },
|
|
184
|
+
{ content }
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// this.log?.info(`[RongCloudClient] 发送结果: code=${result.code}`);
|
|
188
|
+
|
|
189
|
+
if (result.code === 0 || result.code === 200) {
|
|
190
|
+
this.log?.info(`[RongCloudClient] 发送成功, messageUId: ${result.data?.messageUId}`);
|
|
191
|
+
return true;
|
|
192
|
+
} else {
|
|
193
|
+
this.log?.error(`[RongCloudClient] 发送失败, code: ${result.code}`);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
this.log?.error(`[RongCloudClient] 发送异常: ${err.message}`);
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async sendReadReceipt(msg) {
|
|
203
|
+
if (!this.isConnected) {
|
|
204
|
+
this.log?.warn('[RongCloudClient] 未连接,无法发送已读回执');
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { conversationType, senderUserId, targetId, messageUId, sentTime } = msg;
|
|
209
|
+
if (!messageUId || !sentTime) {
|
|
210
|
+
this.log?.warn('[RongCloudClient] 消息缺少 messageUId 或 sentTime,无法发送已读回执');
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 本地生成的 messageUId 无法发送已读回执
|
|
215
|
+
if (String(messageUId).startsWith('local-')) {
|
|
216
|
+
this.log?.warn('[RongCloudClient] messageUId 为本地生成,跳过已读回执');
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.log?.info(`[RongCloudClient] 准备发送已读回执: conversationType=${conversationType}, senderUserId=${senderUserId}, targetId=${targetId}, messageUId=${messageUId}, sentTime=${sentTime}`);
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// 优先使用 V5 已读回执 API(与前端 enableReadV5 对齐)
|
|
224
|
+
if (typeof RongIMLib.sendReadReceiptResponseV5 === 'function') {
|
|
225
|
+
this.log?.info(`[RongCloudClient] 发送 V5 已读回执 -> targetId=${targetId}, messageUId=${messageUId}`);
|
|
226
|
+
const result = await RongIMLib.sendReadReceiptResponseV5(
|
|
227
|
+
{ conversationType, targetId },
|
|
228
|
+
[messageUId]
|
|
229
|
+
);
|
|
230
|
+
this.log?.info(`[RongCloudClient] V5 已读回执结果: code=${result.code}, msg=${result.msg || ''}`);
|
|
231
|
+
return result.code === 0 || result.code === 200;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (conversationType === ConversationType.GROUP) {
|
|
235
|
+
if (typeof RongIMLib.sendReadReceiptResponseV2 !== 'function') {
|
|
236
|
+
this.log?.warn('[RongCloudClient] SDK 不支持群聊已读回执');
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
this.log?.info(`[RongCloudClient] 发送群聊已读回执 -> ${targetId}, messageUId=${messageUId}`);
|
|
240
|
+
const result = await RongIMLib.sendReadReceiptResponseV2(targetId, {
|
|
241
|
+
[senderUserId]: [messageUId]
|
|
242
|
+
});
|
|
243
|
+
this.log?.info(`[RongCloudClient] 群聊已读回执结果: code=${result.code}, msg=${result.msg || ''}`);
|
|
244
|
+
return result.code === 0 || result.code === 200;
|
|
245
|
+
} else {
|
|
246
|
+
if (typeof RongIMLib.sendReadReceiptMessage !== 'function') {
|
|
247
|
+
this.log?.warn('[RongCloudClient] SDK 不支持单聊已读回执');
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
this.log?.info(`[RongCloudClient] 发送单聊已读回执 -> ${senderUserId}, messageUId=${messageUId}`);
|
|
251
|
+
const result = await RongIMLib.sendReadReceiptMessage(senderUserId, messageUId, sentTime);
|
|
252
|
+
this.log?.info(`[RongCloudClient] 单聊已读回执结果: code=${result.code}, msg=${result.msg || ''}`);
|
|
253
|
+
return result.code === 0 || result.code === 200;
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
this.log?.error(`[RongCloudClient] 发送已读回执异常: ${err.message}`);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async disconnect() {
|
|
262
|
+
this.log?.info('[RongCloudClient] 断开连接...');
|
|
263
|
+
this.isConnected = false;
|
|
264
|
+
try {
|
|
265
|
+
await RongIMLib.disconnect();
|
|
266
|
+
this.log?.info('[RongCloudClient] 已断开');
|
|
267
|
+
} catch (err) {
|
|
268
|
+
this.log?.error(`[RongCloudClient] 断开异常: ${err.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = { RongCloudClient, ConversationType };
|