@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.
- package/LICENSE +22 -0
- package/README.md +393 -0
- package/README.zh.md +390 -0
- package/bin/qqbot-cli.js +243 -0
- package/clawdbot.plugin.json +16 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +22 -0
- package/dist/src/api.d.ts +138 -0
- package/dist/src/api.js +523 -0
- package/dist/src/channel.d.ts +3 -0
- package/dist/src/channel.js +337 -0
- package/dist/src/config.d.ts +25 -0
- package/dist/src/config.js +156 -0
- package/dist/src/gateway.d.ts +18 -0
- package/dist/src/gateway.js +2315 -0
- package/dist/src/image-server.d.ts +62 -0
- package/dist/src/image-server.js +401 -0
- package/dist/src/known-users.d.ts +100 -0
- package/dist/src/known-users.js +263 -0
- package/dist/src/onboarding.d.ts +10 -0
- package/dist/src/onboarding.js +203 -0
- package/dist/src/outbound.d.ts +150 -0
- package/dist/src/outbound.js +1175 -0
- package/dist/src/proactive.d.ts +170 -0
- package/dist/src/proactive.js +399 -0
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/session-store.d.ts +52 -0
- package/dist/src/session-store.js +254 -0
- package/dist/src/types.d.ts +145 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils/audio-convert.d.ts +73 -0
- package/dist/src/utils/audio-convert.js +645 -0
- package/dist/src/utils/file-utils.d.ts +46 -0
- package/dist/src/utils/file-utils.js +107 -0
- package/dist/src/utils/image-size.d.ts +51 -0
- package/dist/src/utils/image-size.js +234 -0
- package/dist/src/utils/media-tags.d.ts +14 -0
- package/dist/src/utils/media-tags.js +120 -0
- package/dist/src/utils/payload.d.ts +112 -0
- package/dist/src/utils/payload.js +186 -0
- package/dist/src/utils/platform.d.ts +126 -0
- package/dist/src/utils/platform.js +358 -0
- package/dist/src/utils/upload-cache.d.ts +34 -0
- package/dist/src/utils/upload-cache.js +93 -0
- package/index.ts +27 -0
- package/moltbot.plugin.json +16 -0
- package/node_modules/@eshaz/web-worker/LICENSE +201 -0
- package/node_modules/@eshaz/web-worker/README.md +134 -0
- package/node_modules/@eshaz/web-worker/browser.js +17 -0
- package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
- package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
- package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
- package/node_modules/@eshaz/web-worker/node.js +223 -0
- package/node_modules/@eshaz/web-worker/package.json +54 -0
- package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
- package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
- package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
- package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
- package/node_modules/mpg123-decoder/README.md +265 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
- package/node_modules/mpg123-decoder/index.js +8 -0
- package/node_modules/mpg123-decoder/package.json +58 -0
- package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
- package/node_modules/mpg123-decoder/types.d.ts +30 -0
- package/node_modules/silk-wasm/LICENSE +21 -0
- package/node_modules/silk-wasm/README.md +85 -0
- package/node_modules/silk-wasm/lib/index.cjs +16 -0
- package/node_modules/silk-wasm/lib/index.d.ts +70 -0
- package/node_modules/silk-wasm/lib/index.mjs +16 -0
- package/node_modules/silk-wasm/lib/silk.wasm +0 -0
- package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
- package/node_modules/silk-wasm/package.json +39 -0
- package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
- package/node_modules/simple-yenc/.prettierignore +1 -0
- package/node_modules/simple-yenc/LICENSE +7 -0
- package/node_modules/simple-yenc/README.md +163 -0
- package/node_modules/simple-yenc/dist/esm.js +1 -0
- package/node_modules/simple-yenc/dist/index.js +1 -0
- package/node_modules/simple-yenc/package.json +50 -0
- package/node_modules/simple-yenc/rollup.config.js +27 -0
- package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +13 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/node_modules/ws/lib/receiver.js +706 -0
- package/node_modules/ws/lib/sender.js +602 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +554 -0
- package/node_modules/ws/lib/websocket.js +1393 -0
- package/node_modules/ws/package.json +69 -0
- package/node_modules/ws/wrapper.mjs +8 -0
- package/openclaw.plugin.json +16 -0
- package/package.json +76 -0
- package/scripts/proactive-api-server.ts +356 -0
- package/scripts/pull-latest.sh +316 -0
- package/scripts/send-proactive.ts +273 -0
- package/scripts/set-markdown.sh +156 -0
- package/scripts/upgrade-and-run.sh +525 -0
- package/scripts/upgrade.sh +127 -0
- package/skills/qqbot-cron/SKILL.md +513 -0
- package/skills/qqbot-media/SKILL.md +194 -0
- package/src/api.ts +704 -0
- package/src/channel.ts +368 -0
- package/src/config.ts +182 -0
- package/src/gateway.ts +2459 -0
- package/src/image-server.ts +474 -0
- package/src/known-users.ts +353 -0
- package/src/onboarding.ts +274 -0
- package/src/openclaw-plugin-sdk.d.ts +483 -0
- package/src/outbound.ts +1301 -0
- package/src/proactive.ts +530 -0
- package/src/runtime.ts +14 -0
- package/src/session-store.ts +303 -0
- package/src/types.ts +153 -0
- package/src/utils/audio-convert.ts +738 -0
- package/src/utils/file-utils.ts +122 -0
- package/src/utils/image-size.ts +266 -0
- package/src/utils/media-tags.ts +134 -0
- package/src/utils/payload.ts +265 -0
- package/src/utils/platform.ts +404 -0
- package/src/utils/upload-cache.ts +128 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# QQBot 拉取最新源码并更新
|
|
4
|
+
# 从 GitHub 拉取最新代码,安装依赖并重启网关
|
|
5
|
+
# 兼容 clawdbot / openclaw / moltbot,macOS 开箱即用
|
|
6
|
+
#
|
|
7
|
+
# 用法:
|
|
8
|
+
# pull-latest.sh # 拉取最新代码并更新
|
|
9
|
+
# pull-latest.sh --branch main # 指定分支(默认 main)
|
|
10
|
+
# pull-latest.sh --force # 跳过交互,强制更新
|
|
11
|
+
# pull-latest.sh --repo <git-url> # 指定仓库地址
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
##############################################################################
|
|
16
|
+
# 常量 & 参数
|
|
17
|
+
##############################################################################
|
|
18
|
+
readonly DEFAULT_REPO="https://github.com/tencent-connect/openclaw-qqbot.git"
|
|
19
|
+
readonly GATEWAY_PORT=18789
|
|
20
|
+
readonly SUPPORTED_CLIS=(openclaw clawdbot moltbot)
|
|
21
|
+
|
|
22
|
+
FORCE=false
|
|
23
|
+
BRANCH="main"
|
|
24
|
+
REPO_URL=""
|
|
25
|
+
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
-f|--force) FORCE=true; shift ;;
|
|
29
|
+
-b|--branch) BRANCH="$2"; shift 2 ;;
|
|
30
|
+
--repo) REPO_URL="$2"; shift 2 ;;
|
|
31
|
+
-h|--help)
|
|
32
|
+
echo "QQBot 拉取最新源码并更新"
|
|
33
|
+
echo ""
|
|
34
|
+
echo "用法:"
|
|
35
|
+
echo " pull-latest.sh # 拉取最新代码并更新"
|
|
36
|
+
echo " pull-latest.sh --branch main # 指定分支(默认 main)"
|
|
37
|
+
echo " pull-latest.sh --force # 跳过交互,强制更新"
|
|
38
|
+
echo " pull-latest.sh --repo <git-url> # 指定仓库地址"
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
echo "未知选项: $1 (使用 --help 查看帮助)"
|
|
43
|
+
exit 1
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
REPO_URL="${REPO_URL:-$DEFAULT_REPO}"
|
|
48
|
+
|
|
49
|
+
##############################################################################
|
|
50
|
+
# 工具函数
|
|
51
|
+
##############################################################################
|
|
52
|
+
json_get() {
|
|
53
|
+
local file="$1" expr="$2"
|
|
54
|
+
node -e "process.stdout.write(String((function(){$expr})(JSON.parse(require('fs').readFileSync('$file','utf8')))||''))" 2>/dev/null || true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
##############################################################################
|
|
58
|
+
# 环境检查
|
|
59
|
+
##############################################################################
|
|
60
|
+
printf "%b\n" "\033[32m=========================================\033[0m"
|
|
61
|
+
printf "%b\n" "\033[32m QQBot 拉取最新源码并更新\033[0m"
|
|
62
|
+
printf "%b\n" "\033[32m=========================================\033[0m"
|
|
63
|
+
echo ""
|
|
64
|
+
|
|
65
|
+
# 检查必要命令
|
|
66
|
+
for dep in node npm git; do
|
|
67
|
+
if ! command -v "$dep" &>/dev/null; then
|
|
68
|
+
printf "%b\n" "\033[31m❌ 未检测到 $dep,请先安装\033[0m"
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
printf "%b\n" "\033[34m系统信息:\033[0m"
|
|
74
|
+
echo " macOS $(sw_vers -productVersion 2>/dev/null || echo '未知')"
|
|
75
|
+
echo " Node $(node -v)"
|
|
76
|
+
echo " npm $(npm -v)"
|
|
77
|
+
echo " Git $(git --version 2>/dev/null | awk '{print $3}')"
|
|
78
|
+
echo " 仓库 $REPO_URL"
|
|
79
|
+
echo " 分支 $BRANCH"
|
|
80
|
+
|
|
81
|
+
# 检测 CLI
|
|
82
|
+
CMD=""
|
|
83
|
+
for name in "${SUPPORTED_CLIS[@]}"; do
|
|
84
|
+
if command -v "$name" &>/dev/null; then
|
|
85
|
+
CMD="$name"
|
|
86
|
+
break
|
|
87
|
+
fi
|
|
88
|
+
done
|
|
89
|
+
if [ -z "$CMD" ]; then
|
|
90
|
+
printf "%b\n" "\033[31m❌ 未找到 openclaw / clawdbot / moltbot 命令,请先安装其中之一\033[0m"
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
echo " CLI $CMD ($($CMD --version 2>/dev/null || echo '未知版本'))"
|
|
94
|
+
|
|
95
|
+
APP_HOME="$HOME/.$CMD"
|
|
96
|
+
APP_CONFIG="$APP_HOME/$CMD.json"
|
|
97
|
+
|
|
98
|
+
##############################################################################
|
|
99
|
+
# 定位插件目录
|
|
100
|
+
##############################################################################
|
|
101
|
+
PROJ_DIR=""
|
|
102
|
+
FRESH_INSTALL=false
|
|
103
|
+
|
|
104
|
+
for app in "${SUPPORTED_CLIS[@]}"; do
|
|
105
|
+
for plugin_dir in openclaw-qqbot qqbot openclaw-qq; do
|
|
106
|
+
ext_dir="$HOME/.$app/extensions/$plugin_dir"
|
|
107
|
+
if [ -d "$ext_dir" ] && [ -f "$ext_dir/package.json" ]; then
|
|
108
|
+
PROJ_DIR="$ext_dir"
|
|
109
|
+
break 2
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
done
|
|
113
|
+
|
|
114
|
+
if [ -z "$PROJ_DIR" ]; then
|
|
115
|
+
PROJ_DIR="$APP_HOME/extensions/openclaw-qqbot"
|
|
116
|
+
FRESH_INSTALL=true
|
|
117
|
+
echo " 插件 未安装(首次安装)"
|
|
118
|
+
else
|
|
119
|
+
echo " 插件 $PROJ_DIR"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
##############################################################################
|
|
123
|
+
# 第一步:获取当前版本
|
|
124
|
+
##############################################################################
|
|
125
|
+
echo ""
|
|
126
|
+
printf "%b\n" "\033[34m1. 获取当前版本...\033[0m"
|
|
127
|
+
|
|
128
|
+
LOCAL_VER=""
|
|
129
|
+
LOCAL_COMMIT=""
|
|
130
|
+
if [ "$FRESH_INSTALL" = true ]; then
|
|
131
|
+
echo " 首次安装,无本地版本"
|
|
132
|
+
else
|
|
133
|
+
[ -f "$PROJ_DIR/package.json" ] && LOCAL_VER=$(json_get "$PROJ_DIR/package.json" "c => c.version")
|
|
134
|
+
[ -d "$PROJ_DIR/.git" ] && LOCAL_COMMIT=$(cd "$PROJ_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "")
|
|
135
|
+
echo " 当前版本: ${LOCAL_VER:-未知}${LOCAL_COMMIT:+ (${LOCAL_COMMIT})}"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
##############################################################################
|
|
139
|
+
# 第二步:备份通道配置
|
|
140
|
+
##############################################################################
|
|
141
|
+
echo ""
|
|
142
|
+
printf "%b\n" "\033[34m2. 备份通道配置...\033[0m"
|
|
143
|
+
|
|
144
|
+
SAVED_CHANNELS_JSON=""
|
|
145
|
+
for app in "${SUPPORTED_CLIS[@]}"; do
|
|
146
|
+
cfg="$HOME/.$app/$app.json"
|
|
147
|
+
[ -f "$cfg" ] || continue
|
|
148
|
+
SAVED_CHANNELS_JSON=$(node -e "
|
|
149
|
+
const cfg = JSON.parse(require('fs').readFileSync('$cfg', 'utf8'));
|
|
150
|
+
// 尝试所有可能的 channel key(原仓库 + 本仓库)
|
|
151
|
+
const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
|
|
152
|
+
for (const key of keys) {
|
|
153
|
+
const ch = cfg.channels && cfg.channels[key];
|
|
154
|
+
if (ch) { process.stdout.write(JSON.stringify(ch)); process.exit(0); }
|
|
155
|
+
}
|
|
156
|
+
" 2>/dev/null || true)
|
|
157
|
+
[ -n "$SAVED_CHANNELS_JSON" ] && break
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
if [ -n "$SAVED_CHANNELS_JSON" ]; then
|
|
161
|
+
echo " ✅ 已备份 qqbot 通道配置"
|
|
162
|
+
else
|
|
163
|
+
echo " ℹ️ 未找到已有通道配置"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
##############################################################################
|
|
167
|
+
# 第三步:拉取最新代码
|
|
168
|
+
##############################################################################
|
|
169
|
+
echo ""
|
|
170
|
+
printf "%b\n" "\033[34m3. 拉取最新代码...\033[0m"
|
|
171
|
+
|
|
172
|
+
TMP_DIR="${TMPDIR:-/tmp}/qqbot-update-$$"
|
|
173
|
+
cleanup() { rm -rf "$TMP_DIR" 2>/dev/null; }
|
|
174
|
+
trap cleanup EXIT INT TERM
|
|
175
|
+
|
|
176
|
+
if [ -d "$PROJ_DIR/.git" ] && [ "$FRESH_INSTALL" = false ]; then
|
|
177
|
+
cd "$PROJ_DIR"
|
|
178
|
+
|
|
179
|
+
# 有本地修改直接重置,插件目录不需要保留用户改动
|
|
180
|
+
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
|
|
181
|
+
echo " 检测到本地修改,自动重置..."
|
|
182
|
+
git checkout -- . 2>/dev/null
|
|
183
|
+
git clean -fd 2>/dev/null
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
echo " 切换到分支 $BRANCH..."
|
|
187
|
+
git fetch --all --prune 2>&1 | tail -3
|
|
188
|
+
git checkout "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null || true
|
|
189
|
+
git reset --hard "origin/$BRANCH" 2>/dev/null
|
|
190
|
+
|
|
191
|
+
REMOTE_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "")
|
|
192
|
+
NEW_VER=$(json_get "$PROJ_DIR/package.json" "c => c.version")
|
|
193
|
+
|
|
194
|
+
if [ -n "$LOCAL_COMMIT" ] && [ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ]; then
|
|
195
|
+
echo " ✅ 已是最新 ($LOCAL_VER, commit: $LOCAL_COMMIT),继续检查依赖..."
|
|
196
|
+
else
|
|
197
|
+
echo " 更新: ${LOCAL_COMMIT:-???} → ${REMOTE_COMMIT}"
|
|
198
|
+
git --no-pager log --oneline "${LOCAL_COMMIT}..HEAD" 2>/dev/null | head -10 || true
|
|
199
|
+
fi
|
|
200
|
+
else
|
|
201
|
+
rm -rf "$TMP_DIR"
|
|
202
|
+
echo " 克隆仓库..."
|
|
203
|
+
if ! git clone --branch "$BRANCH" --depth 1 "$REPO_URL" "$TMP_DIR" 2>&1 | tail -3; then
|
|
204
|
+
printf "%b\n" "\033[31m❌ Git clone 失败\033[0m"
|
|
205
|
+
echo ""
|
|
206
|
+
echo "请排查:"
|
|
207
|
+
echo " 1. 检查网络: curl -I https://github.com"
|
|
208
|
+
echo " 2. 检查仓库地址: $REPO_URL"
|
|
209
|
+
echo " 3. 如果是私有仓库,确认已配置 SSH key 或 token"
|
|
210
|
+
exit 1
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
mkdir -p "$PROJ_DIR"
|
|
214
|
+
rsync -a --delete --exclude 'node_modules' "$TMP_DIR/" "$PROJ_DIR/"
|
|
215
|
+
|
|
216
|
+
cd "$PROJ_DIR"
|
|
217
|
+
REMOTE_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "")
|
|
218
|
+
NEW_VER=$(json_get "$PROJ_DIR/package.json" "c => c.version")
|
|
219
|
+
echo " 已克隆到版本: ${NEW_VER:-未知} (${REMOTE_COMMIT})"
|
|
220
|
+
cleanup
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
NEW_VER="${NEW_VER:-未知}"
|
|
224
|
+
printf "%b\n" "\033[32m ✅ 代码已更新到 $NEW_VER\033[0m"
|
|
225
|
+
|
|
226
|
+
##############################################################################
|
|
227
|
+
# 第四步:安装依赖
|
|
228
|
+
##############################################################################
|
|
229
|
+
echo ""
|
|
230
|
+
printf "%b\n" "\033[34m4. 安装依赖...\033[0m"
|
|
231
|
+
|
|
232
|
+
cd "$PROJ_DIR"
|
|
233
|
+
if ! npm install --omit=dev 2>&1 | tail -5; then
|
|
234
|
+
printf "%b\n" "\033[31m❌ npm 依赖安装失败\033[0m"
|
|
235
|
+
echo ""
|
|
236
|
+
echo "请排查:"
|
|
237
|
+
echo " 1. 手动重试: cd $PROJ_DIR && npm install --omit=dev"
|
|
238
|
+
echo " 2. 清理后重试: rm -rf $PROJ_DIR/node_modules && npm install --omit=dev"
|
|
239
|
+
echo " 3. 切换镜像: npm config set registry https://registry.npmmirror.com/"
|
|
240
|
+
exit 1
|
|
241
|
+
fi
|
|
242
|
+
echo " ✅ 依赖安装完成"
|
|
243
|
+
|
|
244
|
+
##############################################################################
|
|
245
|
+
# 第五步:恢复配置 → 重启网关
|
|
246
|
+
##############################################################################
|
|
247
|
+
echo ""
|
|
248
|
+
printf "%b\n" "\033[34m5. 恢复配置并重启网关...\033[0m"
|
|
249
|
+
|
|
250
|
+
# 恢复通道配置
|
|
251
|
+
if [ -n "$SAVED_CHANNELS_JSON" ]; then
|
|
252
|
+
if node -e "
|
|
253
|
+
const fs = require('fs');
|
|
254
|
+
const cfg = JSON.parse(fs.readFileSync('$APP_CONFIG', 'utf8'));
|
|
255
|
+
cfg.channels = cfg.channels || {};
|
|
256
|
+
cfg.channels.qqbot = $SAVED_CHANNELS_JSON;
|
|
257
|
+
fs.writeFileSync('$APP_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
|
|
258
|
+
" 2>/dev/null; then
|
|
259
|
+
echo " ✅ 通道配置已恢复"
|
|
260
|
+
else
|
|
261
|
+
printf "%b\n" "\033[33m ⚠️ 通道配置写入失败,请手动检查: $APP_CONFIG\033[0m"
|
|
262
|
+
fi
|
|
263
|
+
elif [ "$FRESH_INSTALL" = true ]; then
|
|
264
|
+
echo ""
|
|
265
|
+
printf "%b\n" "\033[33m ⚠️ 首次安装,请配置 QQ Bot 凭据:\033[0m"
|
|
266
|
+
echo " $CMD channels add --channel qqbot --token 'YOUR_APPID:YOUR_SECRET'"
|
|
267
|
+
echo ""
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
# 停止旧 gateway
|
|
271
|
+
echo " 停止旧网关..."
|
|
272
|
+
$CMD gateway stop 2>/dev/null || true
|
|
273
|
+
sleep 1
|
|
274
|
+
|
|
275
|
+
# 强制杀占用端口的进程
|
|
276
|
+
PORT_PID=$(lsof -ti:"$GATEWAY_PORT" 2>/dev/null || true)
|
|
277
|
+
if [ -n "$PORT_PID" ]; then
|
|
278
|
+
printf "%b\n" "\033[33m ⚠️ 端口 $GATEWAY_PORT 仍被占用 (PID: $PORT_PID),强制终止...\033[0m"
|
|
279
|
+
kill -9 $PORT_PID 2>/dev/null || true
|
|
280
|
+
sleep 1
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
# 卸载 launchd 服务(防止自动拉起旧进程)
|
|
284
|
+
for svc in ai.openclaw.gateway ai.clawdbot.gateway ai.moltbot.gateway; do
|
|
285
|
+
launchctl bootout "gui/$(id -u)/$svc" 2>/dev/null || true
|
|
286
|
+
done
|
|
287
|
+
|
|
288
|
+
# 启动网关
|
|
289
|
+
echo " 启动网关..."
|
|
290
|
+
if $CMD gateway 2>&1; then
|
|
291
|
+
printf "%b\n" "\033[32m ✅ 网关已启动\033[0m"
|
|
292
|
+
else
|
|
293
|
+
echo ""
|
|
294
|
+
printf "%b\n" "\033[33m ⚠️ 网关启动失败(不影响已安装的插件)\033[0m"
|
|
295
|
+
echo ""
|
|
296
|
+
echo " 请手动启动:"
|
|
297
|
+
echo " 1. 安装服务: $CMD gateway install"
|
|
298
|
+
echo " 2. 启动网关: $CMD gateway"
|
|
299
|
+
echo " 3. 查看日志: $CMD logs --follow"
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
##############################################################################
|
|
303
|
+
# 完成
|
|
304
|
+
##############################################################################
|
|
305
|
+
echo ""
|
|
306
|
+
printf "%b\n" "\033[32m=========================================\033[0m"
|
|
307
|
+
printf "%b\n" "\033[32m ✅ QQBot 已更新到 ${NEW_VER}${REMOTE_COMMIT:+ (${REMOTE_COMMIT})}\033[0m"
|
|
308
|
+
[ -n "$LOCAL_VER" ] && printf "%b\n" "\033[32m (从 ${LOCAL_VER}${LOCAL_COMMIT:+ (${LOCAL_COMMIT})} 升级)\033[0m"
|
|
309
|
+
printf "%b\n" "\033[32m=========================================\033[0m"
|
|
310
|
+
echo ""
|
|
311
|
+
echo "常用命令:"
|
|
312
|
+
echo " $CMD logs --follow # 跟踪日志"
|
|
313
|
+
echo " $CMD gateway restart # 重启服务"
|
|
314
|
+
echo " $CMD plugins list # 查看插件列表"
|
|
315
|
+
echo " cd $PROJ_DIR && git log # 查看更新历史"
|
|
316
|
+
echo "========================================="
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env npx ts-node
|
|
2
|
+
/**
|
|
3
|
+
* QQBot 主动消息 CLI 工具
|
|
4
|
+
*
|
|
5
|
+
* 使用示例:
|
|
6
|
+
* # 发送私聊消息
|
|
7
|
+
* npx ts-node scripts/send-proactive.ts --to "用户openid" --text "你好!"
|
|
8
|
+
*
|
|
9
|
+
* # 发送群聊消息
|
|
10
|
+
* npx ts-node scripts/send-proactive.ts --to "群组openid" --type group --text "群公告"
|
|
11
|
+
*
|
|
12
|
+
* # 列出已知用户
|
|
13
|
+
* npx ts-node scripts/send-proactive.ts --list
|
|
14
|
+
*
|
|
15
|
+
* # 列出群聊用户
|
|
16
|
+
* npx ts-node scripts/send-proactive.ts --list --type group
|
|
17
|
+
*
|
|
18
|
+
* # 广播消息
|
|
19
|
+
* npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --type c2c --limit 10
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
sendProactiveMessageDirect,
|
|
24
|
+
listKnownUsers,
|
|
25
|
+
getKnownUsersStats,
|
|
26
|
+
broadcastMessage,
|
|
27
|
+
} from "../src/proactive.js";
|
|
28
|
+
import type { ResolvedQQBotAccount } from "../src/types.js";
|
|
29
|
+
import * as fs from "node:fs";
|
|
30
|
+
import * as path from "node:path";
|
|
31
|
+
|
|
32
|
+
// 解析命令行参数
|
|
33
|
+
function parseArgs(): Record<string, string | boolean> {
|
|
34
|
+
const args: Record<string, string | boolean> = {};
|
|
35
|
+
const argv = process.argv.slice(2);
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < argv.length; i++) {
|
|
38
|
+
const arg = argv[i];
|
|
39
|
+
if (arg.startsWith("--")) {
|
|
40
|
+
const key = arg.slice(2);
|
|
41
|
+
const nextArg = argv[i + 1];
|
|
42
|
+
if (nextArg && !nextArg.startsWith("--")) {
|
|
43
|
+
args[key] = nextArg;
|
|
44
|
+
i++;
|
|
45
|
+
} else {
|
|
46
|
+
args[key] = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return args;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 从配置文件加载账户信息
|
|
55
|
+
function loadAccount(accountId = "default"): ResolvedQQBotAccount | null {
|
|
56
|
+
const configPath = path.join(process.env.HOME || "/home/ubuntu", "clawd", "config.json");
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
60
|
+
// 尝试从环境变量获取
|
|
61
|
+
const appId = process.env.QQBOT_APP_ID;
|
|
62
|
+
const clientSecret = process.env.QQBOT_CLIENT_SECRET;
|
|
63
|
+
|
|
64
|
+
if (appId && clientSecret) {
|
|
65
|
+
return {
|
|
66
|
+
accountId,
|
|
67
|
+
appId,
|
|
68
|
+
clientSecret,
|
|
69
|
+
enabled: true,
|
|
70
|
+
secretSource: "env",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.error("配置文件不存在且环境变量未设置");
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
79
|
+
const qqbot = config.channels?.qqbot;
|
|
80
|
+
|
|
81
|
+
if (!qqbot) {
|
|
82
|
+
console.error("配置中没有 qqbot 配置");
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 解析账户配置
|
|
87
|
+
if (accountId === "default") {
|
|
88
|
+
return {
|
|
89
|
+
accountId: "default",
|
|
90
|
+
appId: qqbot.appId || process.env.QQBOT_APP_ID,
|
|
91
|
+
clientSecret: qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
|
|
92
|
+
enabled: qqbot.enabled ?? true,
|
|
93
|
+
secretSource: qqbot.clientSecret ? "config" : "env",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const accountConfig = qqbot.accounts?.[accountId];
|
|
98
|
+
if (accountConfig) {
|
|
99
|
+
return {
|
|
100
|
+
accountId,
|
|
101
|
+
appId: accountConfig.appId || qqbot.appId || process.env.QQBOT_APP_ID,
|
|
102
|
+
clientSecret: accountConfig.clientSecret || qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
|
|
103
|
+
enabled: accountConfig.enabled ?? true,
|
|
104
|
+
secretSource: accountConfig.clientSecret ? "config" : "env",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.error(`账户 ${accountId} 不存在`);
|
|
109
|
+
return null;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(`加载配置失败: ${err}`);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main() {
|
|
117
|
+
const args = parseArgs();
|
|
118
|
+
|
|
119
|
+
// 显示帮助
|
|
120
|
+
if (args.help || args.h) {
|
|
121
|
+
console.log(`
|
|
122
|
+
QQBot 主动消息 CLI 工具
|
|
123
|
+
|
|
124
|
+
用法:
|
|
125
|
+
npx ts-node scripts/send-proactive.ts [选项]
|
|
126
|
+
|
|
127
|
+
选项:
|
|
128
|
+
--to <openid> 目标用户或群组的 openid
|
|
129
|
+
--text <message> 要发送的消息内容
|
|
130
|
+
--type <type> 消息类型: c2c (私聊) 或 group (群聊),默认 c2c
|
|
131
|
+
--account <id> 账户 ID,默认 default
|
|
132
|
+
|
|
133
|
+
--list 列出已知用户
|
|
134
|
+
--stats 显示用户统计
|
|
135
|
+
--broadcast 广播消息给所有已知用户
|
|
136
|
+
--limit <n> 限制数量
|
|
137
|
+
|
|
138
|
+
--help, -h 显示帮助
|
|
139
|
+
|
|
140
|
+
示例:
|
|
141
|
+
# 发送私聊消息
|
|
142
|
+
npx ts-node scripts/send-proactive.ts --to "0Eda5EA7-xxx" --text "你好!"
|
|
143
|
+
|
|
144
|
+
# 发送群聊消息
|
|
145
|
+
npx ts-node scripts/send-proactive.ts --to "A1B2C3D4" --type group --text "群公告"
|
|
146
|
+
|
|
147
|
+
# 列出最近 10 个私聊用户
|
|
148
|
+
npx ts-node scripts/send-proactive.ts --list --type c2c --limit 10
|
|
149
|
+
|
|
150
|
+
# 广播消息
|
|
151
|
+
npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --limit 5
|
|
152
|
+
`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const accountId = (args.account as string) || "default";
|
|
157
|
+
const type = (args.type as "c2c" | "group") || "c2c";
|
|
158
|
+
const limit = args.limit ? parseInt(args.limit as string, 10) : undefined;
|
|
159
|
+
|
|
160
|
+
// 列出已知用户
|
|
161
|
+
if (args.list) {
|
|
162
|
+
const users = listKnownUsers({
|
|
163
|
+
type: args.type as "c2c" | "group" | "channel" | undefined,
|
|
164
|
+
accountId: args.account as string | undefined,
|
|
165
|
+
limit,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (users.length === 0) {
|
|
169
|
+
console.log("没有已知用户");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(`\n已知用户列表 (共 ${users.length} 个):\n`);
|
|
174
|
+
console.log("类型\t\tOpenID\t\t\t\t\t\t昵称\t\t最后交互时间");
|
|
175
|
+
console.log("─".repeat(100));
|
|
176
|
+
|
|
177
|
+
for (const user of users) {
|
|
178
|
+
const lastTime = new Date(user.lastInteractionAt).toLocaleString();
|
|
179
|
+
console.log(`${user.type}\t\t${user.openid.slice(0, 20)}...\t${user.nickname || "-"}\t\t${lastTime}`);
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 显示统计
|
|
185
|
+
if (args.stats) {
|
|
186
|
+
const stats = getKnownUsersStats(args.account as string | undefined);
|
|
187
|
+
console.log(`\n用户统计:`);
|
|
188
|
+
console.log(` 总计: ${stats.total}`);
|
|
189
|
+
console.log(` 私聊: ${stats.c2c}`);
|
|
190
|
+
console.log(` 群聊: ${stats.group}`);
|
|
191
|
+
console.log(` 频道: ${stats.channel}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 广播消息
|
|
196
|
+
if (args.broadcast) {
|
|
197
|
+
if (!args.text) {
|
|
198
|
+
console.error("请指定消息内容 (--text)");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 加载配置用于广播
|
|
203
|
+
const configPath = detectConfigPath();
|
|
204
|
+
let cfg: Record<string, unknown> = {};
|
|
205
|
+
try {
|
|
206
|
+
if (configPath && fs.existsSync(configPath)) {
|
|
207
|
+
cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
208
|
+
}
|
|
209
|
+
} catch {}
|
|
210
|
+
|
|
211
|
+
console.log(`\n开始广播消息...\n`);
|
|
212
|
+
const result = await broadcastMessage(args.text as string, cfg as any, {
|
|
213
|
+
type,
|
|
214
|
+
accountId,
|
|
215
|
+
limit,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
console.log(`\n广播完成:`);
|
|
219
|
+
console.log(` 发送总数: ${result.total}`);
|
|
220
|
+
console.log(` 成功: ${result.success}`);
|
|
221
|
+
console.log(` 失败: ${result.failed}`);
|
|
222
|
+
|
|
223
|
+
if (result.failed > 0) {
|
|
224
|
+
console.log(`\n失败详情:`);
|
|
225
|
+
for (const r of result.results) {
|
|
226
|
+
if (!r.result.success) {
|
|
227
|
+
console.log(` ${r.to}: ${r.result.error}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 发送单条消息
|
|
235
|
+
if (args.to && args.text) {
|
|
236
|
+
const account = loadAccount(accountId);
|
|
237
|
+
if (!account) {
|
|
238
|
+
console.error("无法加载账户配置");
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log(`\n发送消息...`);
|
|
243
|
+
console.log(` 目标: ${args.to}`);
|
|
244
|
+
console.log(` 类型: ${type}`);
|
|
245
|
+
console.log(` 内容: ${args.text}`);
|
|
246
|
+
|
|
247
|
+
const result = await sendProactiveMessageDirect(
|
|
248
|
+
account,
|
|
249
|
+
args.to as string,
|
|
250
|
+
args.text as string,
|
|
251
|
+
type
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
if (result.success) {
|
|
255
|
+
console.log(`\n✅ 发送成功!`);
|
|
256
|
+
console.log(` 消息ID: ${result.messageId}`);
|
|
257
|
+
console.log(` 时间戳: ${result.timestamp}`);
|
|
258
|
+
} else {
|
|
259
|
+
console.log(`\n❌ 发送失败: ${result.error}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 没有有效参数
|
|
266
|
+
console.error("请指定操作。使用 --help 查看帮助。");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
main().catch((err) => {
|
|
271
|
+
console.error(`执行失败: ${err}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
});
|