@kirigaya/openclaw-onebot 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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/dist/channel.d.ts +100 -0
  4. package/dist/channel.js +173 -0
  5. package/dist/config.d.ts +6 -0
  6. package/dist/config.js +62 -0
  7. package/dist/connection.d.ts +94 -0
  8. package/dist/connection.js +426 -0
  9. package/dist/debug-log.d.ts +7 -0
  10. package/dist/debug-log.js +24 -0
  11. package/dist/gateway-proxy.d.ts +8 -0
  12. package/dist/gateway-proxy.js +36 -0
  13. package/dist/handlers/group-increase.d.ts +21 -0
  14. package/dist/handlers/group-increase.js +95 -0
  15. package/dist/handlers/process-inbound.d.ts +11 -0
  16. package/dist/handlers/process-inbound.js +224 -0
  17. package/dist/index.d.ts +11 -0
  18. package/dist/index.js +33 -0
  19. package/dist/load-script.d.ts +5 -0
  20. package/dist/load-script.js +22 -0
  21. package/dist/message.d.ts +6 -0
  22. package/dist/message.js +24 -0
  23. package/dist/reply-context.d.ts +12 -0
  24. package/dist/reply-context.js +33 -0
  25. package/dist/scheduler.d.ts +19 -0
  26. package/dist/scheduler.js +70 -0
  27. package/dist/sdk.d.ts +9 -0
  28. package/dist/sdk.js +36 -0
  29. package/dist/send.d.ts +23 -0
  30. package/dist/send.js +98 -0
  31. package/dist/service.d.ts +4 -0
  32. package/dist/service.js +71 -0
  33. package/dist/setup.d.ts +1 -0
  34. package/dist/setup.js +65 -0
  35. package/dist/tools.d.ts +18 -0
  36. package/dist/tools.js +188 -0
  37. package/dist/types.d.ts +28 -0
  38. package/dist/types.js +4 -0
  39. package/openclaw.plugin.json +72 -0
  40. package/package.json +74 -0
  41. package/skills/onebot-ops/SKILL.md +61 -0
  42. package/skills/onebot-ops/config.md +55 -0
  43. package/skills/onebot-ops/receive.md +85 -0
  44. package/skills/onebot-ops/send.md +39 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * OneBot WebSocket 服务
