@zeyiy/openclaw-channel 0.3.5 → 0.3.7
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 +29 -1
- package/README.zh-CN.md +29 -1
- package/dist/config.js +3 -0
- package/dist/inbound.d.ts +3 -1
- package/dist/inbound.js +199 -6
- package/dist/media.js +2 -2
- package/dist/portal.js +1285 -4
- package/dist/tools.js +154 -113
- package/dist/types.d.ts +63 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -1,130 +1,171 @@
|
|
|
1
1
|
import { getConnectedClient } from "./clients";
|
|
2
|
+
import { fetchGroupInfo, fetchGroupMembers } from "./inbound";
|
|
2
3
|
import { sendFileToTarget, sendImageToTarget, sendTextToTarget, sendVideoToTarget } from "./media";
|
|
3
4
|
import { parseTarget } from "./targets";
|
|
4
5
|
import { formatSdkError } from "./utils";
|
|
6
|
+
function err(msg) {
|
|
7
|
+
return { content: [{ type: "text", text: msg }] };
|
|
8
|
+
}
|
|
9
|
+
function ok(msg) {
|
|
10
|
+
return { content: [{ type: "text", text: msg }] };
|
|
11
|
+
}
|
|
5
12
|
export function registerOpenIMTools(api) {
|
|
6
13
|
if (typeof api.registerTool !== "function")
|
|
7
14
|
return;
|
|
8
|
-
const ensureTargetAndClient = (params) => {
|
|
9
|
-
const target = parseTarget(params.target);
|
|
10
|
-
if (!target) {
|
|
11
|
-
return {
|
|
12
|
-
ok: false,
|
|
13
|
-
result: {
|
|
14
|
-
content: [{ type: "text", text: "Invalid target format. Expected user:<id> or group:<id>." }],
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const client = getConnectedClient(params.accountId);
|
|
19
|
-
if (!client) {
|
|
20
|
-
return {
|
|
21
|
-
ok: false,
|
|
22
|
-
result: {
|
|
23
|
-
content: [{ type: "text", text: "OpenIM is not connected." }],
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return { ok: true, target, client };
|
|
28
|
-
};
|
|
29
|
-
api.registerTool({
|
|
30
|
-
name: "openim_send_text",
|
|
31
|
-
description: "Send a text message via OpenIM. target format: user:ID or group:ID.",
|
|
32
|
-
parameters: {
|
|
33
|
-
type: "object",
|
|
34
|
-
properties: {
|
|
35
|
-
target: { type: "string", description: "user:123 or group:456" },
|
|
36
|
-
text: { type: "string", description: "Text to send" },
|
|
37
|
-
accountId: { type: "string", description: "Optional account ID. Defaults to `default` or the first connected account." },
|
|
38
|
-
},
|
|
39
|
-
required: ["target", "text"],
|
|
40
|
-
},
|
|
41
|
-
async execute(_id, params) {
|
|
42
|
-
const checked = ensureTargetAndClient(params);
|
|
43
|
-
if (!checked.ok)
|
|
44
|
-
return checked.result;
|
|
45
|
-
try {
|
|
46
|
-
await sendTextToTarget(checked.client, checked.target, params.text);
|
|
47
|
-
return { content: [{ type: "text", text: "Sent successfully" }] };
|
|
48
|
-
}
|
|
49
|
-
catch (e) {
|
|
50
|
-
return { content: [{ type: "text", text: `Send failed: ${formatSdkError(e)}` }] };
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
15
|
api.registerTool({
|
|
55
|
-
name: "
|
|
56
|
-
|
|
16
|
+
name: "openim_management",
|
|
17
|
+
label: "OpenIM Management",
|
|
18
|
+
description: "Unified tool for OpenIM operations: send messages (text/image/video/file), " +
|
|
19
|
+
"query group info, and list group members. Use this tool for all OpenIM interactions.",
|
|
57
20
|
parameters: {
|
|
58
21
|
type: "object",
|
|
59
22
|
properties: {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
api.registerTool({
|
|
106
|
-
name: "openim_send_file",
|
|
107
|
-
description: "Send a file via OpenIM. `file` supports a local path or URL; `name` is optional.",
|
|
108
|
-
parameters: {
|
|
109
|
-
type: "object",
|
|
110
|
-
properties: {
|
|
111
|
-
target: { type: "string", description: "user:123 or group:456" },
|
|
112
|
-
file: { type: "string", description: "Local path (`file://` supported) or URL" },
|
|
113
|
-
name: { type: "string", description: "Optional filename (recommended for URL input)" },
|
|
114
|
-
accountId: { type: "string", description: "Optional account ID" },
|
|
23
|
+
action: {
|
|
24
|
+
type: "string",
|
|
25
|
+
enum: [
|
|
26
|
+
"send-text",
|
|
27
|
+
"send-image",
|
|
28
|
+
"send-video",
|
|
29
|
+
"send-file",
|
|
30
|
+
"group-info",
|
|
31
|
+
"group-members",
|
|
32
|
+
],
|
|
33
|
+
description: "The action to perform.",
|
|
34
|
+
},
|
|
35
|
+
target: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Target for send actions. Format: user:<id> or group:<id>.",
|
|
38
|
+
},
|
|
39
|
+
text: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Text content. Required for send-text.",
|
|
42
|
+
},
|
|
43
|
+
image: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Image local path or URL. Required for send-image.",
|
|
46
|
+
},
|
|
47
|
+
video: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Video local path or URL. Required for send-video.",
|
|
50
|
+
},
|
|
51
|
+
file: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "File local path or URL. Required for send-file.",
|
|
54
|
+
},
|
|
55
|
+
name: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "Optional filename for send-video / send-file.",
|
|
58
|
+
},
|
|
59
|
+
groupID: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Group ID. Required for group-info and group-members.",
|
|
62
|
+
},
|
|
63
|
+
accountId: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Optional OpenIM account ID. Defaults to the first connected account.",
|
|
66
|
+
},
|
|
115
67
|
},
|
|
116
|
-
required: ["
|
|
68
|
+
required: ["action"],
|
|
117
69
|
},
|
|
118
70
|
async execute(_id, params) {
|
|
119
|
-
const
|
|
120
|
-
if (!
|
|
121
|
-
return
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
71
|
+
const client = getConnectedClient(params.accountId);
|
|
72
|
+
if (!client)
|
|
73
|
+
return err("OpenIM is not connected.");
|
|
74
|
+
switch (params.action) {
|
|
75
|
+
// ── Send actions ────────────────────────────────────────────
|
|
76
|
+
case "send-text": {
|
|
77
|
+
if (!params.target || !params.text)
|
|
78
|
+
return err("Missing required: target, text");
|
|
79
|
+
const target = parseTarget(params.target);
|
|
80
|
+
if (!target)
|
|
81
|
+
return err("Invalid target format. Expected user:<id> or group:<id>.");
|
|
82
|
+
try {
|
|
83
|
+
await sendTextToTarget(client, target, params.text);
|
|
84
|
+
return ok("Sent successfully");
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
return err(`Send failed: ${formatSdkError(e)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
case "send-image": {
|
|
91
|
+
if (!params.target || !params.image)
|
|
92
|
+
return err("Missing required: target, image");
|
|
93
|
+
const target = parseTarget(params.target);
|
|
94
|
+
if (!target)
|
|
95
|
+
return err("Invalid target format. Expected user:<id> or group:<id>.");
|
|
96
|
+
try {
|
|
97
|
+
await sendImageToTarget(client, target, params.image);
|
|
98
|
+
return ok("Image sent successfully");
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return err(`Send failed: ${formatSdkError(e)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
case "send-video": {
|
|
105
|
+
if (!params.target || !params.video)
|
|
106
|
+
return err("Missing required: target, video");
|
|
107
|
+
const target = parseTarget(params.target);
|
|
108
|
+
if (!target)
|
|
109
|
+
return err("Invalid target format. Expected user:<id> or group:<id>.");
|
|
110
|
+
try {
|
|
111
|
+
await sendVideoToTarget(client, target, params.video, params.name);
|
|
112
|
+
return ok("Video sent successfully");
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
return err(`Send failed: ${formatSdkError(e)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
case "send-file": {
|
|
119
|
+
if (!params.target || !params.file)
|
|
120
|
+
return err("Missing required: target, file");
|
|
121
|
+
const target = parseTarget(params.target);
|
|
122
|
+
if (!target)
|
|
123
|
+
return err("Invalid target format. Expected user:<id> or group:<id>.");
|
|
124
|
+
try {
|
|
125
|
+
await sendFileToTarget(client, target, params.file, params.name);
|
|
126
|
+
return ok("File sent successfully");
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return err(`Send failed: ${formatSdkError(e)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// ── Group query actions ─────────────────────────────────────
|
|
133
|
+
case "group-info": {
|
|
134
|
+
if (!params.groupID)
|
|
135
|
+
return err("Missing required: groupID");
|
|
136
|
+
try {
|
|
137
|
+
const info = await fetchGroupInfo(client, params.groupID, undefined, true);
|
|
138
|
+
if (!info)
|
|
139
|
+
return err("Group not found or fetch failed.");
|
|
140
|
+
const parts = [
|
|
141
|
+
`Group: ${info.groupName}`,
|
|
142
|
+
`Members: ${info.memberCount}`,
|
|
143
|
+
info.notification ? `Announcement: ${info.notification}` : null,
|
|
144
|
+
info.introduction ? `Introduction: ${info.introduction}` : null,
|
|
145
|
+
].filter(Boolean);
|
|
146
|
+
return ok(parts.join("\n"));
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
return err(`Failed: ${formatSdkError(e)}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
case "group-members": {
|
|
153
|
+
if (!params.groupID)
|
|
154
|
+
return err("Missing required: groupID");
|
|
155
|
+
try {
|
|
156
|
+
const data = await fetchGroupMembers(client, params.groupID, undefined, true);
|
|
157
|
+
if (!data || data.uidToName.size === 0)
|
|
158
|
+
return err("No members found or fetch failed.");
|
|
159
|
+
const lines = Array.from(data.uidToName.entries())
|
|
160
|
+
.map(([uid, name]) => `${name} (${uid})`);
|
|
161
|
+
return ok(`Group members (${lines.length}):\n${lines.join("\n")}`);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return err(`Failed: ${formatSdkError(e)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
default:
|
|
168
|
+
return err(`Unknown action: ${params.action}`);
|
|
128
169
|
}
|
|
129
170
|
},
|
|
130
171
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -10,9 +10,27 @@ export interface OpenIMAccountConfig {
|
|
|
10
10
|
platformID: number;
|
|
11
11
|
requireMention: boolean;
|
|
12
12
|
inboundWhitelist: string[];
|
|
13
|
+
historyLimit: number;
|
|
13
14
|
botId?: string;
|
|
14
15
|
portalWsAddr?: string;
|
|
15
16
|
}
|
|
17
|
+
export interface GroupInfoCacheEntry {
|
|
18
|
+
groupName: string;
|
|
19
|
+
notification: string;
|
|
20
|
+
introduction: string;
|
|
21
|
+
memberCount: number;
|
|
22
|
+
fetchedAt: number;
|
|
23
|
+
}
|
|
24
|
+
export interface GroupMemberCacheEntry {
|
|
25
|
+
uidToName: Map<string, string>;
|
|
26
|
+
fetchedAt: number;
|
|
27
|
+
}
|
|
28
|
+
export interface HistoryEntry {
|
|
29
|
+
sender: string;
|
|
30
|
+
senderName: string;
|
|
31
|
+
body: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
16
34
|
export interface OpenIMClientState {
|
|
17
35
|
sdk: ApiService;
|
|
18
36
|
config: OpenIMAccountConfig;
|
|
@@ -39,7 +57,51 @@ export interface InboundBodyResult {
|
|
|
39
57
|
kind: "text" | "image" | "video" | "file" | "mixed" | "unknown";
|
|
40
58
|
media?: InboundMediaItem[];
|
|
41
59
|
}
|
|
42
|
-
export type PortalMethod = "bot.agent.get" | "models.list" | "agents.list" | "agents.files.list" | "agents.files.get" | "agents.files.set" | "agents.create" | "ping";
|
|
60
|
+
export type PortalMethod = "bot.agent.get" | "models.list" | "agents.list" | "agents.files.list" | "agents.files.get" | "agents.files.set" | "agents.create" | "agent.skills.status" | "agent.skills.set" | "agent.model.set" | "tools.catalog" | "skills.status" | "skills.search" | "skills.set" | "skills.install" | "cron.list" | "ping";
|
|
61
|
+
export type CronJobSchedule = {
|
|
62
|
+
kind: "at";
|
|
63
|
+
at: string;
|
|
64
|
+
} | {
|
|
65
|
+
kind: "every";
|
|
66
|
+
everyMs: number;
|
|
67
|
+
anchorMs?: number;
|
|
68
|
+
} | {
|
|
69
|
+
kind: "cron";
|
|
70
|
+
expr: string;
|
|
71
|
+
tz?: string;
|
|
72
|
+
staggerMs?: number;
|
|
73
|
+
};
|
|
74
|
+
export type CronJobState = {
|
|
75
|
+
nextRunAtMs?: number;
|
|
76
|
+
runningAtMs?: number;
|
|
77
|
+
lastRunAtMs?: number;
|
|
78
|
+
lastRunStatus?: "ok" | "error" | "skipped";
|
|
79
|
+
lastError?: string;
|
|
80
|
+
lastDurationMs?: number;
|
|
81
|
+
consecutiveErrors?: number;
|
|
82
|
+
lastDelivered?: boolean;
|
|
83
|
+
lastDeliveryStatus?: "delivered" | "not-delivered" | "unknown" | "not-requested";
|
|
84
|
+
lastDeliveryError?: string;
|
|
85
|
+
lastFailureAlertAtMs?: number;
|
|
86
|
+
};
|
|
87
|
+
export interface CronJobEntry {
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
agentId?: string;
|
|
92
|
+
sessionKey?: string;
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
deleteAfterRun?: boolean;
|
|
95
|
+
createdAtMs: number;
|
|
96
|
+
updatedAtMs: number;
|
|
97
|
+
schedule: CronJobSchedule;
|
|
98
|
+
sessionTarget: string;
|
|
99
|
+
wakeMode: "next-heartbeat" | "now";
|
|
100
|
+
payload: Record<string, unknown>;
|
|
101
|
+
delivery?: Record<string, unknown>;
|
|
102
|
+
failureAlert?: false | Record<string, unknown>;
|
|
103
|
+
state: CronJobState;
|
|
104
|
+
}
|
|
43
105
|
export interface PortalRequest {
|
|
44
106
|
id: string;
|
|
45
107
|
method: PortalMethod;
|
package/openclaw.plugin.json
CHANGED