@kirigaya/openclaw-onebot 1.0.3 → 1.0.5

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.
@@ -2,24 +2,32 @@
2
2
  * Markdown 转 HTML(保留格式,含代码高亮)
3
3
  * 用于 OG 图片模式下的 Markdown 渲染
4
4
  */
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
5
8
  import { marked } from "marked";
6
9
  import hljs from "highlight.js";
7
- const HIGHLIGHT_CSS = `
8
- .hljs{display:block;overflow-x:auto;padding:1em;background:#1e1e1e;color:#d4d4d4;border-radius:6px;font-family:Consolas,Monaco,monospace;font-size:13px;line-height:1.5}
9
- .hljs-keyword{color:#569cd6}
10
- .hljs-string{color:#ce9178}
11
- .hljs-number{color:#b5cea8}
12
- .hljs-comment{color:#6a9955}
13
- .hljs-function{color:#dcdcaa}
14
- .hljs-title{color:#4ec9b0}
15
- .hljs-params{color:#9cdcfe}
16
- .hljs-built_in{color:#4ec9b0}
17
- .hljs-class{color:#4ec9b0}
18
- .hljs-variable{color:#9cdcfe}
19
- .hljs-attr{color:#9cdcfe}
20
- .hljs-tag{color:#569cd6}
21
- .hljs-name{color:#569cd6}
22
- .hljs-meta{color:#808080}
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ /** 内置 dust 主题 CSS 文件路径(相对当前模块,包根目录下的 themes/dust.css) */
12
+ function getDustThemePath() {
13
+ return join(__dirname, "..", "themes", "dust.css");
14
+ }
15
+ const HIGHLIGHT_CSS = `
16
+ .hljs{display:block;overflow-x:auto;padding:1em;background:#1e1e1e;color:#d4d4d4;border-radius:6px;font-family:Consolas,Monaco,monospace;font-size:13px;line-height:1.5}
17
+ .hljs-keyword{color:#569cd6}
18
+ .hljs-string{color:#ce9178}
19
+ .hljs-number{color:#b5cea8}
20
+ .hljs-comment{color:#6a9955}
21
+ .hljs-function{color:#dcdcaa}
22
+ .hljs-title{color:#4ec9b0}
23
+ .hljs-params{color:#9cdcfe}
24
+ .hljs-built_in{color:#4ec9b0}
25
+ .hljs-class{color:#4ec9b0}
26
+ .hljs-variable{color:#9cdcfe}
27
+ .hljs-attr{color:#9cdcfe}
28
+ .hljs-tag{color:#569cd6}
29
+ .hljs-name{color:#569cd6}
30
+ .hljs-meta{color:#808080}
23
31
  `;
24
32
  function highlightCode(code, lang) {
25
33
  if (lang && hljs.getLanguage(lang)) {
@@ -42,34 +50,62 @@ marked.use({
42
50
  },
43
51
  },
44
52
  });
45
- const WRAPPER_STYLE = `
46
- <style>
47
- *{margin:0;padding:0;box-sizing:border-box}
48
- body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:15px;line-height:1.6;color:#24292e;background:#fff;padding:24px;max-width:800px}
49
- h1,h2,h3,h4,h5,h6{margin:16px 0 8px;font-weight:600;line-height:1.25}
50
- h1{font-size:1.5em}
51
- h2{font-size:1.3em}
52
- h3{font-size:1.15em}
53
- p{margin:8px 0}
54
- ul,ol{margin:8px 0;padding-left:24px}
55
- li{margin:4px 0}
56
- code{background:#f6f8fa;padding:2px 6px;border-radius:4px;font-size:0.9em;font-family:Consolas,Monaco,monospace}
57
- pre{margin:12px 0;overflow-x:auto}
58
- pre code{background:transparent;padding:0}
59
- blockquote{border-left:4px solid #dfe2e5;padding-left:16px;margin:8px 0;color:#6a737d}
60
- a{color:#0366d6;text-decoration:none}
61
- a:hover{text-decoration:underline}
62
- table{border-collapse:collapse;margin:12px 0}
63
- th,td{border:1px solid #dfe2e5;padding:8px 12px;text-align:left}
64
- th{background:#f6f8fa;font-weight:600}
65
- ${HIGHLIGHT_CSS}
66
- </style>
53
+ const WRAPPER_STYLE = `
54
+ <style>
55
+ *{margin:0;padding:0;box-sizing:border-box}
56
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;font-size:15px;line-height:1.6;color:#24292e;background:#fff;padding:24px;max-width:800px}
57
+ h1,h2,h3,h4,h5,h6{margin:16px 0 8px;font-weight:600;line-height:1.25}
58
+ h1{font-size:1.5em}
59
+ h2{font-size:1.3em}
60
+ h3{font-size:1.15em}
61
+ p{margin:8px 0}
62
+ ul,ol{margin:8px 0;padding-left:24px}
63
+ li{margin:4px 0}
64
+ code{background:#f6f8fa;padding:2px 6px;border-radius:4px;font-size:0.9em;font-family:Consolas,Monaco,monospace}
65
+ pre{margin:12px 0;overflow-x:auto}
66
+ pre code{background:transparent;padding:0}
67
+ blockquote{border-left:4px solid #dfe2e5;padding-left:16px;margin:8px 0;color:#6a737d}
68
+ a{color:#0366d6;text-decoration:none}
69
+ a:hover{text-decoration:underline}
70
+ table{border-collapse:collapse;margin:12px 0}
71
+ th,td{border:1px solid #dfe2e5;padding:8px 12px;text-align:left}
72
+ th{background:#f6f8fa;font-weight:600}
73
+ ${HIGHLIGHT_CSS}
74
+ </style>
67
75
  `;
68
76
  export function markdownToHtml(md) {
69
77
  if (!md || typeof md !== "string")
70
78
  return "";
71
79
  return marked.parse(md, { async: false });
72
80
  }
73
- export function getMarkdownStyles() {
74
- return WRAPPER_STYLE;
81
+ /**
82
+ * 获取用于 OG 图片的完整样式(基础 + 主题)
83
+ * @param theme "default" 无额外样式;"dust" 内置 dust 主题;或 custom 时的 CSS 文件绝对路径
84
+ */
85
+ export function getMarkdownStyles(theme) {
86
+ let extra = "";
87
+ const t = (theme || "default").trim();
88
+ if (t === "dust") {
89
+ const dustPath = getDustThemePath();
90
+ try {
91
+ if (existsSync(dustPath)) {
92
+ const dustCss = readFileSync(dustPath, "utf-8");
93
+ extra = `<style>body{background:var(--background) !important;padding:24px;max-width:800px;}${dustCss}</style>`;
94
+ }
95
+ }
96
+ catch {
97
+ /* ignore */
98
+ }
99
+ }
100
+ else if (t !== "default" && t !== "none" && (t.includes("/") || t.includes("\\"))) {
101
+ try {
102
+ if (existsSync(t)) {
103
+ extra = `<style>${readFileSync(t, "utf-8")}</style>`;
104
+ }
105
+ }
106
+ catch {
107
+ /* ignore */
108
+ }
109
+ }
110
+ return WRAPPER_STYLE + extra;
75
111
  }
@@ -2,4 +2,6 @@
2
2
  * Markdown 转 OG 图片
3
3
  * 依赖可选的 node-html-to-image(需安装:npm install node-html-to-image)
4
4
  */
5
- export declare function markdownToImage(md: string): Promise<string | null>;
5
+ export declare function markdownToImage(md: string, opts?: {
6
+ theme?: string;
7
+ }): Promise<string | null>;
package/dist/og-image.js CHANGED
@@ -7,7 +7,7 @@ import { join } from "path";
7
7
  import { tmpdir } from "os";
8
8
  import { markdownToHtml, getMarkdownStyles } from "./markdown-to-html.js";
9
9
  const OG_TEMP_DIR = join(tmpdir(), "openclaw-onebot-og");
10
- export async function markdownToImage(md) {
10
+ export async function markdownToImage(md, opts) {
11
11
  if (!md?.trim())
12
12
  return null;
13
13
  let nodeHtmlToImage;
@@ -21,8 +21,10 @@ export async function markdownToImage(md) {
21
21
  if (!nodeHtmlToImage)
22
22
  return null;
23
23
  const bodyHtml = markdownToHtml(md);
24
- const styles = getMarkdownStyles();
25
- const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8">${styles}</head><body>${bodyHtml}</body></html>`;
24
+ const styles = getMarkdownStyles(opts?.theme);
25
+ const theme = (opts?.theme || "").trim();
26
+ const wrappedBody = theme === "dust" ? `<div class="markdown">${bodyHtml}</div>` : bodyHtml;
27
+ const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8">${styles}</head><body>${wrappedBody}</body></html>`;
26
28
  mkdirSync(OG_TEMP_DIR, { recursive: true });
27
29
  const outPath = join(OG_TEMP_DIR, `og-${Date.now()}-${Math.random().toString(36).slice(2)}.png`);
28
30
  try {
@@ -31,7 +33,11 @@ export async function markdownToImage(md) {
31
33
  html: fullHtml,
32
34
  type: "png",
33
35
  quality: 90,
34
- puppeteerArgs: { headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"] },
36
+ puppeteerArgs: {
37
+ headless: true,
38
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
39
+ executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
40
+ },
35
41
  });
36
42
  return `file://${outPath.replace(/\\/g, "/")}`;
37
43
  }
package/dist/setup.js CHANGED
@@ -49,6 +49,27 @@ export async function runOneBotSetup() {
49
49
  ],
50
50
  initialValue: "normal",
51
51
  }));
52
+ let ogImageRenderTheme = "default";
53
+ let ogImageRenderThemePath;
54
+ if (longMessageMode === "og_image") {
55
+ const themeChoice = guardCancel(await clackSelect({
56
+ message: "长消息生成图片时的渲染样式:",
57
+ options: [
58
+ { value: "default", label: "default(无额外样式,默认白底黑字)" },
59
+ { value: "dust", label: "dust(内置暖色旧纸质感)" },
60
+ { value: "custom", label: "custom(自定义 CSS 文件路径)" },
61
+ ],
62
+ initialValue: "default",
63
+ }));
64
+ ogImageRenderTheme = themeChoice;
65
+ if (themeChoice === "custom") {
66
+ const customPath = guardCancel(await clackText({
67
+ message: "CSS 文件绝对路径",
68
+ initialValue: "",
69
+ }));
70
+ ogImageRenderThemePath = (customPath || "").trim() || undefined;
71
+ }
72
+ }
52
73
  const longMessageThreshold = guardCancel(await clackText({
53
74
  message: "长消息阈值(字符数,超过则启用上述模式)",
54
75
  initialValue: "300",
@@ -86,6 +107,7 @@ export async function runOneBotSetup() {
86
107
  requireMention: true,
87
108
  renderMarkdownToPlain,
88
109
  longMessageMode,
110
+ ...(longMessageMode === "og_image" ? { ogImageRenderTheme, ...(ogImageRenderThemePath != null ? { ogImageRenderThemePath } : {}) } : {}),
89
111
  longMessageThreshold: Number.isFinite(thresholdNum) ? thresholdNum : 300,
90
112
  ...(whitelistIds.length > 0 ? { whitelistUserIds: whitelistIds } : {}),
91
113
  };
package/dist/tools.d.ts CHANGED
@@ -2,16 +2,18 @@
2
2
  * Agent 工具注册
3
3
  * 供 OpenClaw cron 等场景下,AI 调用 OneBot 能力
4
4
  */
5
- import { sendPrivateMsg, sendGroupMsg, sendGroupImage, sendPrivateImage, getGroupMsgHistory, getGroupInfo, getStrangerInfo, getGroupMemberInfo, getAvatarUrl } from "./connection.js";
5
+ import { sendPrivateMsg, sendGroupMsg, sendGroupImage, sendPrivateImage, getGroupMsgHistory, getGroupMsgHistoryInRange, getGroupInfo, getStrangerInfo, getGroupMemberInfo, searchGroupMemberByName, getAvatarUrl } from "./connection.js";
6
6
  export interface OneBotClient {
7
7
  sendGroupMsg: typeof sendGroupMsg;
8
8
  sendGroupImage: typeof sendGroupImage;
9
9
  sendPrivateMsg: typeof sendPrivateMsg;
10
10
  sendPrivateImage: typeof sendPrivateImage;
11
11
  getGroupMsgHistory: typeof getGroupMsgHistory;
12
+ getGroupMsgHistoryInRange: typeof getGroupMsgHistoryInRange;
12
13
  getGroupInfo: typeof getGroupInfo;
13
14
  getStrangerInfo: typeof getStrangerInfo;
14
15
  getGroupMemberInfo: typeof getGroupMemberInfo;
16
+ searchGroupMemberByName: typeof searchGroupMemberByName;
15
17
  getAvatarUrl: typeof getAvatarUrl;
16
18
  }
17
19
  export declare const onebotClient: OneBotClient;
package/dist/tools.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import WebSocket from "ws";
6
6
  import { loadScript } from "./load-script.js";
7
- import { getWs, sendPrivateMsg, sendGroupMsg, sendGroupImage, sendPrivateImage, uploadGroupFile, uploadPrivateFile, getGroupMsgHistory, getGroupInfo, getStrangerInfo, getGroupMemberInfo, getAvatarUrl, } from "./connection.js";
7
+ import { getWs, sendPrivateMsg, sendGroupMsg, sendGroupImage, sendPrivateImage, uploadGroupFile, uploadPrivateFile, getGroupMsgHistory, getGroupMsgHistoryInRange, getGroupInfo, getStrangerInfo, getGroupMemberInfo, searchGroupMemberByName, getAvatarUrl, } from "./connection.js";
8
8
  import { getRenderMarkdownToPlain } from "./config.js";
9
9
  import { markdownToPlain } from "./markdown.js";
10
10
  export const onebotClient = {
@@ -13,9 +13,11 @@ export const onebotClient = {
13
13
  sendPrivateMsg,
14
14
  sendPrivateImage,
15
15
  getGroupMsgHistory,
16
+ getGroupMsgHistoryInRange,
16
17
  getGroupInfo,
17
18
  getStrangerInfo,
18
19
  getGroupMemberInfo,
20
+ searchGroupMemberByName,
19
21
  getAvatarUrl,
20
22
  };
21
23
  export function registerTools(api) {
@@ -120,14 +122,16 @@ export function registerTools(api) {
120
122
  });
121
123
  api.registerTool({
122
124
  name: "onebot_get_group_msg_history",
123
- description: "获取群聊历史消息(需 Lagrange.Core,go-cqhttp 可能不支持)。用于定时总结、日报等场景",
125
+ description: "获取群聊历史消息。可指定 hours 获取最近 N 小时内消息(分页拉取),不指定则返回单页(始终从旧到新)。需 Lagrange.Core",
124
126
  parameters: {
125
127
  type: "object",
126
128
  properties: {
127
129
  group_id: { type: "number", description: "群号" },
128
- count: { type: "number", description: "获取条数,默认 50" },
129
- message_seq: { type: "number", description: "可选,起始消息序号" },
130
- message_id: { type: "number", description: "可选,起始消息 ID" },
130
+ hours: { type: "number", description: "可选。指定则获取从现在到过去 N 小时内的消息(如 24 即过去 24 小时)" },
131
+ count: { type: "number", description: "单页条数(未指定 hours 时生效),默认 50" },
132
+ message_seq: { type: "number", description: "可选,起始消息序号(分页用,未指定 hours 时生效)" },
133
+ message_id: { type: "number", description: "可选,起始消息 ID(未指定 hours 时生效)" },
134
+ limit: { type: "number", description: "指定 hours 时最多返回条数,默认 3000" },
131
135
  },
132
136
  required: ["group_id"],
133
137
  },
@@ -137,10 +141,26 @@ export function registerTools(api) {
137
141
  return { content: [{ type: "text", text: "OneBot 未连接" }] };
138
142
  }
139
143
  try {
144
+ const hoursNum = params.hours != null ? Number(params.hours) : undefined;
145
+ if (typeof hoursNum === "number" && Number.isFinite(hoursNum) && hoursNum > 0) {
146
+ const startTime = Math.floor(Date.now() / 1000) - hoursNum * 3600;
147
+ const msgs = await getGroupMsgHistoryInRange(params.group_id, {
148
+ startTime,
149
+ limit: params.limit ?? 3000,
150
+ chunkSize: 100,
151
+ });
152
+ const summary = msgs.map((m) => {
153
+ const text = typeof m.message === "string" ? m.message : JSON.stringify(m.message);
154
+ const nick = m.sender?.nickname ?? m.sender?.user_id ?? "?";
155
+ return `[${new Date(m.time * 1000).toISOString()}] ${nick}: ${text.slice(0, 200)}`;
156
+ });
157
+ return { content: [{ type: "text", text: summary.join("\n") || "无历史消息", metadata: { count: msgs.length } }] };
158
+ }
140
159
  const msgs = await getGroupMsgHistory(params.group_id, {
141
160
  count: params.count ?? 50,
142
161
  message_seq: params.message_seq,
143
162
  message_id: params.message_id,
163
+ reverse_order: true,
144
164
  });
145
165
  const summary = msgs.map((m) => {
146
166
  const text = typeof m.message === "string" ? m.message : JSON.stringify(m.message);
@@ -154,6 +174,35 @@ export function registerTools(api) {
154
174
  }
155
175
  },
156
176
  });
177
+ api.registerTool({
178
+ name: "onebot_search_group_member",
179
+ description: "按名字模糊匹配群成员,返回匹配到的 QQ 号与展示名(群名片优先)。用于根据昵称/群名片查 QQ 号",
180
+ parameters: {
181
+ type: "object",
182
+ properties: {
183
+ group_id: { type: "number", description: "群号" },
184
+ name: { type: "string", description: "要搜索的名字(群名片或昵称,支持模糊匹配)" },
185
+ },
186
+ required: ["group_id", "name"],
187
+ },
188
+ async execute(_id, params) {
189
+ const w = getWs();
190
+ if (!w || w.readyState !== WebSocket.OPEN) {
191
+ return { content: [{ type: "text", text: "OneBot 未连接" }] };
192
+ }
193
+ try {
194
+ const list = await searchGroupMemberByName(params.group_id, params.name);
195
+ if (!list.length) {
196
+ return { content: [{ type: "text", text: `未找到匹配「${params.name}」的群成员` }] };
197
+ }
198
+ const lines = list.map((m) => `QQ: ${m.user_id} 展示名: ${m.displayName}`);
199
+ return { content: [{ type: "text", text: lines.join("\n"), metadata: { count: list.length, matches: list } }] };
200
+ }
201
+ catch (e) {
202
+ return { content: [{ type: "text", text: `搜索失败: ${e?.message}` }] };
203
+ }
204
+ },
205
+ });
157
206
  api.registerTool({
158
207
  name: "onebot_run_script",
159
208
  description: "执行用户配置的 JS/TS 脚本(.mjs/.ts/.mts),脚本可调用 OneBot API(获取群历史、发图等)。用于定时任务中实现自定义逻辑(如 OG 图片生成、日报汇总)",
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "id": "openclaw-onebot",
3
3
  "name": "OneBot Channel",
4
- "version": "1.0.1",
4
+ "version": "1.0.5",
5
5
  "description": "OneBot v11 protocol channel (QQ/Lagrange.Core via WebSocket)",
6
6
  "author": "Lagrange.Onebot",
7
7
  "channels": ["onebot"],
8
+ "commands": ["onebot"],
8
9
  "configSchema": {
9
10
  "type": "object",
10
11
  "additionalProperties": false,
@@ -33,6 +34,16 @@
33
34
  "default": true,
34
35
  "description": "是否将机器人回复中的 Markdown 渲染为纯文本再发送(去除 **、# 等标记)"
35
36
  },
37
+ "normalModeFlushIntervalMs": {
38
+ "type": "number",
39
+ "default": 1200,
40
+ "description": "normal 模式下聚合发送的等待窗口(毫秒)。块流式开启后,插件会在该时间窗口内合并回复再发送。"
41
+ },
42
+ "normalModeFlushChars": {
43
+ "type": "number",
44
+ "default": 160,
45
+ "description": "normal 模式下聚合发送的字符阈值。达到该长度会提前 flush,而不是继续等待时间窗口结束。"
46
+ },
36
47
  "groupIncrease": {
37
48
  "type": "object",
38
49
  "properties": {
@@ -71,6 +82,16 @@
71
82
  "default": 300,
72
83
  "description": "长消息阈值(字符数),超过则启用 longMessageMode"
73
84
  },
85
+ "ogImageRenderTheme": {
86
+ "type": "string",
87
+ "enum": ["default", "dust", "custom"],
88
+ "default": "default",
89
+ "description": "长消息 og_image 时的渲染主题:default 无额外样式,dust 内置 dust 样式,custom 时需填写 ogImageRenderThemePath"
90
+ },
91
+ "ogImageRenderThemePath": {
92
+ "type": "string",
93
+ "description": "当 ogImageRenderTheme 为 custom 时,填写 CSS 文件绝对路径"
94
+ },
74
95
  "cronJobs": {
75
96
  "type": "array",
76
97
  "description": "内置定时任务(无 AI 介入,直接执行脚本并推送到群聊)",
@@ -97,6 +118,10 @@
97
118
  "host": { "label": "主机地址", "placeholder": "127.0.0.1" },
98
119
  "port": { "label": "端口", "placeholder": "8080" },
99
120
  "requireMention": { "label": "群聊需 @ 回复" },
100
- "renderMarkdownToPlain": { "label": "Markdown 转纯文本" }
121
+ "renderMarkdownToPlain": { "label": "Markdown 转纯文本" },
122
+ "normalModeFlushIntervalMs": { "label": "normal 聚合窗口(ms)" },
123
+ "normalModeFlushChars": { "label": "normal 提前发送阈值" },
124
+ "ogImageRenderTheme": { "label": "OG 图片渲染主题(default/dust/custom)" },
125
+ "ogImageRenderThemePath": { "label": "自定义 CSS 路径(仅当主题为 custom 时)" }
101
126
  }
102
127
  }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@kirigaya/openclaw-onebot",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "OneBot v11 protocol channel plugin for OpenClaw (QQ/Lagrange.Core/go-cqhttp)",
5
5
  "license": "MIT",
6
- "publishConfig": { "access": "public" },
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
7
9
  "author": "LSTM-Kirigaya",
8
10
  "repository": {
9
11
  "type": "git",
@@ -26,13 +28,19 @@
26
28
  "exports": "./dist/index.js",
27
29
  "files": [
28
30
  "dist",
31
+ "themes",
29
32
  "skills",
30
33
  "openclaw.plugin.json",
31
34
  "README.md"
32
35
  ],
33
36
  "openclaw": {
34
- "extensions": ["./dist/index.js"],
35
- "skills": ["skills/onebot-ops", "skills/onebot-scripts"],
37
+ "extensions": [
38
+ "./dist/index.js"
39
+ ],
40
+ "skills": [
41
+ "skills/onebot-ops",
42
+ "skills/onebot-scripts"
43
+ ],
36
44
  "channel": {
37
45
  "id": "onebot",
38
46
  "label": "OneBot",
@@ -40,40 +48,50 @@
40
48
  "docsPath": "/channels/onebot",
41
49
  "blurb": "OneBot v11 protocol via WebSocket (go-cqhttp, Lagrange.Core, etc.)",
42
50
  "order": 85,
43
- "aliases": ["qq", "lagrange", "cqhttp"]
51
+ "aliases": [
52
+ "qq",
53
+ "lagrange",
54
+ "cqhttp"
55
+ ]
44
56
  }
45
57
  },
46
58
  "scripts": {
59
+ "openclaw": "openclaw",
47
60
  "build": "tsc",
48
61
  "test:connect": "npx tsx scripts/test-connect.ts",
49
62
  "test:group-welcome": "npx tsx scripts/test-group-welcome.ts",
50
63
  "test:group-increase-handler": "npx tsx scripts/test-group-increase-handler.ts",
64
+ "test:render-og-image": "npx tsx test/render-og-image.ts",
51
65
  "prepublishOnly": "npm run build",
52
66
  "pub": "npm run build && npm publish"
53
67
  },
54
68
  "dependencies": {
55
- "ws": "^8.17.0",
56
69
  "@clack/prompts": "^1.0.0",
57
- "tsx": "^4.0.0",
58
70
  "cron": "^4.4.0",
71
+ "fuse.js": "^7.1.0",
72
+ "highlight.js": "^11.10.0",
59
73
  "marked": "^15.0.0",
60
- "highlight.js": "^11.10.0"
74
+ "tsx": "^4.0.0",
75
+ "ws": "^8.17.0"
61
76
  },
62
77
  "optionalDependencies": {
63
78
  "node-html-to-image": "^3.0.0"
64
79
  },
65
80
  "peerDependencies": {
66
- "openclaw": "*",
67
81
  "clawdbot": "*"
68
82
  },
69
83
  "peerDependenciesMeta": {
70
- "clawdbot": { "optional": true },
71
- "openclaw": { "optional": true }
84
+ "clawdbot": {
85
+ "optional": true
86
+ },
87
+ "openclaw": {
88
+ "optional": true
89
+ }
72
90
  },
73
91
  "devDependencies": {
74
- "typescript": "^5.4.0",
75
92
  "@types/node": "^22.0.0",
76
- "@types/ws": "^8.5.10"
93
+ "@types/ws": "^8.5.10",
94
+ "typescript": "^5.4.0"
77
95
  },
78
96
  "engines": {
79
97
  "node": ">=22"
@@ -46,16 +46,27 @@ openclaw plugins install ./openclaw-onebot
46
46
  | **图片消息** | message 为 `[{ type: "image", data: { file } }]` |
47
47
  | **delete_msg** | 撤回消息 |
48
48
  | **get_msg** | 获取单条消息 |
49
- | **get_group_msg_history** | 获取群历史(Lagrange.Core 扩展) |
49
+ | **get_group_msg_history** | 获取群历史(Lagrange.Core 扩展),支持 reverse_order 分页 |
50
50
  | **upload_group_file** | 上传群文件 |
51
51
  | **upload_private_file** | 上传私聊文件 |
52
52
  | **set_msg_emoji_like** | 表情回应(Lagrange/QQ NT 扩展) |
53
53
 
54
- ## 常用命令
54
+ ## Agent 工具与 CLI
55
+
56
+ 插件提供群历史、按名字搜 QQ 等能力,既注册为 **Agent 工具**(供 Cron/脚本/AI 调用),也提供等价的 **CLI 命令**,便于 AI 与人工按文档调用。
57
+
58
+ **详细说明与所有命令用法见:[agent-tools.md](agent-tools.md)**
59
+
60
+ ### 常用 CLI 一览
55
61
 
56
62
  | 命令 | 说明 |
57
63
  |------|------|
58
64
  | `openclaw onebot setup` | 交互式配置 OneBot 连接 |
59
- | `openclaw message send --channel onebot --target group:xxx --message "hi"` | 发送群消息 |
65
+ | `openclaw onebot get-group-msg-history --group-id <群号> [--hours N]` | 获取群历史(单页或最近 N 小时内,从旧到新) |
66
+ | `openclaw onebot search-group-member --group-id <群号> --name <名字>` | 按名字模糊搜群友 QQ 号 |
67
+ | `openclaw onebot upload-file --target group:<群号> --file <路径> --name <文件名>` | 上传文件到群/私聊 |
68
+ | `openclaw message send --channel onebot --target group:xxx --message "hi"` | 发送文本/图片 |
60
69
  | `openclaw gateway status` | 查看 Gateway 状态 |
61
70
  | `openclaw logs --follow` | 查看日志 |
71
+
72
+ AI 或脚本需要「获取群历史、查群友 QQ」时,应使用上述 CLI 或查阅 [agent-tools.md](agent-tools.md) 中的完整参数说明。
@@ -0,0 +1,116 @@
1
+ # OneBot Agent 工具与 CLI
2
+
3
+ 插件通过 WebSocket 与 OneBot(Lagrange.Core / go-cqhttp)通信,提供 **Agent 工具**(供 Cron/脚本/AI 调用)和等价的 **CLI 命令**(供人工或工作流直接调用)。
4
+ AI 或脚本应优先使用 **CLI**,便于复现、调试和文档化。
5
+
6
+ ---
7
+
8
+ ## 前置条件
9
+
10
+ - **Gateway 已启动**:`openclaw gateway`(或 `openclaw gateway run`)
11
+ - OneBot 已运行并连接;若为正向 WS,CLI 会按配置自动建连
12
+ - 配置:`openclaw.json` 的 `channels.onebot` 或环境变量 `ONEBOT_WS_*`
13
+
14
+ ---
15
+
16
+ ## 1. 获取群历史消息
17
+
18
+ **Agent 工具**:`onebot_get_group_msg_history`
19
+ **CLI**:
20
+
21
+ ```bash
22
+ openclaw onebot get-group-msg-history --group-id <群号> [--hours <N>] [--count 50] [--message-seq <序号>]
23
+ ```
24
+
25
+ - 指定 **`--hours N`**:获取**从现在到过去 N 小时**内的消息(内部按时间范围分页拉取),例如 `--hours 24` 即过去 24 小时。
26
+ - 不指定 `--hours`:按单页返回,始终**从旧到新**;分页时用上一批最早一条的 `message_seq` 作为 `--message-seq`。
27
+
28
+ | 参数 | 说明 |
29
+ |------|------|
30
+ | `--group-id` | 群号(必填) |
31
+ | `--hours` | 获取最近 N 小时内的消息(可选;指定后按时间范围拉取) |
32
+ | `--count` | 条数,默认 50(未指定 --hours 时生效) |
33
+ | `--message-seq` | 起始消息序号(可选,分页用,未指定 --hours 时生效) |
34
+
35
+ 示例:
36
+
37
+ ```bash
38
+ openclaw onebot get-group-msg-history --group-id 123456789
39
+ openclaw onebot get-group-msg-history --group-id 123456789 --hours 24
40
+ openclaw onebot get-group-msg-history --group-id 123456789 --hours 1 --limit 500
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 2. 按名字模糊搜索群成员(查 QQ 号)
46
+
47
+ **Agent 工具**:`onebot_search_group_member`
48
+ **CLI**:
49
+
50
+ ```bash
51
+ openclaw onebot search-group-member --group-id <群号> --name <名字>
52
+ ```
53
+
54
+ | 参数 | 说明 |
55
+ |------|------|
56
+ | `--group-id` | 群号(必填) |
57
+ | `--name` | 要搜的名字(群名片或昵称,模糊匹配) |
58
+
59
+ 输出:匹配到的 QQ 与展示名。
60
+
61
+ 示例:
62
+
63
+ ```bash
64
+ openclaw onebot search-group-member --group-id 123456789 --name 小明
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 3. 发送文本
70
+
71
+ **Agent 工具**:`onebot_send_text`
72
+ **CLI**(推荐使用主命令):
73
+
74
+ ```bash
75
+ openclaw message send --channel onebot --target group:<群号> --message "内容"
76
+ openclaw message send --channel onebot --target user:<QQ号> --message "内容"
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 4. 发送图片
82
+
83
+ **Agent 工具**:`onebot_send_image`
84
+ **CLI**:
85
+
86
+ ```bash
87
+ openclaw message send --channel onebot --target group:<群号> --media "file:///path/to.png"
88
+ openclaw message send --channel onebot --target user:<QQ号> --media "https://example.com/pic.jpg"
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 5. 上传文件到群/私聊
94
+
95
+ **Agent 工具**:`onebot_upload_file`
96
+ **CLI**:
97
+
98
+ ```bash
99
+ openclaw onebot upload-file --target group:<群号> --file <本地绝对路径> --name <显示文件名>
100
+ openclaw onebot upload-file --target user:<QQ号> --file <本地绝对路径> --name <显示文件名>
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 6. 执行脚本(Cron 等)
106
+
107
+ **Agent 工具**:`onebot_run_script`
108
+ **CLI**:无直接一对一命令,可由 Cron 或工作流调用脚本,脚本内使用上述 CLI 或 `onebotClient` API。
109
+
110
+ ---
111
+
112
+ ## 使用建议
113
+
114
+ - **AI / 自动化**:优先使用上述 **CLI 命令**,便于在 Skill 中写明「如何调用」、可复现。
115
+ - **Cron / 内置任务**:在 `openclaw.json` 的 `cronJobs` 中配置 `script`,脚本内通过 `onebotClient` 或子进程调用 CLI。
116
+ - **临时查询**:直接运行 `openclaw onebot search-group-member ...` 等。