@tencent-connect/openclaw-qqbot 1.0.0-alpha.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 (141) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +393 -0
  3. package/README.zh.md +390 -0
  4. package/bin/qqbot-cli.js +243 -0
  5. package/clawdbot.plugin.json +16 -0
  6. package/dist/index.d.ts +17 -0
  7. package/dist/index.js +22 -0
  8. package/dist/src/api.d.ts +138 -0
  9. package/dist/src/api.js +523 -0
  10. package/dist/src/channel.d.ts +3 -0
  11. package/dist/src/channel.js +337 -0
  12. package/dist/src/config.d.ts +25 -0
  13. package/dist/src/config.js +156 -0
  14. package/dist/src/gateway.d.ts +18 -0
  15. package/dist/src/gateway.js +2315 -0
  16. package/dist/src/image-server.d.ts +62 -0
  17. package/dist/src/image-server.js +401 -0
  18. package/dist/src/known-users.d.ts +100 -0
  19. package/dist/src/known-users.js +263 -0
  20. package/dist/src/onboarding.d.ts +10 -0
  21. package/dist/src/onboarding.js +203 -0
  22. package/dist/src/outbound.d.ts +150 -0
  23. package/dist/src/outbound.js +1175 -0
  24. package/dist/src/proactive.d.ts +170 -0
  25. package/dist/src/proactive.js +399 -0
  26. package/dist/src/runtime.d.ts +3 -0
  27. package/dist/src/runtime.js +10 -0
  28. package/dist/src/session-store.d.ts +52 -0
  29. package/dist/src/session-store.js +254 -0
  30. package/dist/src/types.d.ts +145 -0
  31. package/dist/src/types.js +1 -0
  32. package/dist/src/utils/audio-convert.d.ts +73 -0
  33. package/dist/src/utils/audio-convert.js +645 -0
  34. package/dist/src/utils/file-utils.d.ts +46 -0
  35. package/dist/src/utils/file-utils.js +107 -0
  36. package/dist/src/utils/image-size.d.ts +51 -0
  37. package/dist/src/utils/image-size.js +234 -0
  38. package/dist/src/utils/media-tags.d.ts +14 -0
  39. package/dist/src/utils/media-tags.js +120 -0
  40. package/dist/src/utils/payload.d.ts +112 -0
  41. package/dist/src/utils/payload.js +186 -0
  42. package/dist/src/utils/platform.d.ts +126 -0
  43. package/dist/src/utils/platform.js +358 -0
  44. package/dist/src/utils/upload-cache.d.ts +34 -0
  45. package/dist/src/utils/upload-cache.js +93 -0
  46. package/index.ts +27 -0
  47. package/moltbot.plugin.json +16 -0
  48. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  49. package/node_modules/@eshaz/web-worker/README.md +134 -0
  50. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  51. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  52. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  53. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  54. package/node_modules/@eshaz/web-worker/node.js +223 -0
  55. package/node_modules/@eshaz/web-worker/package.json +54 -0
  56. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  57. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  58. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  59. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  60. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  61. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  62. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  63. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  64. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  65. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  66. package/node_modules/mpg123-decoder/README.md +265 -0
  67. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  68. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  69. package/node_modules/mpg123-decoder/index.js +8 -0
  70. package/node_modules/mpg123-decoder/package.json +58 -0
  71. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  72. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  73. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  74. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  75. package/node_modules/silk-wasm/LICENSE +21 -0
  76. package/node_modules/silk-wasm/README.md +85 -0
  77. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  78. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  79. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  80. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  81. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  82. package/node_modules/silk-wasm/package.json +39 -0
  83. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  84. package/node_modules/simple-yenc/.prettierignore +1 -0
  85. package/node_modules/simple-yenc/LICENSE +7 -0
  86. package/node_modules/simple-yenc/README.md +163 -0
  87. package/node_modules/simple-yenc/dist/esm.js +1 -0
  88. package/node_modules/simple-yenc/dist/index.js +1 -0
  89. package/node_modules/simple-yenc/package.json +50 -0
  90. package/node_modules/simple-yenc/rollup.config.js +27 -0
  91. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  92. package/node_modules/ws/LICENSE +20 -0
  93. package/node_modules/ws/README.md +548 -0
  94. package/node_modules/ws/browser.js +8 -0
  95. package/node_modules/ws/index.js +13 -0
  96. package/node_modules/ws/lib/buffer-util.js +131 -0
  97. package/node_modules/ws/lib/constants.js +19 -0
  98. package/node_modules/ws/lib/event-target.js +292 -0
  99. package/node_modules/ws/lib/extension.js +203 -0
  100. package/node_modules/ws/lib/limiter.js +55 -0
  101. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  102. package/node_modules/ws/lib/receiver.js +706 -0
  103. package/node_modules/ws/lib/sender.js +602 -0
  104. package/node_modules/ws/lib/stream.js +161 -0
  105. package/node_modules/ws/lib/subprotocol.js +62 -0
  106. package/node_modules/ws/lib/validation.js +152 -0
  107. package/node_modules/ws/lib/websocket-server.js +554 -0
  108. package/node_modules/ws/lib/websocket.js +1393 -0
  109. package/node_modules/ws/package.json +69 -0
  110. package/node_modules/ws/wrapper.mjs +8 -0
  111. package/openclaw.plugin.json +16 -0
  112. package/package.json +76 -0
  113. package/scripts/proactive-api-server.ts +356 -0
  114. package/scripts/pull-latest.sh +316 -0
  115. package/scripts/send-proactive.ts +273 -0
  116. package/scripts/set-markdown.sh +156 -0
  117. package/scripts/upgrade-and-run.sh +525 -0
  118. package/scripts/upgrade.sh +127 -0
  119. package/skills/qqbot-cron/SKILL.md +513 -0
  120. package/skills/qqbot-media/SKILL.md +194 -0
  121. package/src/api.ts +704 -0
  122. package/src/channel.ts +368 -0
  123. package/src/config.ts +182 -0
  124. package/src/gateway.ts +2459 -0
  125. package/src/image-server.ts +474 -0
  126. package/src/known-users.ts +353 -0
  127. package/src/onboarding.ts +274 -0
  128. package/src/openclaw-plugin-sdk.d.ts +483 -0
  129. package/src/outbound.ts +1301 -0
  130. package/src/proactive.ts +530 -0
  131. package/src/runtime.ts +14 -0
  132. package/src/session-store.ts +303 -0
  133. package/src/types.ts +153 -0
  134. package/src/utils/audio-convert.ts +738 -0
  135. package/src/utils/file-utils.ts +122 -0
  136. package/src/utils/image-size.ts +266 -0
  137. package/src/utils/media-tags.ts +134 -0
  138. package/src/utils/payload.ts +265 -0
  139. package/src/utils/platform.ts +404 -0
  140. package/src/utils/upload-cache.ts +128 -0
  141. package/tsconfig.json +16 -0
