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,109 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import { createLogger } from './logger.js';
|
|
3
|
+
import { getMacAddress } from './mac-address.js';
|
|
4
|
+
const log = createLogger('device-status');
|
|
5
|
+
/**
|
|
6
|
+
* 快速检测本地端口是否监听
|
|
7
|
+
* @param port 端口号
|
|
8
|
+
* @param host 主机地址,默认 127.0.0.1
|
|
9
|
+
* @param timeoutMs 超时时间,默认 2000ms
|
|
10
|
+
*/
|
|
11
|
+
export function checkPort(port, host = '127.0.0.1', timeoutMs = 2000) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const socket = new net.Socket();
|
|
14
|
+
let settled = false;
|
|
15
|
+
const done = (result) => {
|
|
16
|
+
if (settled)
|
|
17
|
+
return;
|
|
18
|
+
settled = true;
|
|
19
|
+
try {
|
|
20
|
+
socket.destroy();
|
|
21
|
+
}
|
|
22
|
+
catch (_a) { }
|
|
23
|
+
resolve(result);
|
|
24
|
+
};
|
|
25
|
+
socket.setTimeout(timeoutMs);
|
|
26
|
+
socket.once('connect', () => done(true));
|
|
27
|
+
socket.once('error', () => done(false));
|
|
28
|
+
socket.once('timeout', () => done(false));
|
|
29
|
+
try {
|
|
30
|
+
socket.connect(port, host);
|
|
31
|
+
}
|
|
32
|
+
catch (_a) {
|
|
33
|
+
done(false);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const OPENCLAW_GATEWAY_PORT = 18789;
|
|
38
|
+
const OPENCODE_GATEWAY_PORT = 4096;
|
|
39
|
+
/**
|
|
40
|
+
* 获取设备上 OpenClaw / OpenCode 的运行状态
|
|
41
|
+
* 通过检查本地端口 18789(OpenClaw gateway)和 4096(OpenCode)实现
|
|
42
|
+
*/
|
|
43
|
+
export async function getDeviceRunningStatus(openclawPort = OPENCLAW_GATEWAY_PORT, opencodePort = OPENCODE_GATEWAY_PORT) {
|
|
44
|
+
const [openclawRunning, opencodeRunning] = await Promise.all([
|
|
45
|
+
checkPort(openclawPort, '127.0.0.1', 2000),
|
|
46
|
+
checkPort(opencodePort, '127.0.0.1', 2000),
|
|
47
|
+
]);
|
|
48
|
+
const openclawStatus = openclawRunning ? 1 : 0;
|
|
49
|
+
const opencodeStatus = opencodeRunning ? 1 : 0;
|
|
50
|
+
const parts = [];
|
|
51
|
+
if (openclawStatus) {
|
|
52
|
+
parts.push('OpenClaw运行中');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
parts.push('OpenClaw未运行');
|
|
56
|
+
}
|
|
57
|
+
if (opencodeStatus) {
|
|
58
|
+
parts.push('OpenCode运行中');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
parts.push('OpenCode未运行');
|
|
62
|
+
}
|
|
63
|
+
const status = {
|
|
64
|
+
openclaw_status: openclawStatus,
|
|
65
|
+
opencode_status: opencodeStatus,
|
|
66
|
+
status_message: parts.join(' | '),
|
|
67
|
+
mac_address: getMacAddress(),
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
};
|
|
70
|
+
log.info(`[DeviceStatus] 设备运行状态: ${JSON.stringify(status)}`);
|
|
71
|
+
return status;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 判断消息是否为设备状态请求
|
|
75
|
+
*/
|
|
76
|
+
export function isDeviceStatusRequest(msg) {
|
|
77
|
+
if (!msg || msg.messageType !== 'command')
|
|
78
|
+
return false;
|
|
79
|
+
let content = msg.content;
|
|
80
|
+
if (typeof content === 'string') {
|
|
81
|
+
try {
|
|
82
|
+
content = JSON.parse(content);
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return content && content.msg_type === 'device_status_request';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 从状态请求消息中提取 request_id 和来源用户ID
|
|
92
|
+
*/
|
|
93
|
+
export function parseDeviceStatusRequest(msg) {
|
|
94
|
+
let content = msg.content;
|
|
95
|
+
if (typeof content === 'string') {
|
|
96
|
+
try {
|
|
97
|
+
content = JSON.parse(content);
|
|
98
|
+
}
|
|
99
|
+
catch (_a) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!content || content.msg_type !== 'device_status_request')
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
requestId: content.request_id || content.requestId || '',
|
|
107
|
+
sourceId: content.source_im_id || msg.senderUserId || '',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 环境模拟 (Polyfill)
|
|
4
|
+
* 让融云 Web SDK (@rongcloud/imlib-next) 能在 Node.js 环境中运行
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.window = void 0;
|
|
8
|
+
// 1. 必须最先引入,注入 indexedDB
|
|
9
|
+
require("fake-indexeddb/auto");
|
|
10
|
+
var jsdom_1 = require("jsdom");
|
|
11
|
+
var ws_1 = require("ws");
|
|
12
|
+
var node_buffer_1 = require("node:buffer");
|
|
13
|
+
var crypto_1 = require("crypto");
|
|
14
|
+
// 2. 初始化 JSDOM
|
|
15
|
+
var dom = new jsdom_1.JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
16
|
+
url: 'http://localhost',
|
|
17
|
+
pretendToBeVisual: true,
|
|
18
|
+
resources: 'usable',
|
|
19
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
20
|
+
});
|
|
21
|
+
var window = dom.window;
|
|
22
|
+
exports.window = window;
|
|
23
|
+
var g = global;
|
|
24
|
+
var win = window;
|
|
25
|
+
// 3. 辅助函数:安全定义属性
|
|
26
|
+
var defineWinProp = function (key, value) {
|
|
27
|
+
try {
|
|
28
|
+
Object.defineProperty(win, key, {
|
|
29
|
+
value: value,
|
|
30
|
+
writable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
enumerable: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (e) { /* ignore */ }
|
|
36
|
+
};
|
|
37
|
+
var defineGlobalProp = function (key, value) {
|
|
38
|
+
try {
|
|
39
|
+
Object.defineProperty(g, key, {
|
|
40
|
+
value: value,
|
|
41
|
+
writable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
enumerable: true
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (e) { /* ignore */ }
|
|
47
|
+
};
|
|
48
|
+
// 4. 注入 IndexedDB 到 window (关键)
|
|
49
|
+
if (g.indexedDB) {
|
|
50
|
+
defineWinProp('indexedDB', g.indexedDB);
|
|
51
|
+
defineWinProp('IDBKeyRange', g.IDBKeyRange);
|
|
52
|
+
defineWinProp('IDBRequest', g.IDBRequest);
|
|
53
|
+
defineWinProp('IDBDatabase', g.IDBDatabase);
|
|
54
|
+
defineWinProp('IDBTransaction', g.IDBTransaction);
|
|
55
|
+
defineWinProp('IDBCursor', g.IDBCursor);
|
|
56
|
+
defineWinProp('IDBIndex', g.IDBIndex);
|
|
57
|
+
defineWinProp('IDBFactory', g.IDBFactory);
|
|
58
|
+
console.log('[Polyfill] indexedDB 已注入 window');
|
|
59
|
+
}
|
|
60
|
+
// 5. 注入 Storage (SDK 可能检查 window.localStorage)
|
|
61
|
+
var storageMock = {
|
|
62
|
+
getItem: function (k) { return null; },
|
|
63
|
+
setItem: function (k, v) { },
|
|
64
|
+
removeItem: function (k) { },
|
|
65
|
+
clear: function () { },
|
|
66
|
+
length: 0,
|
|
67
|
+
key: function (i) { return null; }
|
|
68
|
+
};
|
|
69
|
+
defineWinProp('localStorage', storageMock);
|
|
70
|
+
defineWinProp('sessionStorage', storageMock);
|
|
71
|
+
defineGlobalProp('localStorage', storageMock);
|
|
72
|
+
defineGlobalProp('sessionStorage', storageMock);
|
|
73
|
+
// 6. 注入 Crypto (关键:Web Crypto API)
|
|
74
|
+
if (!win.crypto) {
|
|
75
|
+
defineWinProp('crypto', crypto_1.default.webcrypto);
|
|
76
|
+
}
|
|
77
|
+
if (!g.crypto) {
|
|
78
|
+
defineGlobalProp('crypto', crypto_1.default.webcrypto);
|
|
79
|
+
}
|
|
80
|
+
// 7. 注入 Navigator 属性
|
|
81
|
+
defineWinProp('onLine', true);
|
|
82
|
+
defineWinProp('language', 'zh-CN');
|
|
83
|
+
defineGlobalProp('navigator', win.navigator);
|
|
84
|
+
// 8. 注入网络相关
|
|
85
|
+
var http_1 = require("http");
|
|
86
|
+
var https_1 = require("https");
|
|
87
|
+
defineGlobalProp('WebSocket', ws_1.default);
|
|
88
|
+
// Node.js 原生 XMLHttpRequest(无 CORS 限制,融云 SDK 依赖此接口做 HTTP 通信)
|
|
89
|
+
function NodeXHR() {
|
|
90
|
+
this.readyState = 0;
|
|
91
|
+
this.status = 0;
|
|
92
|
+
this.responseText = '';
|
|
93
|
+
this.onreadystatechange = null;
|
|
94
|
+
this._method = '';
|
|
95
|
+
this._url = null;
|
|
96
|
+
this._headers = null;
|
|
97
|
+
}
|
|
98
|
+
NodeXHR.prototype.open = function (method, url) {
|
|
99
|
+
this._method = method;
|
|
100
|
+
try {
|
|
101
|
+
this._url = new URL(url);
|
|
102
|
+
}
|
|
103
|
+
catch (_a) {
|
|
104
|
+
this._url = { protocol: 'http:', hostname: '', pathname: url };
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
NodeXHR.prototype.send = function (body) {
|
|
108
|
+
var self = this;
|
|
109
|
+
if (!self._url) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
var mod = self._url.protocol === 'https:' ? https_1.default : http_1.default;
|
|
113
|
+
var options = {
|
|
114
|
+
method: self._method,
|
|
115
|
+
headers: self._headers || {},
|
|
116
|
+
};
|
|
117
|
+
var req = mod.request(self._url, options, function (res) {
|
|
118
|
+
var data = '';
|
|
119
|
+
res.on('data', function (c) { data += c; });
|
|
120
|
+
res.on('end', function () {
|
|
121
|
+
self.status = res.statusCode || 0;
|
|
122
|
+
self.readyState = 4;
|
|
123
|
+
self.responseText = data;
|
|
124
|
+
if (typeof self.onreadystatechange === 'function') {
|
|
125
|
+
self.onreadystatechange();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
req.on('error', function () {
|
|
130
|
+
self.readyState = 4;
|
|
131
|
+
if (typeof self.onreadystatechange === 'function') {
|
|
132
|
+
self.onreadystatechange();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (body)
|
|
136
|
+
req.write(body);
|
|
137
|
+
req.end();
|
|
138
|
+
};
|
|
139
|
+
NodeXHR.prototype.setRequestHeader = function (k, v) {
|
|
140
|
+
if (!this._headers)
|
|
141
|
+
this._headers = {};
|
|
142
|
+
this._headers[k] = v;
|
|
143
|
+
};
|
|
144
|
+
NodeXHR.prototype.abort = function () { };
|
|
145
|
+
defineGlobalProp('XMLHttpRequest', NodeXHR);
|
|
146
|
+
try {
|
|
147
|
+
win.XMLHttpRequest = NodeXHR;
|
|
148
|
+
}
|
|
149
|
+
catch (e) { /* ignore */ }
|
|
150
|
+
defineGlobalProp('window', win);
|
|
151
|
+
defineGlobalProp('document', win.document);
|
|
152
|
+
defineGlobalProp('location', win.location);
|
|
153
|
+
// 9. 注入文件相关 (SDK 发送图片/文件时需要)
|
|
154
|
+
if (!win.Blob)
|
|
155
|
+
defineWinProp('Blob', node_buffer_1.Blob);
|
|
156
|
+
if (!win.File)
|
|
157
|
+
defineWinProp('File', node_buffer_1.File);
|
|
158
|
+
if (!win.URL)
|
|
159
|
+
defineWinProp('URL', { createObjectURL: function () { return ''; }, revokeObjectURL: function () { } });
|
|
160
|
+
// 10. 其他常用 API
|
|
161
|
+
if (!win.requestAnimationFrame)
|
|
162
|
+
defineWinProp('requestAnimationFrame', function (cb) { return setTimeout(cb, 16); });
|
|
163
|
+
if (!win.cancelAnimationFrame)
|
|
164
|
+
defineWinProp('cancelAnimationFrame', function (id) { return clearTimeout(id); });
|
|
165
|
+
if (!win.performance)
|
|
166
|
+
defineWinProp('performance', { now: function () { return Date.now(); } });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GroupConfig {
|
|
2
|
+
maxRounds: number;
|
|
3
|
+
currentRounds: number;
|
|
4
|
+
isStopped: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class GroupConfigManager {
|
|
7
|
+
private configs;
|
|
8
|
+
private configFile;
|
|
9
|
+
private configDir;
|
|
10
|
+
constructor(configPath?: string);
|
|
11
|
+
load(): void;
|
|
12
|
+
save(): void;
|
|
13
|
+
getConfig(groupId: string): GroupConfig;
|
|
14
|
+
setMaxRounds(groupId: string, maxRounds: number): void;
|
|
15
|
+
incrementRounds(groupId: string): boolean;
|
|
16
|
+
stopConversation(groupId: string): void;
|
|
17
|
+
startConversation(groupId: string): void;
|
|
18
|
+
resetRounds(groupId: string): void;
|
|
19
|
+
isGroupOwner(groupId: string, userId: string): Promise<boolean>;
|
|
20
|
+
handleCommand(groupId: string, senderUserId: string, content: string): Promise<string | null>;
|
|
21
|
+
}
|
|
22
|
+
export declare const groupConfigManager: GroupConfigManager;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群对话轮数管理器
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
import { createLogger } from './logger.js';
|
|
9
|
+
const log = createLogger('group-config');
|
|
10
|
+
const CONFIG_DIR = path.join(os.homedir(), '.claw-bridge', 'openclaw');
|
|
11
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'group-conversation-config.json');
|
|
12
|
+
const SERVER_URL = process.env.DM_SERVER_URL || 'https://newsradar.dreamdt.cn/im';
|
|
13
|
+
export class GroupConfigManager {
|
|
14
|
+
constructor(configPath) {
|
|
15
|
+
this.configs = {};
|
|
16
|
+
this.configFile = configPath || CONFIG_FILE;
|
|
17
|
+
this.configDir = path.dirname(this.configFile);
|
|
18
|
+
this.load();
|
|
19
|
+
}
|
|
20
|
+
load() {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync(this.configFile)) {
|
|
23
|
+
const content = fs.readFileSync(this.configFile, 'utf-8');
|
|
24
|
+
this.configs = JSON.parse(content);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
this.configs = {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
save() {
|
|
32
|
+
try {
|
|
33
|
+
if (!fs.existsSync(this.configDir)) {
|
|
34
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
fs.writeFileSync(this.configFile, JSON.stringify(this.configs, null, 2), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
log.error('[GroupConfig] 保存配置失败: ' + e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
getConfig(groupId) {
|
|
43
|
+
if (!this.configs[groupId]) {
|
|
44
|
+
this.configs[groupId] = { maxRounds: 100, currentRounds: 0, isStopped: false };
|
|
45
|
+
}
|
|
46
|
+
return this.configs[groupId];
|
|
47
|
+
}
|
|
48
|
+
setMaxRounds(groupId, maxRounds) {
|
|
49
|
+
const config = this.getConfig(groupId);
|
|
50
|
+
config.maxRounds = maxRounds;
|
|
51
|
+
this.save();
|
|
52
|
+
}
|
|
53
|
+
incrementRounds(groupId) {
|
|
54
|
+
const config = this.getConfig(groupId);
|
|
55
|
+
if (config.isStopped || config.currentRounds >= config.maxRounds) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
config.currentRounds++;
|
|
59
|
+
this.save();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
stopConversation(groupId) {
|
|
63
|
+
const config = this.getConfig(groupId);
|
|
64
|
+
config.isStopped = true;
|
|
65
|
+
this.save();
|
|
66
|
+
}
|
|
67
|
+
startConversation(groupId) {
|
|
68
|
+
const config = this.getConfig(groupId);
|
|
69
|
+
config.isStopped = false;
|
|
70
|
+
this.save();
|
|
71
|
+
}
|
|
72
|
+
resetRounds(groupId) {
|
|
73
|
+
const config = this.getConfig(groupId);
|
|
74
|
+
config.currentRounds = 0;
|
|
75
|
+
this.save();
|
|
76
|
+
}
|
|
77
|
+
async isGroupOwner(groupId, userId) {
|
|
78
|
+
var _a;
|
|
79
|
+
try {
|
|
80
|
+
const resp = await axios.get(`${SERVER_URL}/im/api/group/info`, {
|
|
81
|
+
params: { groupId },
|
|
82
|
+
timeout: 10000,
|
|
83
|
+
});
|
|
84
|
+
if (((_a = resp.data) === null || _a === void 0 ? void 0 : _a.code) === 200 && resp.data.data) {
|
|
85
|
+
return resp.data.data.ownerId === userId;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
log.error('[GroupConfig] 获取群信息失败:', e.message);
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
async handleCommand(groupId, senderUserId, content) {
|
|
94
|
+
const trimmed = content.trim();
|
|
95
|
+
if (!trimmed.startsWith('/claw '))
|
|
96
|
+
return null;
|
|
97
|
+
const parts = trimmed.slice(6).trim().split(/\s+/);
|
|
98
|
+
const command = parts[0];
|
|
99
|
+
const isOwner = await this.isGroupOwner(groupId, senderUserId);
|
|
100
|
+
if (!isOwner) {
|
|
101
|
+
return '❌ 只有群主可以执行 OpenClaw 控制指令';
|
|
102
|
+
}
|
|
103
|
+
switch (command) {
|
|
104
|
+
case 'stop':
|
|
105
|
+
this.stopConversation(groupId);
|
|
106
|
+
return '⏹ OpenClaw 对话已停止';
|
|
107
|
+
case 'start':
|
|
108
|
+
this.startConversation(groupId);
|
|
109
|
+
return '▶️ OpenClaw 对话已恢复';
|
|
110
|
+
case 'reset':
|
|
111
|
+
this.resetRounds(groupId);
|
|
112
|
+
return '🔄 OpenClaw 对话轮数已重置';
|
|
113
|
+
case 'setRounds': {
|
|
114
|
+
const num = parseInt(parts[1], 10);
|
|
115
|
+
if (isNaN(num) || num < 1) {
|
|
116
|
+
return '❌ 轮数必须是大于0的数字';
|
|
117
|
+
}
|
|
118
|
+
this.setMaxRounds(groupId, num);
|
|
119
|
+
return `✅ OpenClaw 最大对话轮数已设置为 ${num}`;
|
|
120
|
+
}
|
|
121
|
+
case 'status': {
|
|
122
|
+
const config = this.getConfig(groupId);
|
|
123
|
+
return `📊 OpenClaw 状态:${config.isStopped ? '已停止' : '进行中'},轮数 ${config.currentRounds}/${config.maxRounds}`;
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export const groupConfigManager = new GroupConfigManager();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface OpenClawChannel {
|
|
2
|
+
name: string;
|
|
3
|
+
initialize(config: any): Promise<void>;
|
|
4
|
+
sendMessage(to: string, content: string, metadata?: any): Promise<string>;
|
|
5
|
+
}
|
|
6
|
+
export declare class ClawMessengerChannel implements OpenClawChannel {
|
|
7
|
+
name: string;
|
|
8
|
+
private config;
|
|
9
|
+
private initialized;
|
|
10
|
+
initialize(config: any): Promise<void>;
|
|
11
|
+
sendMessage(to: string, content: string, metadata?: any): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
export { registerNode, getOrRegisterToken, loadConfig, getMacAddress, getAppKey, getAppSecret } from './auto-register.js';
|
|
14
|
+
export { OpenClawClient } from './openclaw-client.js';
|
|
15
|
+
export { RongCloudServerAPI } from './rongcloud-server-api.js';
|
|
16
|
+
declare const _default: ClawMessengerChannel;
|
|
17
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw 插件入口
|
|
3
|
+
* 提供虾说 IM 与 OpenClaw 的桥接能力
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from './logger.js';
|
|
6
|
+
import { spawnOpenClaw } from './utils.js';
|
|
7
|
+
const log = createLogger('index');
|
|
8
|
+
export class ClawMessengerChannel {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.name = 'claw_messenger';
|
|
11
|
+
this.config = null;
|
|
12
|
+
this.initialized = false;
|
|
13
|
+
}
|
|
14
|
+
async initialize(config) {
|
|
15
|
+
if (this.initialized)
|
|
16
|
+
return;
|
|
17
|
+
this.config = config;
|
|
18
|
+
log.info('[ClawMessenger] 插件初始化完成');
|
|
19
|
+
this.initialized = true;
|
|
20
|
+
}
|
|
21
|
+
async sendMessage(to, content, metadata) {
|
|
22
|
+
const sessionId = (metadata === null || metadata === void 0 ? void 0 : metadata.sessionId) || `claw-${Date.now()}`;
|
|
23
|
+
try {
|
|
24
|
+
const output = await spawnOpenClaw(['agent', '-m', content, '--session-id', sessionId]);
|
|
25
|
+
return output || 'OpenClaw 未返回有效响应';
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
log.error('[ClawMessenger] OpenClaw 调用失败:', err.message);
|
|
29
|
+
return `OpenClaw 调用失败: ${err.message}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export { registerNode, getOrRegisterToken, loadConfig, getMacAddress, getAppKey, getAppSecret } from './auto-register.js';
|
|
34
|
+
export { OpenClawClient } from './openclaw-client.js';
|
|
35
|
+
export { RongCloudServerAPI } from './rongcloud-server-api.js';
|
|
36
|
+
export default new ClawMessengerChannel();
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export declare class Logger {
|
|
3
|
+
private name;
|
|
4
|
+
private level;
|
|
5
|
+
constructor(name: string, level?: LogLevel);
|
|
6
|
+
private write;
|
|
7
|
+
trace(msg: string, meta?: Record<string, any>): void;
|
|
8
|
+
debug(msg: string, meta?: Record<string, any>): void;
|
|
9
|
+
info(msg: string, meta?: Record<string, any>): void;
|
|
10
|
+
warn(msg: string, meta?: Record<string, any>): void;
|
|
11
|
+
error(msg: string, meta?: Record<string, any>): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function createLogger(name: string, level?: LogLevel): Logger;
|
|
14
|
+
export declare function setGlobalLogLevel(level: LogLevel): void;
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const LEVEL_MAP = {
|
|
5
|
+
trace: 0,
|
|
6
|
+
debug: 1,
|
|
7
|
+
info: 2,
|
|
8
|
+
warn: 3,
|
|
9
|
+
error: 4,
|
|
10
|
+
};
|
|
11
|
+
function getGlobalLogLevel() {
|
|
12
|
+
var _a;
|
|
13
|
+
const env = (_a = process.env.CLAW_LOG_LEVEL) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
14
|
+
if (env && env in LEVEL_MAP)
|
|
15
|
+
return env;
|
|
16
|
+
// 生产环境可设置 CLAW_LOG_LEVEL=error 减少刷屏,默认保持 info 便于调试
|
|
17
|
+
return 'info';
|
|
18
|
+
}
|
|
19
|
+
function getLogDir() {
|
|
20
|
+
const home = os.homedir();
|
|
21
|
+
return path.join(home, '.clawmessenger', 'logs');
|
|
22
|
+
}
|
|
23
|
+
function ensureLogDir() {
|
|
24
|
+
const dir = getLogDir();
|
|
25
|
+
if (!fs.existsSync(dir)) {
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
function formatDate(d) {
|
|
31
|
+
return d.toISOString().replace('T', ' ').slice(0, 19);
|
|
32
|
+
}
|
|
33
|
+
function formatLine(level, msg, meta) {
|
|
34
|
+
let line = `[${formatDate(new Date())}] [${level.toUpperCase()}] ${msg}`;
|
|
35
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
36
|
+
line += ` | ${JSON.stringify(meta)}`;
|
|
37
|
+
}
|
|
38
|
+
return line;
|
|
39
|
+
}
|
|
40
|
+
const globalLevel = getGlobalLogLevel();
|
|
41
|
+
export class Logger {
|
|
42
|
+
constructor(name, level) {
|
|
43
|
+
this.name = name;
|
|
44
|
+
this.level = LEVEL_MAP[level || globalLevel];
|
|
45
|
+
}
|
|
46
|
+
write(level, msg, meta) {
|
|
47
|
+
var _a;
|
|
48
|
+
if (LEVEL_MAP[level] < this.level)
|
|
49
|
+
return;
|
|
50
|
+
const fullMsg = `[${this.name}] ${msg}`;
|
|
51
|
+
const line = formatLine(level, fullMsg, meta);
|
|
52
|
+
// 写入文件(同步追加,避免并发问题)
|
|
53
|
+
try {
|
|
54
|
+
const dir = ensureLogDir();
|
|
55
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
56
|
+
const file = path.join(dir, `clawmessenger-${date}.log`);
|
|
57
|
+
fs.appendFileSync(file, line + '\n', 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
catch (_b) {
|
|
60
|
+
// 忽略文件写入错误,避免日志系统自身崩溃导致主流程失败
|
|
61
|
+
}
|
|
62
|
+
// 控制台输出
|
|
63
|
+
if (typeof ((_a = process.stdout) === null || _a === void 0 ? void 0 : _a.isTTY) === 'boolean') {
|
|
64
|
+
const color = level === 'error' ? '\x1b[31m' :
|
|
65
|
+
level === 'warn' ? '\x1b[33m' :
|
|
66
|
+
level === 'debug' ? '\x1b[36m' :
|
|
67
|
+
level === 'trace' ? '\x1b[90m' :
|
|
68
|
+
'\x1b[32m';
|
|
69
|
+
const reset = '\x1b[0m';
|
|
70
|
+
const consoleFn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
|
71
|
+
consoleFn(`${color}[${level.toUpperCase()}]${reset} ${line}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const consoleFn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
|
75
|
+
consoleFn(line);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
trace(msg, meta) {
|
|
79
|
+
this.write('trace', msg, meta);
|
|
80
|
+
}
|
|
81
|
+
debug(msg, meta) {
|
|
82
|
+
this.write('debug', msg, meta);
|
|
83
|
+
}
|
|
84
|
+
info(msg, meta) {
|
|
85
|
+
this.write('info', msg, meta);
|
|
86
|
+
}
|
|
87
|
+
warn(msg, meta) {
|
|
88
|
+
this.write('warn', msg, meta);
|
|
89
|
+
}
|
|
90
|
+
error(msg, meta) {
|
|
91
|
+
this.write('error', msg, meta);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const loggers = new Map();
|
|
95
|
+
export function createLogger(name, level) {
|
|
96
|
+
if (!loggers.has(name)) {
|
|
97
|
+
loggers.set(name, new Logger(name, level));
|
|
98
|
+
}
|
|
99
|
+
return loggers.get(name);
|
|
100
|
+
}
|
|
101
|
+
export function setGlobalLogLevel(level) {
|
|
102
|
+
process.env.CLAW_LOG_LEVEL = level;
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getMacAddress(): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { platform } from 'os';
|
|
4
|
+
import { createLogger } from './logger.js';
|
|
5
|
+
const log = createLogger('mac-address');
|
|
6
|
+
export function getMacAddress() {
|
|
7
|
+
const os = platform();
|
|
8
|
+
try {
|
|
9
|
+
if (os === 'win32') {
|
|
10
|
+
const result = execSync('getmac /fo csv /nh', { encoding: 'utf-8' });
|
|
11
|
+
const lines = result.trim().split('\n');
|
|
12
|
+
if (lines.length > 0) {
|
|
13
|
+
const parts = lines[0].split(',');
|
|
14
|
+
if (parts.length > 0) {
|
|
15
|
+
const mac = parts[0].replace(/"/g, '').trim().toUpperCase();
|
|
16
|
+
if (/^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$/.test(mac)) {
|
|
17
|
+
return mac.replace(/-/g, ':');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (os === 'linux') {
|
|
23
|
+
const paths = ['/sys/class/net/eth0/address', '/sys/class/net/enp0s3/address'];
|
|
24
|
+
for (const path of paths) {
|
|
25
|
+
try {
|
|
26
|
+
const mac = readFileSync(path, 'utf-8').trim().toUpperCase();
|
|
27
|
+
if (mac)
|
|
28
|
+
return mac;
|
|
29
|
+
}
|
|
30
|
+
catch (_a) { }
|
|
31
|
+
}
|
|
32
|
+
const result = execSync('ip link show | grep ether | head -1', { encoding: 'utf-8' });
|
|
33
|
+
const match = result.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
|
|
34
|
+
return match ? match[1].toUpperCase() : '00:00:00:00:00:00';
|
|
35
|
+
}
|
|
36
|
+
else if (os === 'darwin') {
|
|
37
|
+
const result = execSync('ifconfig en0 | grep ether', { encoding: 'utf-8' });
|
|
38
|
+
const match = result.match(/([0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2})/);
|
|
39
|
+
return match ? match[1].toUpperCase() : '00:00:00:00:00:00';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
log.error('Failed to get MAC address: ' + error);
|
|
44
|
+
}
|
|
45
|
+
return '00:00:00:00:00:00';
|
|
46
|
+
}
|