@tencent-connect/openclaw-qqbot 1.5.6 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -146
- package/README.zh.md +46 -146
- package/bin/qqbot-cli.js +6 -6
- package/dist/AI/345/210/233/346/226/260/345/272/224/347/224/250/345/245/226_/347/224/263/346/212/245/344/271/246.md +211 -0
- package/dist/src/gateway.js +109 -92
- package/dist/src/slash-commands.d.ts +48 -0
- package/dist/src/slash-commands.js +212 -0
- package/dist/src/utils/audio-convert.d.ts +0 -6
- package/dist/src/utils/audio-convert.js +0 -89
- package/package.json +1 -1
- package/scripts/{upgrade.sh → cleanup-legacy-plugins.sh} +3 -3
- package/scripts/set-markdown.sh +20 -20
- package/scripts/upgrade-via-npm.sh +204 -0
- package/scripts/{upgrade-and-run.sh → upgrade-via-source.sh} +60 -44
- package/src/api.ts +104 -24
- package/src/channel.ts +2 -1
- package/src/gateway.ts +229 -33
- package/src/image-server.ts +5 -2
- package/src/outbound.ts +32 -26
- package/src/ref-index-store.ts +358 -0
- package/src/types.ts +6 -0
- package/src/utils/platform.ts +16 -2
- package/scripts/draw_arch.py +0 -174
- package/scripts/npm-upgrade.sh +0 -120
- package/scripts/pull-latest.sh +0 -316
package/scripts/draw_arch.py
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Generate QQBot + OpenClaw architecture diagram for product managers."""
|
|
3
|
-
|
|
4
|
-
import matplotlib
|
|
5
|
-
matplotlib.use('Agg')
|
|
6
|
-
import matplotlib.pyplot as plt
|
|
7
|
-
import matplotlib.patches as mpatches
|
|
8
|
-
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
|
|
9
|
-
import numpy as np
|
|
10
|
-
|
|
11
|
-
# ── 中文字体 ──
|
|
12
|
-
plt.rcParams['font.sans-serif'] = ['PingFang SC', 'Heiti SC', 'STHeiti', 'SimHei', 'Arial Unicode MS']
|
|
13
|
-
plt.rcParams['axes.unicode_minus'] = False
|
|
14
|
-
|
|
15
|
-
fig, ax = plt.subplots(1, 1, figsize=(20, 14))
|
|
16
|
-
ax.set_xlim(0, 20)
|
|
17
|
-
ax.set_ylim(0, 14)
|
|
18
|
-
ax.axis('off')
|
|
19
|
-
fig.patch.set_facecolor('#FAFBFC')
|
|
20
|
-
|
|
21
|
-
# ── 颜色 ──
|
|
22
|
-
C = {
|
|
23
|
-
'user': '#4A90D9',
|
|
24
|
-
'qq': '#12B7F5',
|
|
25
|
-
'plugin': '#FF6B35',
|
|
26
|
-
'openclaw':'#6C5CE7',
|
|
27
|
-
'ai': '#00B894',
|
|
28
|
-
'skill': '#FDCB6E',
|
|
29
|
-
'white': '#FFFFFF',
|
|
30
|
-
'text': '#2D3436',
|
|
31
|
-
'light': '#F0F0F5',
|
|
32
|
-
'border': '#DFE6E9',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
def rounded_box(x, y, w, h, color, label, sublabel=None, fontsize=13, icon=None, alpha=0.95):
|
|
36
|
-
box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.15",
|
|
37
|
-
facecolor=color, edgecolor='white', linewidth=2, alpha=alpha, zorder=2)
|
|
38
|
-
ax.add_patch(box)
|
|
39
|
-
cy = y + h/2
|
|
40
|
-
if sublabel:
|
|
41
|
-
cy += 0.15
|
|
42
|
-
txt = f"{icon} {label}" if icon else label
|
|
43
|
-
ax.text(x + w/2, cy, txt, ha='center', va='center', fontsize=fontsize,
|
|
44
|
-
fontweight='bold', color='white' if color not in [C['white'], C['light'], C['skill']] else C['text'], zorder=3)
|
|
45
|
-
if sublabel:
|
|
46
|
-
ax.text(x + w/2, cy - 0.35, sublabel, ha='center', va='center', fontsize=9,
|
|
47
|
-
color='#ffffffbb' if color not in [C['white'], C['light'], C['skill']] else '#636e72', zorder=3)
|
|
48
|
-
|
|
49
|
-
def section_box(x, y, w, h, color, label, fontsize=11):
|
|
50
|
-
box = FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.1",
|
|
51
|
-
facecolor=color + '15', edgecolor=color, linewidth=1.5, linestyle='--', zorder=1)
|
|
52
|
-
ax.add_patch(box)
|
|
53
|
-
ax.text(x + 0.15, y + h - 0.25, label, ha='left', va='center', fontsize=fontsize,
|
|
54
|
-
fontweight='bold', color=color, zorder=3)
|
|
55
|
-
|
|
56
|
-
def arrow(x1, y1, x2, y2, color='#636e72', style='->', label=None, lw=2):
|
|
57
|
-
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
|
|
58
|
-
arrowprops=dict(arrowstyle=style, color=color, lw=lw, connectionstyle="arc3,rad=0"),
|
|
59
|
-
zorder=4)
|
|
60
|
-
if label:
|
|
61
|
-
mx, my = (x1+x2)/2, (y1+y2)/2
|
|
62
|
-
ax.text(mx, my + 0.2, label, ha='center', va='center', fontsize=8, color=color,
|
|
63
|
-
bbox=dict(boxstyle='round,pad=0.2', facecolor='white', edgecolor='none', alpha=0.9), zorder=5)
|
|
64
|
-
|
|
65
|
-
def double_arrow(x1, y1, x2, y2, color='#636e72', label=None, lw=2):
|
|
66
|
-
arrow(x1, y1, x2, y2, color=color, style='<->', label=label, lw=lw)
|
|
67
|
-
|
|
68
|
-
# ══════════════════════════════════════════════
|
|
69
|
-
# 标题
|
|
70
|
-
# ══════════════════════════════════════════════
|
|
71
|
-
ax.text(10, 13.5, 'OpenClaw QQBot 插件 · 系统架构', ha='center', va='center',
|
|
72
|
-
fontsize=22, fontweight='bold', color=C['text'])
|
|
73
|
-
ax.text(10, 13.1, '让 AI 助手通过 QQ 与用户对话', ha='center', va='center',
|
|
74
|
-
fontsize=12, color='#636e72')
|
|
75
|
-
|
|
76
|
-
# ══════════════════════════════════════════════
|
|
77
|
-
# 第一层:用户侧
|
|
78
|
-
# ══════════════════════════════════════════════
|
|
79
|
-
rounded_box(1, 11.2, 2.5, 1.2, C['user'], '👤 QQ 用户', '私聊 / 群聊 / 频道', fontsize=14)
|
|
80
|
-
rounded_box(5, 11.2, 2.5, 1.2, C['user'], '👥 QQ 群', '@ 机器人触发', fontsize=14)
|
|
81
|
-
rounded_box(9, 11.2, 2.5, 1.2, C['user'], '📢 QQ 频道', '公域/私域频道', fontsize=14)
|
|
82
|
-
|
|
83
|
-
# ══════════════════════════════════════════════
|
|
84
|
-
# 第二层:QQ 平台
|
|
85
|
-
# ══════════════════════════════════════════════
|
|
86
|
-
section_box(0.5, 9, 12, 1.8, C['qq'], '')
|
|
87
|
-
rounded_box(3, 9.3, 6, 1.2, C['qq'], '🐧 QQ 机器人平台', 'WebSocket 长连接 + HTTP API', fontsize=15)
|
|
88
|
-
|
|
89
|
-
# 用户 → QQ 平台
|
|
90
|
-
arrow(2.25, 11.2, 4.5, 10.5, C['qq'], label='发消息')
|
|
91
|
-
arrow(6.25, 11.2, 6, 10.5, C['qq'])
|
|
92
|
-
arrow(10.25, 11.2, 7.5, 10.5, C['qq'])
|
|
93
|
-
|
|
94
|
-
# ══════════════════════════════════════════════
|
|
95
|
-
# 第三层:QQBot 插件(核心)
|
|
96
|
-
# ══════════════════════════════════════════════
|
|
97
|
-
section_box(0.5, 5.2, 18.5, 3.5, C['plugin'], 'QQBot 渠道插件 (@tencent-connect/openclaw-qqbot)')
|
|
98
|
-
|
|
99
|
-
# 左侧:网关
|
|
100
|
-
rounded_box(1, 6.8, 3, 1.2, C['plugin'], '🔌 WebSocket 网关', '连接/心跳/重连/Resume', fontsize=11)
|
|
101
|
-
# 中间:消息处理
|
|
102
|
-
rounded_box(4.5, 6.8, 3.5, 1.2, C['plugin'], '📨 消息处理引擎', '收发/分块/限流/队列', fontsize=11)
|
|
103
|
-
# 右侧:富媒体
|
|
104
|
-
rounded_box(8.5, 6.8, 3, 1.2, C['plugin'], '🎨 富媒体处理', '图片/语音/视频/文件', fontsize=11)
|
|
105
|
-
|
|
106
|
-
# 下排
|
|
107
|
-
rounded_box(1, 5.5, 2.5, 1, '#E17055', '🔑 多账户管理', '独立Token/连接', fontsize=9)
|
|
108
|
-
rounded_box(3.8, 5.5, 2.5, 1, '#E17055', '📢 主动消息', '推送/广播/定时', fontsize=9)
|
|
109
|
-
rounded_box(6.6, 5.5, 2.5, 1, '#E17055', '🎙️ 语音处理', 'STT转文字/TTS合成', fontsize=9)
|
|
110
|
-
rounded_box(9.4, 5.5, 2.5, 1, '#E17055', '🖼️ 本地图床', '自动上传/去重缓存', fontsize=9)
|
|
111
|
-
|
|
112
|
-
# 右侧能力标签
|
|
113
|
-
rounded_box(12.2, 7.5, 6.3, 1, C['skill'], '⭐ 核心能力', fontsize=12)
|
|
114
|
-
capabilities = [
|
|
115
|
-
'✅ 私聊 / 群聊 / 频道 三场景',
|
|
116
|
-
'✅ 图片·语音·视频·文件 收发',
|
|
117
|
-
'✅ 语音转文字 (STT) + 文字转语音 (TTS)',
|
|
118
|
-
'✅ 多机器人同时在线',
|
|
119
|
-
'✅ 定时提醒 & 主动推送',
|
|
120
|
-
'✅ 断线自动重连 & Session 恢复',
|
|
121
|
-
'✅ 30+ 标签变体自动纠错',
|
|
122
|
-
'✅ 跨平台 Mac/Linux/Windows',
|
|
123
|
-
]
|
|
124
|
-
for i, cap in enumerate(capabilities):
|
|
125
|
-
ax.text(12.4, 7.2 - i * 0.28, cap, ha='left', va='center', fontsize=8.5,
|
|
126
|
-
color=C['text'], zorder=3)
|
|
127
|
-
|
|
128
|
-
# QQ平台 → 插件
|
|
129
|
-
double_arrow(6, 9.3, 6, 8.0, C['qq'], label='WebSocket', lw=2.5)
|
|
130
|
-
|
|
131
|
-
# ══════════════════════════════════════════════
|
|
132
|
-
# 第四层:OpenClaw 框架
|
|
133
|
-
# ══════════════════════════════════════════════
|
|
134
|
-
section_box(0.5, 1.5, 12, 3.4, C['openclaw'], 'OpenClaw AI 助手框架')
|
|
135
|
-
|
|
136
|
-
rounded_box(1, 3.2, 2.8, 1.2, C['openclaw'], '🧠 对话管理', '上下文/多轮/记忆', fontsize=11)
|
|
137
|
-
rounded_box(4.2, 3.2, 2.8, 1.2, C['openclaw'], '🔧 工具系统', 'Function Calling', fontsize=11)
|
|
138
|
-
rounded_box(7.4, 3.2, 2.8, 1.2, C['openclaw'], '⏰ 定时任务', 'Cron 调度器', fontsize=11)
|
|
139
|
-
|
|
140
|
-
rounded_box(1, 1.8, 3.5, 1.1, C['openclaw'], '📚 Skills 技能', 'qqbot-media / qqbot-cron', fontsize=10)
|
|
141
|
-
rounded_box(5, 1.8, 3, 1.1, C['openclaw'], '🔌 插件系统', '渠道/工具插件', fontsize=10)
|
|
142
|
-
rounded_box(8.5, 1.8, 3.5, 1.1, C['openclaw'], '⚙️ 配置管理', 'openclaw.json', fontsize=10)
|
|
143
|
-
|
|
144
|
-
# 插件 → OpenClaw
|
|
145
|
-
double_arrow(4, 5.5, 4, 4.4, C['openclaw'], label='消息桥接', lw=2.5)
|
|
146
|
-
|
|
147
|
-
# ══════════════════════════════════════════════
|
|
148
|
-
# 右侧:AI 模型层
|
|
149
|
-
# ══════════════════════════════════════════════
|
|
150
|
-
section_box(13, 1.5, 6, 3.4, C['ai'], 'AI 模型层(可替换)')
|
|
151
|
-
|
|
152
|
-
rounded_box(13.5, 3.5, 5, 0.85, C['ai'], '🤖 Claude / GPT / DeepSeek / 混元 ...', fontsize=10)
|
|
153
|
-
rounded_box(13.5, 2.5, 2.3, 0.8, '#00A884', '💬 对话', fontsize=10)
|
|
154
|
-
rounded_box(16.2, 2.5, 2.3, 0.8, '#00A884', '🎨 画图', fontsize=10)
|
|
155
|
-
rounded_box(13.5, 1.7, 2.3, 0.65, '#00A884', '🔍 搜索', fontsize=9)
|
|
156
|
-
rounded_box(16.2, 1.7, 2.3, 0.65, '#00A884', '📝 写作', fontsize=9)
|
|
157
|
-
|
|
158
|
-
# OpenClaw → AI
|
|
159
|
-
double_arrow(10.3, 3.2, 13.5, 3.2, C['ai'], label='API 调用', lw=2.5)
|
|
160
|
-
|
|
161
|
-
# ══════════════════════════════════════════════
|
|
162
|
-
# 流程标注
|
|
163
|
-
# ══════════════════════════════════════════════
|
|
164
|
-
ax.text(10, 0.7, '💡 用户在 QQ 发消息 → QQBot 插件接收 → 交给 OpenClaw → AI 模型回复 → 通过 QQ 返回给用户',
|
|
165
|
-
ha='center', va='center', fontsize=11, color='#636e72', style='italic',
|
|
166
|
-
bbox=dict(boxstyle='round,pad=0.5', facecolor='#f8f9fa', edgecolor=C['border'], linewidth=1))
|
|
167
|
-
|
|
168
|
-
ax.text(10, 0.2, 'v1.5.5 · @tencent-connect/openclaw-qqbot · MIT License',
|
|
169
|
-
ha='center', va='center', fontsize=9, color='#b2bec3')
|
|
170
|
-
|
|
171
|
-
plt.tight_layout(pad=0.5)
|
|
172
|
-
plt.savefig('/Users/lishoushuai/tmp/qqbot/docs/images/architecture.png', dpi=200, bbox_inches='tight',
|
|
173
|
-
facecolor='#FAFBFC', edgecolor='none')
|
|
174
|
-
print("Architecture diagram saved to docs/images/architecture.png")
|
package/scripts/npm-upgrade.sh
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# QQBot 通过 npm 包升级
|
|
4
|
-
#
|
|
5
|
-
# 用法:
|
|
6
|
-
# npm-upgrade.sh # 升级到 latest(默认)
|
|
7
|
-
# npm-upgrade.sh --tag alpha # 升级到 alpha
|
|
8
|
-
# npm-upgrade.sh --version 1.0.0-alpha.0 # 升级到指定版本
|
|
9
|
-
|
|
10
|
-
set -eo pipefail
|
|
11
|
-
|
|
12
|
-
PKG_NAME="@tencent-connect/openclaw-qqbot"
|
|
13
|
-
INSTALL_SRC=""
|
|
14
|
-
|
|
15
|
-
while [[ $# -gt 0 ]]; do
|
|
16
|
-
case "$1" in
|
|
17
|
-
--tag) INSTALL_SRC="${PKG_NAME}@$2"; shift 2 ;;
|
|
18
|
-
--version) INSTALL_SRC="${PKG_NAME}@$2"; shift 2 ;;
|
|
19
|
-
-h|--help)
|
|
20
|
-
echo "用法:"
|
|
21
|
-
echo " npm-upgrade.sh # 升级到 latest(默认)"
|
|
22
|
-
echo " npm-upgrade.sh --tag alpha # 升级到 alpha"
|
|
23
|
-
echo " npm-upgrade.sh --version 1.0.0-alpha.0 # 升级到指定版本"
|
|
24
|
-
exit 0
|
|
25
|
-
;;
|
|
26
|
-
*) echo "未知选项: $1"; exit 1 ;;
|
|
27
|
-
esac
|
|
28
|
-
done
|
|
29
|
-
INSTALL_SRC="${INSTALL_SRC:-${PKG_NAME}@latest}"
|
|
30
|
-
|
|
31
|
-
# 检测 CLI
|
|
32
|
-
CMD=""
|
|
33
|
-
for name in openclaw clawdbot moltbot; do
|
|
34
|
-
command -v "$name" &>/dev/null && CMD="$name" && break
|
|
35
|
-
done
|
|
36
|
-
[ -z "$CMD" ] && echo "❌ 未找到 openclaw / clawdbot / moltbot" && exit 1
|
|
37
|
-
|
|
38
|
-
APP_CONFIG="$HOME/.$CMD/$CMD.json"
|
|
39
|
-
EXTENSIONS_DIR="$HOME/.$CMD/extensions"
|
|
40
|
-
|
|
41
|
-
echo "==========================================="
|
|
42
|
-
echo " QQBot npm 升级: $INSTALL_SRC"
|
|
43
|
-
echo "==========================================="
|
|
44
|
-
echo ""
|
|
45
|
-
|
|
46
|
-
# [1/4] 备份并临时移除通道配置(避免 plugins install 因 unknown channel 拒绝执行)
|
|
47
|
-
echo "[1/4] 备份通道配置..."
|
|
48
|
-
if [ -f "$APP_CONFIG" ]; then
|
|
49
|
-
node -e "
|
|
50
|
-
const fs = require('fs');
|
|
51
|
-
const cfg = JSON.parse(fs.readFileSync('$APP_CONFIG', 'utf8'));
|
|
52
|
-
const keys = ['qqbot', 'openclaw-qqbot', 'openclaw-qq'];
|
|
53
|
-
let saved = null;
|
|
54
|
-
for (const key of keys) {
|
|
55
|
-
const ch = cfg.channels && cfg.channels[key];
|
|
56
|
-
if (ch) { saved = ch; delete cfg.channels[key]; break; }
|
|
57
|
-
}
|
|
58
|
-
// 清理 plugins.entries 中的旧记录(避免 stale config entry 告警)
|
|
59
|
-
if (cfg.plugins && cfg.plugins.entries) {
|
|
60
|
-
delete cfg.plugins.entries['openclaw-qqbot'];
|
|
61
|
-
}
|
|
62
|
-
if (saved) {
|
|
63
|
-
fs.writeFileSync('$APP_CONFIG.qqbot-backup.json', JSON.stringify(saved, null, 2));
|
|
64
|
-
fs.writeFileSync('$APP_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
|
|
65
|
-
console.log(' ✅ 已备份并临时移除 channels.qqbot');
|
|
66
|
-
} else {
|
|
67
|
-
console.log(' ℹ️ 无已有通道配置');
|
|
68
|
-
}
|
|
69
|
-
" 2>/dev/null || echo " ⚠️ 备份失败"
|
|
70
|
-
else
|
|
71
|
-
echo " ℹ️ 无配置文件"
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
# [2/4] 清理旧插件目录
|
|
75
|
-
echo ""
|
|
76
|
-
echo "[2/4] 清理旧插件..."
|
|
77
|
-
for old_plugin in openclaw-qqbot qqbot openclaw-qq @sliverp/qqbot @tencent-connect/qqbot @tencent-connect/openclaw-qq @tencent-connect/openclaw-qqbot; do
|
|
78
|
-
$CMD plugins uninstall "$old_plugin" 2>/dev/null && echo " 已卸载: $old_plugin" || true
|
|
79
|
-
done
|
|
80
|
-
for dir_name in openclaw-qqbot qqbot openclaw-qq; do
|
|
81
|
-
if [ -d "$EXTENSIONS_DIR/$dir_name" ]; then
|
|
82
|
-
rm -rf "$EXTENSIONS_DIR/$dir_name"
|
|
83
|
-
echo " 已清理残留目录: $dir_name"
|
|
84
|
-
fi
|
|
85
|
-
done
|
|
86
|
-
|
|
87
|
-
# [3/4] 安装新版本
|
|
88
|
-
echo ""
|
|
89
|
-
echo "[3/4] 安装新版本..."
|
|
90
|
-
$CMD plugins install "$INSTALL_SRC" 2>&1
|
|
91
|
-
|
|
92
|
-
# 恢复通道配置
|
|
93
|
-
BACKUP_FILE="$APP_CONFIG.qqbot-backup.json"
|
|
94
|
-
if [ -f "$BACKUP_FILE" ] && [ -f "$APP_CONFIG" ]; then
|
|
95
|
-
node -e "
|
|
96
|
-
const fs = require('fs');
|
|
97
|
-
const cfg = JSON.parse(fs.readFileSync('$APP_CONFIG', 'utf8'));
|
|
98
|
-
const saved = JSON.parse(fs.readFileSync('$BACKUP_FILE', 'utf8'));
|
|
99
|
-
cfg.channels = cfg.channels || {};
|
|
100
|
-
cfg.channels.qqbot = saved;
|
|
101
|
-
fs.writeFileSync('$APP_CONFIG', JSON.stringify(cfg, null, 4) + '\n');
|
|
102
|
-
fs.unlinkSync('$BACKUP_FILE');
|
|
103
|
-
console.log(' ✅ 通道配置已恢复');
|
|
104
|
-
" 2>/dev/null || echo " ⚠️ 通道配置恢复失败,请手动检查: $APP_CONFIG"
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
# [4/4] 重启网关
|
|
108
|
-
echo ""
|
|
109
|
-
echo "[4/4] 重启网关..."
|
|
110
|
-
$CMD gateway restart 2>&1 || true
|
|
111
|
-
|
|
112
|
-
echo ""
|
|
113
|
-
echo "==========================================="
|
|
114
|
-
echo " ✅ 升级完成"
|
|
115
|
-
echo "==========================================="
|
|
116
|
-
echo ""
|
|
117
|
-
echo "常用命令:"
|
|
118
|
-
echo " $CMD logs --follow # 跟踪日志"
|
|
119
|
-
echo " $CMD gateway restart # 重启服务"
|
|
120
|
-
echo " $CMD plugins list # 查看插件列表"
|
package/scripts/pull-latest.sh
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
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 "========================================="
|