@@ -0,0 +1,156 @@
1
+ #!/bin/bash
2
+
3
+ # QQBot Markdown 配置脚本
4
+ # 用于单独设置是否启用 Markdown 消息格式
5
+ # 直接编辑 JSON 配置文件,避免框架验证拒绝未注册的 channel
6
+
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ cd "$SCRIPT_DIR"
11
+
12
+ # 自动检测 CLI 配置文件(兼容 openclaw / clawdbot / moltbot)
13
+ OPENCLAW_CONFIG=""
14
+ for app in openclaw clawdbot moltbot; do
15
+ cfg="$HOME/.$app/$app.json"
16
+ if [ -f "$cfg" ]; then
17
+ OPENCLAW_CONFIG="$cfg"
18
+ break
19
+ fi
20
+ done
21
+
22
+ if [ -z "$OPENCLAW_CONFIG" ]; then
23
+ echo "❌ 未找到 openclaw / clawdbot / moltbot 配置文件"
24
+ echo " 请先运行 openclaw onboard 初始化配置"
25
+ exit 1
26
+ fi
27
+
28
+ show_help() {
29
+ echo "用法: $0 [选项]"
30
+ echo ""
31
+ echo "选项:"
32
+ echo " enable, on, yes 启用 Markdown 消息格式"
33
+ echo " disable, off, no 禁用 Markdown 消息格式(使用纯文本)"
34
+ echo " status 显示当前 Markdown 配置状态"
35
+ echo " -h, --help 显示帮助信息"
36
+ echo ""
37
+ echo "示例:"
38
+ echo " $0 enable 启用 Markdown"
39
+ echo " $0 disable 禁用 Markdown"
40
+ echo " $0 status 查看当前状态"
41
+ echo " $0 交互式选择"
42
+ echo ""
43
+ echo "⚠️ 注意: 启用 Markdown 需要在 QQ 开放平台申请 Markdown 消息权限"
44
+ echo " 如果没有权限,消息将无法正常发送!"
45
+ }
46
+
47
+ set_markdown_value() {
48
+ local value="$1"
49
+ node -e "
50
+ const fs = require('fs');
51
+ const cfg = JSON.parse(fs.readFileSync('$OPENCLAW_CONFIG', 'utf-8'));
52
+ if (!cfg.channels) cfg.channels = {};
53
+ if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
54
+ cfg.channels.qqbot.markdownSupport = $value;
55
+ fs.writeFileSync('$OPENCLAW_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
56
+ "
57
+ }
58
+
59
+ enable_markdown() {
60
+ echo "✅ 启用 Markdown 消息格式..."
61
+ set_markdown_value true
62
+ echo ""
63
+ echo "Markdown 已启用。"
64
+ echo "⚠️ 请确保您已在 QQ 开放平台申请了 Markdown 消息权限。"
65
+ }
66
+
67
+ disable_markdown() {
68
+ echo "❌ 禁用 Markdown 消息格式(使用纯文本)..."
69
+ set_markdown_value false
70
+ echo ""
71
+ echo "Markdown 已禁用,将使用纯文本格式发送消息。"
72
+ }
73
+
74
+ show_status() {
75
+ echo "当前 Markdown 配置状态:"
76
+ echo " 配置文件: $OPENCLAW_CONFIG"
77
+ echo ""
78
+ current=$(node -e "
79
+ const cfg = JSON.parse(require('fs').readFileSync('$OPENCLAW_CONFIG', 'utf-8'));
80
+ console.log(cfg.channels?.qqbot?.markdownSupport ?? '未设置');
81
+ " 2>/dev/null || echo "未设置")
82
+ if [ "$current" = "true" ]; then
83
+ echo " 状态: ✅ 已启用"
84
+ echo ""
85
+ echo " ⚠️ 请确保您已在 QQ 开放平台申请了 Markdown 消息权限。"
86
+ elif [ "$current" = "false" ]; then
87
+ echo " 状态: ❌ 已禁用(纯文本模式)"
88
+ else
89
+ echo " 状态: 未设置(默认: 禁用)"
90
+ fi
91
+ }
92
+
93
+ interactive_select() {
94
+ echo "========================================="
95
+ echo " QQBot Markdown 配置"
96
+ echo "========================================="
97
+ echo ""
98
+ show_status
99
+ echo ""
100
+ echo "-----------------------------------------"
101
+ echo ""
102
+ echo "是否启用 Markdown 消息格式?"
103
+ echo ""
104
+ echo "⚠️ 注意: 启用 Markdown 需要在 QQ 开放平台申请 Markdown 消息权限"
105
+ echo " 如果没有权限,消息将无法正常发送!"
106
+ echo ""
107
+ echo " 1) 启用 Markdown"
108
+ echo " 2) 禁用 Markdown(纯文本)"
109
+ echo " 3) 取消"
110
+ echo ""
111
+ read -t 10 -p "请选择 [1-3] (默认: 2): " choice || choice="2"
112
+
113
+ case "$choice" in
114
+ 1)
115
+ echo ""
116
+ enable_markdown
117
+ ;;
118
+ 2|"")
119
+ echo ""
120
+ disable_markdown
121
+ ;;
122
+ 3)
123
+ echo "已取消。"
124
+ exit 0
125
+ ;;
126
+ *)
127
+ echo "无效选择,已取消。"
128
+ exit 1
129
+ ;;
130
+ esac
131
+ }
132
+
133
+ # 主逻辑
134
+ case "${1:-}" in
135
+ enable|on|yes)
136
+ enable_markdown
137
+ ;;
138
+ disable|off|no)
139
+ disable_markdown
140
+ ;;
141
+ status)
142
+ show_status
143
+ ;;
144
+ -h|--help)
145
+ show_help
146
+ ;;
147
+ "")
148
+ interactive_select
149
+ ;;
150
+ *)
151
+ echo "未知选项: $1"
152
+ echo ""
153
+ show_help
154
+ exit 1
155
+ ;;
156
+ esac
@@ -0,0 +1,525 @@
1
+ #!/bin/bash
2
+
3
+ # QQBot 一键更新并启动脚本
4
+ # 版本: 2.0 (增强错误处理版)
5
+ #
6
+ # 主要改进:
7
+ # 1. 详细的安装错误诊断和排查建议
8
+ # 2. 所有关键步骤的错误捕获和报告
9
+ # 3. 日志文件保存和错误摘要
10
+ # 4. 智能故障排查指南
11
+ # 5. 用户友好的交互提示
12
+
13
+ set -eo pipefail
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ # 如果脚本在 scripts/ 子目录里,往上一级就是项目根目录
17
+ if [ "$(basename "$SCRIPT_DIR")" = "scripts" ]; then
18
+ PROJ_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
19
+ else
20
+ PROJ_DIR="$SCRIPT_DIR"
21
+ fi
22
+ cd "$PROJ_DIR"
23
+
24
+ # 解析命令行参数
25
+ APPID=""
26
+ SECRET=""
27
+ MARKDOWN=""
28
+
29
+ while [[ $# -gt 0 ]]; do
30
+ case $1 in
31
+ --appid)
32
+ APPID="$2"
33
+ shift 2
34
+ ;;
35
+ --secret)
36
+ SECRET="$2"
37
+ shift 2
38
+ ;;
39
+ --markdown)
40
+ MARKDOWN="$2"
41
+ shift 2
42
+ ;;
43
+ -h|--help)
44
+ echo "用法: $0 [选项]"
45
+ echo ""
46
+ echo "选项:"
47
+ echo " --appid <appid> QQ机器人 AppID"
48
+ echo " --secret <secret> QQ机器人 Secret"
49
+ echo " --markdown <yes|no> 是否启用 Markdown 消息格式(默认: no)"
50
+ echo " -h, --help 显示帮助信息"
51
+ echo ""
52
+ echo "也可以通过环境变量设置:"
53
+ echo " QQBOT_APPID QQ机器人 AppID"
54
+ echo " QQBOT_SECRET QQ机器人 Secret"
55
+ echo " QQBOT_TOKEN QQ机器人 Token (AppID:Secret)"
56
+ echo " QQBOT_MARKDOWN 是否启用 Markdown(yes/no)"
57
+ echo ""
58
+ echo "不带参数时,将使用已有配置直接启动。"
59
+ echo ""
60
+ echo "⚠️ 注意: 启用 Markdown 需要在 QQ 开放平台申请 Markdown 消息权限"
61
+ exit 0
62
+ ;;
63
+ *)
64
+ echo "未知选项: $1"
65
+ echo "使用 --help 查看帮助信息"
66
+ exit 1
67
+ ;;
68
+ esac
69
+ done
70
+
71
+ # 使用命令行参数或环境变量
72
+ APPID="${APPID:-$QQBOT_APPID}"
73
+ SECRET="${SECRET:-$QQBOT_SECRET}"
74
+ MARKDOWN="${MARKDOWN:-$QQBOT_MARKDOWN}"
75
+
76
+ echo "========================================="
77
+ echo " QQBot 一键更新启动脚本"
78
+ echo "========================================="
79
+
80
+ # 1. 备份已有 qqbot 通道配置,防止升级过程丢失
81
+ echo ""
82
+ echo "[1/6] 备份已有配置..."
83
+ SAVED_QQBOT_TOKEN=""
84
+ for APP_NAME in openclaw clawdbot moltbot; do
85
+ CONFIG_FILE="$HOME/.$APP_NAME/$APP_NAME.json"
86
+ if [ -f "$CONFIG_FILE" ]; then
87
+ SAVED_QQBOT_TOKEN=$(node -e "
88
+ const cfg = JSON.parse(require('fs').readFileSync('$CONFIG_FILE', 'utf8'));
89
+ // 尝试所有可能的 channel key(原仓库 + 本仓库)
90
+ const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
91
+ for (const key of keys) {
92
+ const ch = cfg.channels && cfg.channels[key];
93
+ if (!ch) continue;
94
+ if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
95
+ if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
96
+ }
97
+ " 2>/dev/null || true)
98
+ if [ -n "$SAVED_QQBOT_TOKEN" ]; then
99
+ echo "已备份 qqbot 通道 token: ${SAVED_QQBOT_TOKEN:0:10}..."
100
+ break
101
+ fi
102
+ fi
103
+ done
104
+
105
+ # 若当前配置中没有,再尝试从 openclaw 备份文件恢复
106
+ if [ -z "$SAVED_QQBOT_TOKEN" ] && [ -d "$HOME/.openclaw" ]; then
107
+ SAVED_QQBOT_TOKEN=$(node -e "
108
+ const fs = require('fs');
109
+ const path = require('path');
110
+ const dir = path.join(process.env.HOME, '.openclaw');
111
+ const files = fs.readdirSync(dir)
112
+ .filter((n) => /^openclaw\.json\.bak(\.\d+)?$/.test(n))
113
+ .map((n) => path.join(dir, n))
114
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
115
+ for (const f of files) {
116
+ try {
117
+ const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
118
+ const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
119
+ for (const key of keys) {
120
+ const ch = cfg.channels && cfg.channels[key];
121
+ if (!ch) continue;
122
+ if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
123
+ if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
124
+ }
125
+ } catch {}
126
+ }
127
+ " 2>/dev/null || true)
128
+
129
+ if [ -n "$SAVED_QQBOT_TOKEN" ]; then
130
+ echo "已从 ~/.openclaw/openclaw.json.bak* 找到 qqbot 备份 token: ${SAVED_QQBOT_TOKEN:0:10}..."
131
+ fi
132
+ fi
133
+
134
+ # 2. 移除老版本
135
+ echo ""
136
+ echo "[2/6] 移除老版本..."
137
+ if [ -f "$PROJ_DIR/scripts/upgrade.sh" ]; then
138
+ bash "$PROJ_DIR/scripts/upgrade.sh"
139
+ else
140
+ echo "警告: upgrade.sh 不存在,跳过移除步骤"
141
+ fi
142
+
143
+ # 3. 安装当前版本
144
+ echo ""
145
+ echo "[3/6] 安装当前版本..."
146
+
147
+ echo "检查当前目录: $(pwd)"
148
+ echo "检查openclaw版本: $(openclaw --version 2>/dev/null || echo 'openclaw not found')"
149
+
150
+ # 记录更新前的 qqbot 插件版本
151
+ OLD_QQBOT_VERSION=$(node -e '
152
+ try {
153
+ const fs = require("fs");
154
+ const path = require("path");
155
+ const candidates = ["openclaw-qqbot", "qqbot", "openclaw-qq"];
156
+ for (const name of candidates) {
157
+ const pkgPath = path.join(process.env.HOME, ".openclaw", "extensions", name, "package.json");
158
+ if (!fs.existsSync(pkgPath)) continue;
159
+ const p = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
160
+ process.stdout.write(p.version || "unknown");
161
+ process.exit(0);
162
+ }
163
+ process.stdout.write("not_installed");
164
+ } catch(e) { process.stdout.write("not_installed"); }
165
+ ' 2>/dev/null || echo "not_installed")
166
+
167
+ echo "开始安装插件..."
168
+ INSTALL_LOG="/tmp/openclaw-install-$(date +%s).log"
169
+
170
+ echo "安装日志文件: $INSTALL_LOG"
171
+ echo "详细信息将记录到日志文件中..."
172
+
173
+ # 尝试安装并捕获详细输出
174
+ if ! openclaw plugins install . 2>&1 | tee "$INSTALL_LOG"; then
175
+ echo ""
176
+ echo "❌ 插件安装失败!"
177
+ echo "========================================="
178
+ echo "故障排查信息:"
179
+ echo "========================================="
180
+
181
+ # 分析错误原因
182
+ echo "1. 检查日志文件末尾: $INSTALL_LOG"
183
+ echo "2. 常见原因分析:"
184
+
185
+ # 检查网络连接
186
+ echo " - 网络问题: 测试 npm 仓库连接"
187
+ echo " curl -I https://registry.npmjs.org/ || curl -I https://registry.npmmirror.com/"
188
+
189
+ # 检查权限
190
+ echo " - 权限问题: 检查安装目录权限"
191
+ echo " ls -la ~/.openclaw/ 2>/dev/null || echo '目录不存在'"
192
+
193
+ # 检查npm配置
194
+ echo " - npm配置: 检查当前npm配置"
195
+ echo " npm config get registry"
196
+
197
+ # 显示错误摘要
198
+ echo ""
199
+ echo "3. 错误摘要:"
200
+ tail -20 "$INSTALL_LOG" | grep -i -E "(error|fail|warn|npm install)"
201
+
202
+ echo ""
203
+ echo "4. 可选解决方案:"
204
+ echo " a. 更换npm镜像源:"
205
+ echo " npm config set registry https://registry.npmmirror.com/"
206
+ echo " b. 清理npm缓存:"
207
+ echo " npm cache clean --force"
208
+ echo " c. 手动安装依赖:"
209
+ echo " cd $(pwd) && npm install --verbose"
210
+
211
+ echo ""
212
+ echo "========================================="
213
+ echo "建议: 先查看完整日志文件: cat $INSTALL_LOG"
214
+ echo "或者尝试手动安装: cd $(pwd) && npm install"
215
+ echo "========================================="
216
+
217
+ read -t 10 -p "是否继续配置其他步骤? (y/N): " continue_choice || continue_choice="N"
218
+ case "$continue_choice" in
219
+ [Yy]* )
220
+ echo "继续执行后续配置步骤..."
221
+ ;;
222
+ * )
223
+ echo "安装失败,脚本退出。"
224
+ echo "请先解决安装问题后再运行此脚本。"
225
+ exit 1
226
+ ;;
227
+ esac
228
+ else
229
+ echo ""
230
+ echo "✅ 插件安装成功!"
231
+ echo "安装日志已保存到: $INSTALL_LOG"
232
+
233
+ # plugins install 会修改 openclaw.json(plugins.allow/entries/installs),
234
+ # gateway 检测到 config change 后会自动 SIGUSR1 重启(可能触发 1~2 次)。
235
+ # 必须等这波自动重启完全结束,否则后续的 gateway restart 会叠加导致竞态。
236
+ echo ""
237
+ echo "等待 gateway 自动重启链完成(约 20 秒)..."
238
+ sleep 20
239
+
240
+ # 记录更新后的 qqbot 插件版本
241
+ NEW_QQBOT_VERSION=$(node -e '
242
+ try {
243
+ const fs = require("fs");
244
+ const path = require("path");
245
+ const candidates = ["openclaw-qqbot", "qqbot", "openclaw-qq"];
246
+ for (const name of candidates) {
247
+ const pkgPath = path.join(process.env.HOME, ".openclaw", "extensions", name, "package.json");
248
+ if (!fs.existsSync(pkgPath)) continue;
249
+ const p = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
250
+ process.stdout.write(p.version || "unknown");
251
+ process.exit(0);
252
+ }
253
+ process.stdout.write("unknown");
254
+ } catch(e) { process.stdout.write("unknown"); }
255
+ ' 2>/dev/null || echo "unknown")
256
+ fi
257
+
258
+ # 4. 配置机器人通道(仅在需要变更时写入配置,避免无意义覆盖)
259
+ echo ""
260
+ echo "[4/6] 配置机器人通道..."
261
+
262
+ # 读取当前 qqbot token(兼容多 key)
263
+ CURRENT_QQBOT_TOKEN=""
264
+ for _app in openclaw clawdbot moltbot; do
265
+ _cfg="$HOME/.$_app/$_app.json"
266
+ if [ -f "$_cfg" ]; then
267
+ CURRENT_QQBOT_TOKEN=$(node -e "
268
+ const cfg = JSON.parse(require('fs').readFileSync('$_cfg', 'utf8'));
269
+ const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
270
+ for (const key of keys) {
271
+ const ch = cfg.channels && cfg.channels[key];
272
+ if (!ch) continue;
273
+ if (ch.token) { process.stdout.write(ch.token); process.exit(0); }
274
+ if (ch.appId && ch.clientSecret) { process.stdout.write(ch.appId + ':' + ch.clientSecret); process.exit(0); }
275
+ }
276
+ " 2>/dev/null || true)
277
+ [ -n "$CURRENT_QQBOT_TOKEN" ] && break
278
+ fi
279
+ done
280
+
281
+ DESIRED_QQBOT_TOKEN=""
282
+ if [ -n "$APPID" ] && [ -n "$SECRET" ]; then
283
+ DESIRED_QQBOT_TOKEN="${APPID}:${SECRET}"
284
+ echo "使用提供的 AppID 和 Secret 配置..."
285
+ elif [ -n "$QQBOT_TOKEN" ]; then
286
+ DESIRED_QQBOT_TOKEN="$QQBOT_TOKEN"
287
+ echo "使用环境变量 QQBOT_TOKEN 配置..."
288
+ elif [ -n "$SAVED_QQBOT_TOKEN" ]; then
289
+ DESIRED_QQBOT_TOKEN="$SAVED_QQBOT_TOKEN"
290
+ echo "未提供 AppID/Secret,使用备份 token 恢复配置..."
291
+ fi
292
+
293
+ if [ -n "$DESIRED_QQBOT_TOKEN" ]; then
294
+ echo "配置机器人通道: qqbot"
295
+ echo "目标Token: ${DESIRED_QQBOT_TOKEN:0:10}..."
296
+ if [ "$CURRENT_QQBOT_TOKEN" = "$DESIRED_QQBOT_TOKEN" ]; then
297
+ echo "✅ 当前配置已是目标值,跳过写入(避免配置覆盖提示)"
298
+ _config_changed=0
299
+ elif ! openclaw channels add --channel qqbot --token "$DESIRED_QQBOT_TOKEN" 2>&1; then
300
+ echo "⚠️ 警告: 机器人通道配置失败,继续使用已有配置"
301
+ _config_changed=0
302
+ else
303
+ echo "✅ 机器人通道配置成功"
304
+ _config_changed=1
305
+ # 重要:配置写入后 gateway 会自动检测变化并热重载(SIGUSR1)
306
+ # 必须等待热重载完成,否则后续的 gateway restart 会导致连续两次重启
307
+ echo "等待 gateway 热重载完成..."
308
+ sleep 5
309
+ fi
310
+ else
311
+ # 未提供任何可用 token 时,检查是否已有可用配置
312
+ _has_channel=0
313
+ if [ -n "$CURRENT_QQBOT_TOKEN" ]; then
314
+ _has_channel=1
315
+ fi
316
+
317
+ if [ "$_has_channel" -eq 0 ]; then
318
+ echo ""
319
+ echo "❌ 未检测到 qqbot 通道配置!"
320
+ echo ""
321
+ echo "首次运行请提供 AppID 和 AppSecret:"
322
+ echo ""
323
+ echo " bash $0 --appid <你的AppID> --secret <你的AppSecret>"
324
+ echo ""
325
+ echo "也可以通过环境变量:"
326
+ echo ""
327
+ echo " QQBOT_APPID=<AppID> QQBOT_SECRET=<AppSecret> bash $0"
328
+ echo ""
329
+ echo "AppID 和 AppSecret 可在 QQ 开放平台 (https://q.qq.com) 获取。"
330
+ exit 1
331
+ else
332
+ echo "使用已有配置"
333
+ fi
334
+ fi
335
+
336
+ # 5. 配置 Markdown 选项(仅在明确指定时才配置)
337
+ echo ""
338
+ echo "[5/6] 配置 Markdown 选项..."
339
+
340
+ if [ -n "$MARKDOWN" ]; then
341
+ # 设置 markdown 配置
342
+ if [ "$MARKDOWN" = "yes" ] || [ "$MARKDOWN" = "y" ] || [ "$MARKDOWN" = "true" ]; then
343
+ MARKDOWN_VALUE="true"
344
+ echo "启用 Markdown 消息格式..."
345
+ else
346
+ MARKDOWN_VALUE="false"
347
+ echo "禁用 Markdown 消息格式(使用纯文本)..."
348
+ fi
349
+
350
+ CURRENT_MARKDOWN_VALUE=$(node -e "
351
+ const fs = require('fs');
352
+ const path = require('path');
353
+ const home = process.env.HOME;
354
+ for (const app of ['openclaw', 'clawdbot', 'moltbot']) {
355
+ const f = path.join(home, '.' + app, app + '.json');
356
+ if (!fs.existsSync(f)) continue;
357
+ try {
358
+ const cfg = JSON.parse(fs.readFileSync(f, 'utf8'));
359
+ const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
360
+ for (const key of keys) {
361
+ const ch = cfg.channels && cfg.channels[key];
362
+ if (!ch) continue;
363
+ if (typeof ch.markdownSupport === 'boolean') { process.stdout.write(String(ch.markdownSupport)); process.exit(0); }
364
+ }
365
+ } catch {}
366
+ }
367
+ " 2>/dev/null || true)
368
+
369
+ if [ "$CURRENT_MARKDOWN_VALUE" = "$MARKDOWN_VALUE" ]; then
370
+ echo "✅ Markdown 配置已是目标值,跳过写入(避免配置覆盖提示)"
371
+ elif openclaw config set channels.qqbot.markdownSupport "$MARKDOWN_VALUE" 2>&1; then
372
+ echo "✅ Markdown配置成功"
373
+ _config_changed=1
374
+ else
375
+ echo "⚠️ openclaw config set 失败,尝试直接编辑配置文件..."
376
+ OPENCLAW_CONFIG="$HOME/.openclaw/openclaw.json"
377
+ if [ -f "$OPENCLAW_CONFIG" ] && node -e "
378
+ const fs = require('fs');
379
+ const cfg = JSON.parse(fs.readFileSync('$OPENCLAW_CONFIG', 'utf-8'));
380
+ if (!cfg.channels) cfg.channels = {};
381
+ if (!cfg.channels.qqbot) cfg.channels.qqbot = {};
382
+ const target = $MARKDOWN_VALUE;
383
+ if (cfg.channels.qqbot.markdownSupport === target) process.exit(0);
384
+ cfg.channels.qqbot.markdownSupport = target;
385
+ fs.writeFileSync('$OPENCLAW_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
386
+ " 2>&1; then
387
+ echo "✅ Markdown配置成功(直接编辑配置文件)"
388
+ _config_changed=1
389
+ else
390
+ echo "⚠️ Markdown配置设置失败,不影响后续运行"
391
+ fi
392
+ fi
393
+ else
394
+ echo "未指定 Markdown 选项,使用已有配置"
395
+ fi
396
+
397
+ # 6. 启动 openclaw
398
+ echo ""
399
+ echo "[6/6] 启动 openclaw..."
400
+ echo "========================================="
401
+
402
+ # 检查openclaw是否可用
403
+ if ! command -v openclaw &> /dev/null; then
404
+ echo "❌ 错误: openclaw 命令未找到!"
405
+ echo ""
406
+ echo "可能的原因:"
407
+ echo "1. OpenClaw未安装或安装失败"
408
+ echo "2. PATH环境变量未包含openclaw路径"
409
+ echo "3. 需要重新登录或重启终端"
410
+ echo ""
411
+ exit 1
412
+ fi
413
+
414
+ echo "OpenClaw版本: $(openclaw --version 2>/dev/null || echo '未知')"
415
+
416
+ # 显示 qqbot 插件更新信息
417
+ NEW_QQBOT_VERSION="${NEW_QQBOT_VERSION:-unknown}"
418
+ if [ "$OLD_QQBOT_VERSION" = "$NEW_QQBOT_VERSION" ]; then
419
+ echo "QQBot 插件版本: $NEW_QQBOT_VERSION (未变化)"
420
+ elif [ "$OLD_QQBOT_VERSION" = "not_installed" ]; then
421
+ echo "QQBot 插件版本: $NEW_QQBOT_VERSION (新安装)"
422
+ else
423
+ echo "QQBot 插件版本: $OLD_QQBOT_VERSION -> $NEW_QQBOT_VERSION"
424
+ fi
425
+ echo ""
426
+ read -t 120 -p "是否后台重启 OpenClaw 网关服务?[Y/n] " start_choice || start_choice="y"
427
+ start_choice="${start_choice:-y}"
428
+ start_choice=$(printf '%s' "$start_choice" | tr '[:upper:]' '[:lower:]')
429
+
430
+ case "$start_choice" in
431
+ y|yes)
432
+ echo ""
433
+ # 不论配置是否变更,都显式 restart 一次,确保插件正确加载
434
+ # (plugins install 触发的自动重启链已在第 3 步等待完成)
435
+ echo "正在后台重启 OpenClaw 网关服务..."
436
+ if ! openclaw gateway restart 2>&1; then
437
+ echo ""
438
+ echo "⚠️ 后台重启失败,可能服务未安装"
439
+ echo "尝试: openclaw gateway install && openclaw gateway start"
440
+ fi
441
+ echo ""
442
+ echo "✅ OpenClaw 网关已在后台重启"
443
+ echo ""
444
+ # 等待 gateway 端口就绪(插件安装+自动重启可能需要 30-60 秒)
445
+ echo "等待 gateway 就绪(插件安装中,可能需要 30-60 秒)..."
446
+ echo "========================================="
447
+ _port_ready=0
448
+ for i in $(seq 1 30); do
449
+ if lsof -i :18789 -sTCP:LISTEN >/dev/null 2>&1; then
450
+ _port_ready=1
451
+ break
452
+ fi
453
+ printf "\r 等待端口 18789 就绪... (%d/30)" "$i"
454
+ sleep 2
455
+ done
456
+ echo ""
457
+
458
+ if [ "$_port_ready" -eq 0 ]; then
459
+ echo "⚠️ 等待超时,gateway 可能仍在启动中"
460
+ echo "请手动检查: openclaw doctor"
461
+ echo "或查看日志: tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
462
+ else
463
+ echo "✅ Gateway 端口已就绪"
464
+ echo ""
465
+ # 检查 QQBot WS 是否连接成功(最多等 30 秒)
466
+ echo "检查 QQBot 插件连接状态..."
467
+ _LOG_FILE="/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
468
+ _qqbot_ready=0
469
+ for _j in $(seq 1 15); do
470
+ if grep -q "Gateway ready" "$_LOG_FILE" 2>/dev/null && \
471
+ _last_ready_time=$(grep "Gateway ready" "$_LOG_FILE" | tail -1 | grep -o '"date":"[^"]*"' | tail -1) && \
472
+ [ -n "$_last_ready_time" ]; then
473
+ _qqbot_ready=1
474
+ break
475
+ fi
476
+ printf "\r 等待 QQBot WS 连接... (%d/15)" "$_j"
477
+ sleep 2
478
+ done
479
+ echo ""
480
+
481
+ if [ "$_qqbot_ready" -eq 0 ]; then
482
+ echo "⚠️ QQBot 插件可能未正确加载,尝试再次重启..."
483
+ openclaw gateway restart 2>&1 || true
484
+ sleep 10
485
+ else
486
+ echo "✅ QQBot 插件已连接"
487
+ fi
488
+ echo ""
489
+ echo "正在跟踪日志输出(按 Ctrl+C 停止查看,不影响后台服务)..."
490
+ echo "========================================="
491
+ _retries=0
492
+ while ! openclaw logs --follow 2>&1; do
493
+ _retries=$((_retries + 1))
494
+ if [ $_retries -ge 5 ]; then
495
+ echo ""
496
+ echo "⚠️ 无法连接日志流,请手动执行: openclaw logs --follow"
497
+ echo "或直接查看日志文件: tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log"
498
+ break
499
+ fi
500
+ echo "等待日志流就绪... (${_retries}/5)"
501
+ sleep 3
502
+ done
503
+ fi
504
+ ;;
505
+ n|no)
506
+ echo ""
507
+ echo "✅ 插件更新完毕,未启动服务"
508
+ echo ""
509
+ echo "后续可手动启动:"
510
+ echo " openclaw gateway restart # 重启后台服务"
511
+ echo " openclaw logs --follow # 跟踪日志"
512
+ ;;
513
+ *)
514
+ echo "无效选择,按默认值 y 执行后台重启"
515
+ echo ""
516
+ echo "正在后台重启 OpenClaw 网关服务..."
517
+ if ! openclaw gateway restart 2>&1; then
518
+ echo "⚠️ 后台重启失败,可能服务未安装"
519
+ echo "尝试: openclaw gateway install && openclaw gateway start"
520
+ fi
521
+ echo "✅ OpenClaw 网关已在后台重启"
522
+ ;;
523
+ esac
524
+
525
+ echo "========================================="