3
+ */
4
+ import { getOneBotConfig } from "./config.js";
5
+ import { connectForward, createServerAndWait, setWs, stopConnection, handleEchoResponse, startImageTempCleanup, stopImageTempCleanup } from "./connection.js";
6
+ import { processInboundMessage } from "./handlers/process-inbound.js";
7
+ import { handleGroupIncrease } from "./handlers/group-increase.js";
8
+ import { startScheduler, stopScheduler } from "./scheduler.js";
9
+ export function registerService(api) {
10
+ api.registerService({
11
+ id: "onebot-ws",
12
+ start: async () => {
13
+ const config = getOneBotConfig(api);
14
+ if (!config) {
15
+ api.logger?.warn?.("[onebot] no config, service will not connect");
16
+ return;
17
+ }
18
+ try {
19
+ let ws;
20
+ if (config.type === "forward-websocket") {
21
+ ws = await connectForward(config);
22
+ }
23
+ else {
24
+ ws = await createServerAndWait(config);
25
+ }
26
+ setWs(ws);
27
+ api.logger?.info?.("[onebot] WebSocket connected");
28
+ startImageTempCleanup();
29
+ startScheduler(api);
30
+ ws.on("message", (data) => {
31
+ try {
32
+ const payload = JSON.parse(data.toString());
33
+ if (handleEchoResponse(payload))
34
+ return;
35
+ if (payload.meta_event_type === "heartbeat")
36
+ return;
37
+ const msg = payload;
38
+ if (msg.post_type === "message" && (msg.message_type === "private" || msg.message_type === "group")) {
39
+ processInboundMessage(api, msg).catch((e) => {
40
+ api.logger?.error?.(`[onebot] processInboundMessage: ${e?.message}`);
41
+ });
42
+ }
43
+ else if (msg.post_type === "notice" && msg.notice_type === "group_increase") {
44
+ handleGroupIncrease(api, msg).catch((e) => {
45
+ api.logger?.error?.(`[onebot] handleGroupIncrease: ${e?.message}`);
46
+ });
47
+ }
48
+ }
49
+ catch (e) {
50
+ api.logger?.error?.(`[onebot] parse message: ${e?.message}`);
51
+ }
52
+ });
53
+ ws.on("close", () => {
54
+ api.logger?.info?.("[onebot] WebSocket closed");
55
+ });
56
+ ws.on("error", (e) => {
57
+ api.logger?.error?.(`[onebot] WebSocket error: ${e?.message}`);
58
+ });
59
+ }
60
+ catch (e) {
61
+ api.logger?.error?.(`[onebot] start failed: ${e?.message}`);
62
+ }
63
+ },
64
+ stop: async () => {
65
+ stopImageTempCleanup();
66
+ stopScheduler();
67
+ stopConnection();
68
+ api.logger?.info?.("[onebot] service stopped");
69
+ },
70
+ });
71
+ }
@@ -0,0 +1 @@
1
+ export declare function runOneBotSetup(): Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * OneBot TUI 配置向导
3
+ * openclaw onebot setup
4
+ */
5
+ import { cancel as clackCancel, isCancel, note as clackNote, outro as clackOutro, select as clackSelect, text as clackText, } from "@clack/prompts";
6
+ import { existsSync, readFileSync, writeFileSync } from "fs";
7
+ import { homedir } from "os";
8
+ import { join } from "path";
9
+ const OPENCLAW_HOME = join(homedir(), ".openclaw");
10
+ const CONFIG_PATH = join(OPENCLAW_HOME, "openclaw.json");
11
+ function guardCancel(v) {
12
+ if (isCancel(v)) {
13
+ clackCancel("已取消。");
14
+ process.exit(0);
15
+ }
16
+ return v;
17
+ }
18
+ export async function runOneBotSetup() {
19
+ const type = guardCancel(await clackSelect({
20
+ message: "连接类型",
21
+ options: [
22
+ { value: "forward-websocket", label: "forward-websocket(正向,主动连接 OneBot)" },
23
+ { value: "backward-websocket", label: "backward-websocket(反向,OneBot 连接本机)" },
24
+ ],
25
+ initialValue: process.env.LAGRANGE_WS_TYPE === "backward-websocket" ? "backward-websocket" : "forward-websocket",
26
+ }));
27
+ const host = guardCancel(await clackText({
28
+ message: "主机地址",
29
+ initialValue: process.env.LAGRANGE_WS_HOST || "127.0.0.1",
30
+ }));
31
+ const portStr = guardCancel(await clackText({
32
+ message: "端口",
33
+ initialValue: process.env.LAGRANGE_WS_PORT || "3001",
34
+ }));
35
+ const accessToken = guardCancel(await clackText({
36
+ message: "Access Token(可选,留空回车跳过)",
37
+ initialValue: process.env.LAGRANGE_WS_ACCESS_TOKEN || "",
38
+ }));
39
+ const port = parseInt(String(portStr).trim(), 10);
40
+ if (!Number.isFinite(port)) {
41
+ console.error("端口必须为数字");
42
+ process.exit(1);
43
+ }
44
+ let existing = {};
45
+ if (existsSync(CONFIG_PATH)) {
46
+ try {
47
+ existing = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
48
+ }
49
+ catch { }
50
+ }
51
+ const channels = existing.channels || {};
52
+ channels.onebot = {
53
+ ...(channels.onebot || {}),
54
+ type,
55
+ host: String(host).trim(),
56
+ port,
57
+ ...(accessToken?.trim() ? { accessToken: String(accessToken).trim() } : {}),
58
+ enabled: true,
59
+ requireMention: true,
60
+ };
61
+ const next = { ...existing, channels };
62
+ writeFileSync(CONFIG_PATH, JSON.stringify(next, null, 2), "utf-8");
63
+ clackNote(`配置已保存到 ${CONFIG_PATH}`, "完成");
64
+ clackOutro("运行 openclaw gateway restart 使配置生效");
65
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Agent 工具注册
3
+ * 供 OpenClaw cron 等场景下,AI 调用 OneBot 能力
4
+ */
5
+ import { sendPrivateMsg, sendGroupMsg, sendGroupImage, sendPrivateImage, getGroupMsgHistory, getGroupInfo, getStrangerInfo, getGroupMemberInfo, getAvatarUrl } from "./connection.js";
6
+ export interface OneBotClient {
7
+ sendGroupMsg: typeof sendGroupMsg;
8
+ sendGroupImage: typeof sendGroupImage;
9
+ sendPrivateMsg: typeof sendPrivateMsg;
10
+ sendPrivateImage: typeof sendPrivateImage;
11
+ getGroupMsgHistory: typeof getGroupMsgHistory;
12
+ getGroupInfo: typeof getGroupInfo;
13
+ getStrangerInfo: typeof getStrangerInfo;
14
+ getGroupMemberInfo: typeof getGroupMemberInfo;
15
+ getAvatarUrl: typeof getAvatarUrl;
16
+ }
17
+ export declare const onebotClient: OneBotClient;
18
+ export declare function registerTools(api: any): void;
package/dist/tools.js ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Agent 工具注册
3
+ * 供 OpenClaw cron 等场景下,AI 调用 OneBot 能力
4
+ */
5
+ import WebSocket from "ws";
6
+ import { loadScript } from "./load-script.js";
7
+ import { getWs, sendPrivateMsg, sendGroupMsg, sendGroupImage, sendPrivateImage, uploadGroupFile, uploadPrivateFile, getGroupMsgHistory, getGroupInfo, getStrangerInfo, getGroupMemberInfo, getAvatarUrl, } from "./connection.js";
8
+ export const onebotClient = {
9
+ sendGroupMsg,
10
+ sendGroupImage,
11
+ sendPrivateMsg,
12
+ sendPrivateImage,
13
+ getGroupMsgHistory,
14
+ getGroupInfo,
15
+ getStrangerInfo,
16
+ getGroupMemberInfo,
17
+ getAvatarUrl,
18
+ };
19
+ export function registerTools(api) {
20
+ if (typeof api.registerTool !== "function")
21
+ return;
22
+ api.registerTool({
23
+ name: "onebot_send_text",
24
+ description: "通过 OneBot 发送文本消息。target 格式:user:QQ号 或 group:群号",
25
+ parameters: {
26
+ type: "object",
27
+ properties: {
28
+ target: { type: "string", description: "user:123456 或 group:789012" },
29
+ text: { type: "string", description: "要发送的文本" },
30
+ },
31
+ required: ["target", "text"],
32
+ },
33
+ async execute(_id, params) {
34
+ const w = getWs();
35
+ if (!w || w.readyState !== WebSocket.OPEN) {
36
+ return { content: [{ type: "text", text: "OneBot 未连接" }] };
37
+ }
38
+ const t = params.target.replace(/^onebot:/i, "");
39
+ try {
40
+ if (t.startsWith("group:")) {
41
+ await sendGroupMsg(parseInt(t.slice(6), 10), params.text);
42
+ }
43
+ else {
44
+ const id = parseInt(t.replace(/^user:/, ""), 10);
45
+ await sendPrivateMsg(id, params.text);
46
+ }
47
+ return { content: [{ type: "text", text: "发送成功" }] };
48
+ }
49
+ catch (e) {
50
+ return { content: [{ type: "text", text: `发送失败: ${e?.message}` }] };
51
+ }
52
+ },
53
+ });
54
+ api.registerTool({
55
+ name: "onebot_send_image",
56
+ description: "通过 OneBot 发送图片。target 格式:user:QQ号 或 group:群号。image 为本地路径(file://)或 URL 或 base64://",
57
+ parameters: {
58
+ type: "object",
59
+ properties: {
60
+ target: { type: "string" },
61
+ image: { type: "string", description: "图片路径或 URL" },
62
+ },
63
+ required: ["target", "image"],
64
+ },
65
+ async execute(_id, params) {
66
+ const w = getWs();
67
+ if (!w || w.readyState !== WebSocket.OPEN) {
68
+ return { content: [{ type: "text", text: "OneBot 未连接" }] };
69
+ }
70
+ const t = params.target.replace(/^onebot:/i, "");
71
+ try {
72
+ if (t.startsWith("group:")) {
73
+ await sendGroupImage(parseInt(t.slice(6), 10), params.image);
74
+ }
75
+ else {
76
+ await sendPrivateImage(parseInt(t.replace(/^user:/, ""), 10), params.image);
77
+ }
78
+ return { content: [{ type: "text", text: "图片发送成功" }] };
79
+ }
80
+ catch (e) {
81
+ return { content: [{ type: "text", text: `发送失败: ${e?.message}` }] };
82
+ }
83
+ },
84
+ });
85
+ api.registerTool({
86
+ name: "onebot_upload_file",
87
+ description: "通过 OneBot 上传文件到群或私聊。target: user:QQ号 或 group:群号。file 为本地绝对路径,name 为显示文件名",
88
+ parameters: {
89
+ type: "object",
90
+ properties: {
91
+ target: { type: "string" },
92
+ file: { type: "string" },
93
+ name: { type: "string" },
94
+ },
95
+ required: ["target", "file", "name"],
96
+ },
97
+ async execute(_id, params) {
98
+ const w = getWs();
99
+ if (!w || w.readyState !== WebSocket.OPEN) {
100
+ return { content: [{ type: "text", text: "OneBot 未连接" }] };
101
+ }
102
+ const t = params.target.replace(/^onebot:/i, "");
103
+ try {
104
+ if (t.startsWith("group:")) {
105
+ await uploadGroupFile(parseInt(t.slice(6), 10), params.file, params.name);
106
+ }
107
+ else {
108
+ await uploadPrivateFile(parseInt(t.replace(/^user:/, ""), 10), params.file, params.name);
109
+ }
110
+ return { content: [{ type: "text", text: "文件上传成功" }] };
111
+ }
112
+ catch (e) {
113
+ return { content: [{ type: "text", text: `上传失败: ${e?.message}` }] };
114
+ }
115
+ },
116
+ });
117
+ api.registerTool({
118
+ name: "onebot_get_group_msg_history",
119
+ description: "获取群聊历史消息(需 Lagrange.Core,go-cqhttp 可能不支持)。用于定时总结、日报等场景",
120
+ parameters: {
121
+ type: "object",
122
+ properties: {
123
+ group_id: { type: "number", description: "群号" },
124
+ count: { type: "number", description: "获取条数,默认 50" },
125
+ message_seq: { type: "number", description: "可选,起始消息序号" },
126
+ message_id: { type: "number", description: "可选,起始消息 ID" },
127
+ },
128
+ required: ["group_id"],
129
+ },
130
+ async execute(_id, params) {
131
+ const w = getWs();
132
+ if (!w || w.readyState !== WebSocket.OPEN) {
133
+ return { content: [{ type: "text", text: "OneBot 未连接" }] };
134
+ }
135
+ try {
136
+ const msgs = await getGroupMsgHistory(params.group_id, {
137
+ count: params.count ?? 50,
138
+ message_seq: params.message_seq,
139
+ message_id: params.message_id,
140
+ });
141
+ const summary = msgs.map((m) => {
142
+ const text = typeof m.message === "string" ? m.message : JSON.stringify(m.message);
143
+ const nick = m.sender?.nickname ?? m.sender?.user_id ?? "?";
144
+ return `[${new Date(m.time * 1000).toISOString()}] ${nick}: ${text.slice(0, 200)}`;
145
+ });
146
+ return { content: [{ type: "text", text: summary.join("\n") || "无历史消息" }] };
147
+ }
148
+ catch (e) {
149
+ return { content: [{ type: "text", text: `获取失败: ${e?.message}` }] };
150
+ }
151
+ },
152
+ });
153
+ api.registerTool({
154
+ name: "onebot_run_script",
155
+ description: "执行用户配置的 JS/TS 脚本(.mjs/.ts/.mts),脚本可调用 OneBot API(获取群历史、发图等)。用于定时任务中实现自定义逻辑(如 OG 图片生成、日报汇总)",
156
+ parameters: {
157
+ type: "object",
158
+ properties: {
159
+ scriptPath: { type: "string", description: "脚本路径,相对 process.cwd() 或绝对路径,支持 .mjs/.ts/.mts,如 ./daily-summary.mjs 或 ./daily-summary.ts" },
160
+ groupIds: { type: "array", items: { type: "number" }, description: "要处理的群号列表" },
161
+ },
162
+ required: ["scriptPath"],
163
+ },
164
+ async execute(_id, params) {
165
+ const w = getWs();
166
+ if (!w || w.readyState !== WebSocket.OPEN) {
167
+ return { content: [{ type: "text", text: "OneBot 未连接" }] };
168
+ }
169
+ try {
170
+ const mod = await loadScript(params.scriptPath);
171
+ const fn = mod?.default ?? mod?.run ?? mod?.execute;
172
+ if (typeof fn !== "function") {
173
+ return { content: [{ type: "text", text: `脚本未导出 default/run/execute 函数` }] };
174
+ }
175
+ const ctx = {
176
+ onebot: onebotClient,
177
+ groupIds: params.groupIds ?? [],
178
+ };
179
+ const result = await fn(ctx);
180
+ const out = result != null ? String(result) : "执行完成";
181
+ return { content: [{ type: "text", text: out }] };
182
+ }
183
+ catch (e) {
184
+ return { content: [{ type: "text", text: `脚本执行失败: ${e?.message}` }] };
185
+ }
186
+ },
187
+ });
188
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * OneBot 协议类型定义
3
+ */
4
+ export interface OneBotMessage {
5
+ post_type: string;
6
+ message_type?: "private" | "group";
7
+ message_id?: number;
8
+ user_id?: number;
9
+ group_id?: number;
10
+ message?: Array<{
11
+ type: string;
12
+ data?: Record<string, unknown>;
13
+ }>;
14
+ raw_message?: string;
15
+ self_id?: number;
16
+ time?: number;
17
+ notice_type?: string;
18
+ [key: string]: unknown;
19
+ }
20
+ export interface OneBotAccountConfig {
21
+ accountId?: string;
22
+ type: "forward-websocket" | "backward-websocket";
23
+ host: string;
24
+ port: number;
25
+ accessToken?: string;
26
+ path?: string;
27
+ enabled?: boolean;
28
+ }
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * OneBot 协议类型定义
3
+ */
4
+ export {};
@@ -0,0 +1,72 @@
1
+ {
2
+ "id": "@kirigaya/openclaw-onebot",
3
+ "name": "OneBot Channel",
4
+ "version": "1.0.0",
5
+ "description": "OneBot v11 protocol channel (QQ/Lagrange.Core via WebSocket)",
6
+ "author": "Lagrange.Onebot",
7
+ "channels": ["onebot"],
8
+ "configSchema": {
9
+ "type": "object",
10
+ "additionalProperties": false,
11
+ "properties": {
12
+ "type": {
13
+ "type": "string",
14
+ "enum": ["forward-websocket", "backward-websocket"],
15
+ "description": "连接类型:正向或反向 WebSocket"
16
+ },
17
+ "host": { "type": "string", "description": "OneBot 主机地址" },
18
+ "port": { "type": "number", "description": "OneBot 端口" },
19
+ "accessToken": { "type": "string", "description": "访问令牌(可选)" },
20
+ "path": { "type": "string", "description": "WS 路径,默认 /onebot/v11/ws" },
21
+ "requireMention": {
22
+ "type": "boolean",
23
+ "default": true,
24
+ "description": "群聊是否必须 @ 机器人才回复"
25
+ },
26
+ "groupIncrease": {
27
+ "type": "object",
28
+ "properties": {
29
+ "enabled": { "type": "boolean", "default": false },
30
+ "message": {
31
+ "type": "string",
32
+ "description": "欢迎语模板。占位符:{name} 新成员昵称,{userId} QQ号,{groupName} 群名,{groupId} 群号,{avatarUrl} 头像链接"
33
+ },
34
+ "handler": {
35
+ "type": "string",
36
+ "description": "自定义脚本路径(.mjs/.ts/.mts),接收 ctx,返回 { text?, imagePath?, imageUrl? }。TS 需安装 tsx。优先级高于 message"
37
+ }
38
+ }
39
+ },
40
+ "thinkingEmojiId": {
41
+ "type": "number",
42
+ "default": 60,
43
+ "description": "表情 ID(Lagrange/QQ NT set_msg_emoji_like),收到消息时添加,回复完成后取消"
44
+ },
45
+ "cronJobs": {
46
+ "type": "array",
47
+ "description": "内置定时任务(无 AI 介入,直接执行脚本并推送到群聊)",
48
+ "items": {
49
+ "type": "object",
50
+ "required": ["name", "cron", "script", "groupIds"],
51
+ "properties": {
52
+ "name": { "type": "string", "description": "任务名称" },
53
+ "cron": { "type": "string", "description": "cron 表达式,如 0 8 * * * 表示每天 8:00" },
54
+ "timezone": { "type": "string", "default": "Asia/Shanghai", "description": "时区" },
55
+ "script": { "type": "string", "description": "脚本路径,相对 process.cwd(),如 ./Tiphareth/src/openclaw/cron/daily-news.ts" },
56
+ "groupIds": {
57
+ "type": "array",
58
+ "items": { "type": "number" },
59
+ "description": "要推送的群号列表"
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ },
66
+ "uiHints": {
67
+ "accessToken": { "label": "Access Token", "sensitive": true },
68
+ "host": { "label": "主机地址", "placeholder": "127.0.0.1" },
69
+ "port": { "label": "端口", "placeholder": "8080" },
70
+ "requireMention": { "label": "群聊需 @ 回复" }
71
+ }
72
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@kirigaya/openclaw-onebot",
3
+ "version": "1.0.0",
4
+ "description": "OneBot v11 protocol channel plugin for OpenClaw (QQ/Lagrange.Core/go-cqhttp)",
5
+ "license": "MIT",
6
+ "publishConfig": { "access": "public" },
7
+ "author": "LSTM-Kirigaya",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/LSTM-Kirigaya/openclaw-onebot.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/LSTM-Kirigaya/openclaw-onebot/issues"
14
+ },
15
+ "homepage": "https://github.com/LSTM-Kirigaya/openclaw-onebot#readme",
16
+ "keywords": [
17
+ "openclaw",
18
+ "onebot",
19
+ "qq",
20
+ "lagrange",
21
+ "cqhttp",
22
+ "chatbot"
23
+ ],
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "exports": "./dist/index.js",
27
+ "files": [
28
+ "dist",
29
+ "skills",
30
+ "openclaw.plugin.json",
31
+ "README.md"
32
+ ],
33
+ "openclaw": {
34
+ "extensions": ["./dist/index.js"],
35
+ "skills": ["skills/onebot-ops", "skills/onebot-scripts"],
36
+ "channel": {
37
+ "id": "onebot",
38
+ "label": "OneBot",
39
+ "selectionLabel": "OneBot (QQ/Lagrange)",
40
+ "docsPath": "/channels/onebot",
41
+ "blurb": "OneBot v11 protocol via WebSocket (go-cqhttp, Lagrange.Core, etc.)",
42
+ "order": 85,
43
+ "aliases": ["qq", "lagrange", "cqhttp"]
44
+ }
45
+ },
46
+ "scripts": {
47
+ "build": "tsc",
48
+ "test:connect": "npx tsx scripts/test-connect.ts",
49
+ "prepublishOnly": "npm run build",
50
+ "pub": "npm run build && npm publish"
51
+ },
52
+ "dependencies": {
53
+ "ws": "^8.17.0",
54
+ "@clack/prompts": "^1.0.0",
55
+ "tsx": "^4.0.0",
56
+ "cron": "^4.4.0"
57
+ },
58
+ "peerDependencies": {
59
+ "openclaw": "*",
60
+ "clawdbot": "*"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "clawdbot": { "optional": true },
64
+ "openclaw": { "optional": true }
65
+ },
66
+ "devDependencies": {
67
+ "typescript": "^5.4.0",
68
+ "@types/node": "^22.0.0",
69
+ "@types/ws": "^8.5.10"
70
+ },
71
+ "engines": {
72
+ "node": ">=22"
73
+ }
74
+ }
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: onebot-ops
3
+ description: OneBot (QQ/Lagrange) 渠道运维与使用规范。消息收发通过 Channel outbound 与 deliver 完成,不依赖 Agent 工具。
4
+ ---
5
+
6
+ # OneBot 运维/使用规范
7
+
8
+ OneBot v11 协议渠道,支持 QQ/Lagrange.Core/go-cqhttp。消息收发均通过 Channel 的 outbound 与 deliver 完成,**不依赖 Agent 工具**。
9
+
10
+ ## 快速判断
11
+
12
+ | 场景 | 说明 |
13
+ |------|------|
14
+ | **接收消息** | 私聊全回复,群聊默认仅 @ 回复,详见 [receive.md](receive.md) |
15
+ | **message 工具 target** | 群聊必须用 `ctx.To`(格式 `group:群号`),勿用 SenderId |
16
+ | **发送消息** | 使用 `openclaw message send` CLI,详见 [send.md](send.md) |
17
+ | **配置** | 运行 `openclaw onebot setup` 或编辑 `openclaw.json`,详见 [config.md](config.md) |
18
+
19
+ ## 插件安装
20
+
21
+ ```bash
22
+ openclaw plugins install @openclaw/onebot
23
+ ```
24
+
25
+ 本地开发时:
26
+
27
+ ```bash
28
+ openclaw plugins install ./openclaw-onebot
29
+ ```
30
+
31
+ ## 前置条件
32
+
33
+ - Gateway 已启动:`openclaw gateway`
34
+ - OneBot 实现(Lagrange.Core / go-cqhttp)已运行并暴露 WebSocket
35
+ - 在 `openclaw.json` 中配置 `channels.onebot` 或通过 `LAGRANGE_WS_*` 环境变量
36
+
37
+ ## OneBot 协议能力
38
+
39
+ 对应 Lagrange.onebot `context.ts` 的 API:
40
+
41
+ | 能力 | 说明 |
42
+ |------|------|
43
+ | **send_private_msg** | 发送私聊消息 |
44
+ | **send_group_msg** | 发送群消息 |
45
+ | **send_msg** | 按 message_type 发送 |
46
+ | **图片消息** | message 为 `[{ type: "image", data: { file } }]` |
47
+ | **delete_msg** | 撤回消息 |
48
+ | **get_msg** | 获取单条消息 |
49
+ | **get_group_msg_history** | 获取群历史(Lagrange.Core 扩展) |
50
+ | **upload_group_file** | 上传群文件 |
51
+ | **upload_private_file** | 上传私聊文件 |
52
+ | **set_msg_emoji_like** | 表情回应(Lagrange/QQ NT 扩展) |
53
+
54
+ ## 常用命令
55
+
56
+ | 命令 | 说明 |
57
+ |------|------|
58
+ | `openclaw onebot setup` | 交互式配置 OneBot 连接 |
59
+ | `openclaw message send --channel onebot --target group:xxx --message "hi"` | 发送群消息 |
60
+ | `openclaw gateway status` | 查看 Gateway 状态 |
61
+ | `openclaw logs --follow` | 查看日志 |
@@ -0,0 +1,55 @@
1
+ # 配置参数
2
+
3
+ ## 参数表
4
+
5
+ | 参数 | 说明 |
6
+ |------|------|
7
+ | type | `forward-websocket` / `backward-websocket` |
8
+ | host | OneBot 主机地址 |
9
+ | port | 端口 |
10
+ | accessToken | 访问令牌(可选) |
11
+ | path | 反向 WS 路径,默认 `/onebot/v11/ws` |
12
+ | requireMention | 群聊是否需 @ 才回复,默认 `true` |
13
+ | thinkingEmojiId | 表情 ID(set_msg_emoji_like),默认 60 |
14
+ | groupIncrease | 新成员入群欢迎,详见 [receive.md](receive.md) |
15
+ | cronJobs | 内置定时任务(无 AI 介入),详见下方 |
16
+
17
+ ## TUI 配置
18
+
19
+ 运行 `openclaw onebot setup` 进行交互式配置。
20
+
21
+ 配置写入 `openclaw.json` 的 `channels.onebot` 或通过 `LAGRANGE_WS_*` 环境变量提供。
22
+
23
+ ## 环境变量
24
+
25
+ | 变量 | 说明 |
26
+ |------|------|
27
+ | LAGRANGE_WS_TYPE | forward-websocket / backward-websocket |
28
+ | LAGRANGE_WS_HOST | 主机地址 |
29
+ | LAGRANGE_WS_PORT | 端口 |
30
+ | LAGRANGE_WS_ACCESS_TOKEN | 访问令牌 |
31
+ | LAGRANGE_WS_PATH | 反向 WS 路径 |
32
+
33
+ ## cronJobs 配置
34
+
35
+ 在 `openclaw.json` 中配置内置定时任务,直接执行脚本并推送到群,无需 AI 介入:
36
+
37
+ ```json
38
+ {
39
+ "channels": {
40
+ "onebot": {
41
+ "cronJobs": [
42
+ {
43
+ "name": "每日科技新闻",
44
+ "cron": "0 8 * * *",
45
+ "timezone": "Asia/Shanghai",
46
+ "script": "./Tiphareth/src/openclaw/cron/daily-news.ts",
47
+ "groupIds": [782833642, 1046693162]
48
+ }
49
+ ]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ 脚本需导出 `default` / `run` / `execute` 函数,接收 `ctx: { onebot, groupIds }`。