@leoqlin/openclaw-qqbot 1.6.7-alpha1

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 (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +484 -0
  3. package/README.zh.md +479 -0
  4. package/bin/qqbot-cli.js +243 -0
  5. package/dist/index.d.ts +17 -0
  6. package/dist/index.js +26 -0
  7. package/dist/src/admin-resolver.d.ts +33 -0
  8. package/dist/src/admin-resolver.js +157 -0
  9. package/dist/src/api.d.ts +301 -0
  10. package/dist/src/api.js +890 -0
  11. package/dist/src/channel.d.ts +29 -0
  12. package/dist/src/channel.js +452 -0
  13. package/dist/src/config.d.ts +56 -0
  14. package/dist/src/config.js +278 -0
  15. package/dist/src/credential-backup.d.ts +31 -0
  16. package/dist/src/credential-backup.js +66 -0
  17. package/dist/src/deliver-debounce.d.ts +74 -0
  18. package/dist/src/deliver-debounce.js +174 -0
  19. package/dist/src/gateway.d.ts +18 -0
  20. package/dist/src/gateway.js +2005 -0
  21. package/dist/src/group-history.d.ts +136 -0
  22. package/dist/src/group-history.js +226 -0
  23. package/dist/src/image-server.d.ts +87 -0
  24. package/dist/src/image-server.js +570 -0
  25. package/dist/src/inbound-attachments.d.ts +60 -0
  26. package/dist/src/inbound-attachments.js +248 -0
  27. package/dist/src/known-users.d.ts +100 -0
  28. package/dist/src/known-users.js +263 -0
  29. package/dist/src/message-gating.d.ts +53 -0
  30. package/dist/src/message-gating.js +107 -0
  31. package/dist/src/message-queue.d.ts +89 -0
  32. package/dist/src/message-queue.js +257 -0
  33. package/dist/src/onboarding.d.ts +10 -0
  34. package/dist/src/onboarding.js +203 -0
  35. package/dist/src/outbound-deliver.d.ts +48 -0
  36. package/dist/src/outbound-deliver.js +392 -0
  37. package/dist/src/outbound.d.ts +205 -0
  38. package/dist/src/outbound.js +938 -0
  39. package/dist/src/proactive.d.ts +170 -0
  40. package/dist/src/proactive.js +399 -0
  41. package/dist/src/ref-index-store.d.ts +101 -0
  42. package/dist/src/ref-index-store.js +298 -0
  43. package/dist/src/reply-dispatcher.d.ts +35 -0
  44. package/dist/src/reply-dispatcher.js +311 -0
  45. package/dist/src/request-context.d.ts +25 -0
  46. package/dist/src/request-context.js +37 -0
  47. package/dist/src/runtime.d.ts +3 -0
  48. package/dist/src/runtime.js +10 -0
  49. package/dist/src/session-store.d.ts +52 -0
  50. package/dist/src/session-store.js +254 -0
  51. package/dist/src/slash-commands.d.ts +77 -0
  52. package/dist/src/slash-commands.js +1866 -0
  53. package/dist/src/startup-greeting.d.ts +30 -0
  54. package/dist/src/startup-greeting.js +97 -0
  55. package/dist/src/streaming.d.ts +247 -0
  56. package/dist/src/streaming.js +899 -0
  57. package/dist/src/stt.d.ts +21 -0
  58. package/dist/src/stt.js +70 -0
  59. package/dist/src/tools/channel.d.ts +16 -0
  60. package/dist/src/tools/channel.js +234 -0
  61. package/dist/src/tools/remind.d.ts +2 -0
  62. package/dist/src/tools/remind.js +256 -0
  63. package/dist/src/types.d.ts +367 -0
  64. package/dist/src/types.js +17 -0
  65. package/dist/src/typing-keepalive.d.ts +27 -0
  66. package/dist/src/typing-keepalive.js +64 -0
  67. package/dist/src/update-checker.d.ts +36 -0
  68. package/dist/src/update-checker.js +171 -0
  69. package/dist/src/utils/audio-convert.d.ts +98 -0
  70. package/dist/src/utils/audio-convert.js +755 -0
  71. package/dist/src/utils/chunked-upload.d.ts +68 -0
  72. package/dist/src/utils/chunked-upload.js +341 -0
  73. package/dist/src/utils/file-utils.d.ts +61 -0
  74. package/dist/src/utils/file-utils.js +172 -0
  75. package/dist/src/utils/image-size.d.ts +51 -0
  76. package/dist/src/utils/image-size.js +234 -0
  77. package/dist/src/utils/media-send.d.ts +158 -0
  78. package/dist/src/utils/media-send.js +499 -0
  79. package/dist/src/utils/media-tags.d.ts +14 -0
  80. package/dist/src/utils/media-tags.js +165 -0
  81. package/dist/src/utils/payload.d.ts +112 -0
  82. package/dist/src/utils/payload.js +186 -0
  83. package/dist/src/utils/pkg-version.d.ts +5 -0
  84. package/dist/src/utils/pkg-version.js +61 -0
  85. package/dist/src/utils/platform.d.ts +137 -0
  86. package/dist/src/utils/platform.js +390 -0
  87. package/dist/src/utils/ssrf-guard.d.ts +25 -0
  88. package/dist/src/utils/ssrf-guard.js +91 -0
  89. package/dist/src/utils/text-parsing.d.ts +36 -0
  90. package/dist/src/utils/text-parsing.js +75 -0
  91. package/dist/src/utils/upload-cache.d.ts +34 -0
  92. package/dist/src/utils/upload-cache.js +93 -0
  93. package/index.ts +31 -0
  94. package/node_modules/@eshaz/web-worker/LICENSE +201 -0
  95. package/node_modules/@eshaz/web-worker/README.md +134 -0
  96. package/node_modules/@eshaz/web-worker/browser.js +17 -0
  97. package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
  98. package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
  99. package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
  100. package/node_modules/@eshaz/web-worker/node.js +223 -0
  101. package/node_modules/@eshaz/web-worker/package.json +54 -0
  102. package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
  103. package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
  104. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
  105. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
  106. package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
  107. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
  108. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
  109. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
  110. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
  111. package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
  112. package/node_modules/mpg123-decoder/README.md +265 -0
  113. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
  114. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
  115. package/node_modules/mpg123-decoder/index.js +8 -0
  116. package/node_modules/mpg123-decoder/package.json +58 -0
  117. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
  118. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
  119. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
  120. package/node_modules/mpg123-decoder/types.d.ts +30 -0
  121. package/node_modules/silk-wasm/LICENSE +21 -0
  122. package/node_modules/silk-wasm/README.md +85 -0
  123. package/node_modules/silk-wasm/lib/index.cjs +16 -0
  124. package/node_modules/silk-wasm/lib/index.d.ts +70 -0
  125. package/node_modules/silk-wasm/lib/index.mjs +16 -0
  126. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  127. package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
  128. package/node_modules/silk-wasm/package.json +39 -0
  129. package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
  130. package/node_modules/simple-yenc/.prettierignore +1 -0
  131. package/node_modules/simple-yenc/LICENSE +7 -0
  132. package/node_modules/simple-yenc/README.md +163 -0
  133. package/node_modules/simple-yenc/dist/esm.js +1 -0
  134. package/node_modules/simple-yenc/dist/index.js +1 -0
  135. package/node_modules/simple-yenc/package.json +50 -0
  136. package/node_modules/simple-yenc/rollup.config.js +27 -0
  137. package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
  138. package/node_modules/ws/LICENSE +20 -0
  139. package/node_modules/ws/README.md +548 -0
  140. package/node_modules/ws/browser.js +8 -0
  141. package/node_modules/ws/index.js +22 -0
  142. package/node_modules/ws/lib/buffer-util.js +131 -0
  143. package/node_modules/ws/lib/constants.js +19 -0
  144. package/node_modules/ws/lib/event-target.js +292 -0
  145. package/node_modules/ws/lib/extension.js +203 -0
  146. package/node_modules/ws/lib/limiter.js +55 -0
  147. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  148. package/node_modules/ws/lib/receiver.js +706 -0
  149. package/node_modules/ws/lib/sender.js +602 -0
  150. package/node_modules/ws/lib/stream.js +161 -0
  151. package/node_modules/ws/lib/subprotocol.js +62 -0
  152. package/node_modules/ws/lib/validation.js +152 -0
  153. package/node_modules/ws/lib/websocket-server.js +554 -0
  154. package/node_modules/ws/lib/websocket.js +1393 -0
  155. package/node_modules/ws/package.json +70 -0
  156. package/node_modules/ws/wrapper.mjs +21 -0
  157. package/openclaw.plugin.json +17 -0
  158. package/package.json +70 -0
  159. package/preload.cjs +33 -0
  160. package/scripts/cleanup-legacy-plugins.sh +124 -0
  161. package/scripts/link-sdk-core.cjs +185 -0
  162. package/scripts/postinstall-link-sdk.js +126 -0
  163. package/scripts/proactive-api-server.ts +369 -0
  164. package/scripts/send-proactive.ts +293 -0
  165. package/scripts/set-markdown.sh +156 -0
  166. package/scripts/test-sendmedia.ts +116 -0
  167. package/scripts/upgrade-via-npm.ps1 +460 -0
  168. package/scripts/upgrade-via-npm.sh +652 -0
  169. package/scripts/upgrade-via-source.sh +1026 -0
  170. package/skills/qqbot-channel/SKILL.md +263 -0
  171. package/skills/qqbot-channel/references/api_references.md +521 -0
  172. package/skills/qqbot-media/SKILL.md +60 -0
  173. package/skills/qqbot-remind/SKILL.md +159 -0
  174. package/src/admin-resolver.ts +181 -0
  175. package/src/api.ts +1284 -0
  176. package/src/channel.ts +477 -0
  177. package/src/config.ts +347 -0
  178. package/src/credential-backup.ts +72 -0
  179. package/src/deliver-debounce.ts +229 -0
  180. package/src/gateway.ts +2245 -0
  181. package/src/group-history.ts +328 -0
  182. package/src/image-server.ts +675 -0
  183. package/src/inbound-attachments.ts +321 -0
  184. package/src/known-users.ts +353 -0
  185. package/src/message-gating.ts +190 -0
  186. package/src/message-queue.ts +352 -0
  187. package/src/onboarding.ts +274 -0
  188. package/src/openclaw-plugin-sdk.d.ts +587 -0
  189. package/src/outbound-deliver.ts +473 -0
  190. package/src/outbound.ts +1131 -0
  191. package/src/proactive.ts +530 -0
  192. package/src/ref-index-store.ts +412 -0
  193. package/src/reply-dispatcher.ts +334 -0
  194. package/src/request-context.ts +49 -0
  195. package/src/runtime.ts +14 -0
  196. package/src/session-store.ts +303 -0
  197. package/src/slash-commands.ts +2030 -0
  198. package/src/startup-greeting.ts +120 -0
  199. package/src/streaming.ts +1077 -0
  200. package/src/stt.ts +86 -0
  201. package/src/tools/channel.ts +281 -0
  202. package/src/tools/remind.ts +308 -0
  203. package/src/types.ts +391 -0
  204. package/src/typing-keepalive.ts +59 -0
  205. package/src/update-checker.ts +186 -0
  206. package/src/utils/audio-convert.ts +859 -0
  207. package/src/utils/chunked-upload.ts +483 -0
  208. package/src/utils/file-utils.ts +193 -0
  209. package/src/utils/image-size.ts +266 -0
  210. package/src/utils/media-send.ts +631 -0
  211. package/src/utils/media-tags.ts +183 -0
  212. package/src/utils/payload.ts +265 -0
  213. package/src/utils/pkg-version.ts +64 -0
  214. package/src/utils/platform.ts +435 -0
  215. package/src/utils/ssrf-guard.ts +102 -0
  216. package/src/utils/text-parsing.ts +85 -0
  217. package/src/utils/upload-cache.ts +128 -0
  218. package/tsconfig.json +16 -0
@@ -0,0 +1,460 @@
1
+ # qqbot upgrade via npm package (Windows PowerShell)
2
+ #
3
+ # Windows-native equivalent of upgrade-via-npm.sh.
4
+ # No bash / Git Bash / WSL required.
5
+ #
6
+ # Usage:
7
+ # .\upgrade-via-npm.ps1 # upgrade to latest (default)
8
+ # .\upgrade-via-npm.ps1 -Version <version> # upgrade to specific version
9
+ # .\upgrade-via-npm.ps1 -SelfVersion # upgrade to local package.json version
10
+ # .\upgrade-via-npm.ps1 -AppId <appid> -Secret <secret> # configure on first install
11
+ # .\upgrade-via-npm.ps1 -NoRestart # file replacement only (for hot-upgrade)
12
+
13
+ param(
14
+ [string]$Version = "",
15
+ [switch]$SelfVersion,
16
+ [string]$AppId = "",
17
+ [string]$Secret = "",
18
+ [switch]$NoRestart,
19
+ [string]$Tag = "",
20
+ [string]$Pkg = "",
21
+ [switch]$Help
22
+ )
23
+
24
+ $ErrorActionPreference = "Stop"
25
+ $PKG_NAME = "@tencent-connect/openclaw-qqbot"
26
+ $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Definition
27
+ $PROJECT_DIR = Split-Path -Parent $SCRIPT_DIR
28
+
29
+ # -Pkg 覆盖包名(支持 "scope/name" 自动补 @)
30
+ if ($Pkg) {
31
+ $Pkg = $Pkg.Trim()
32
+ if (-not $Pkg.StartsWith("@")) { $Pkg = "@$Pkg" }
33
+ $PKG_NAME = $Pkg
34
+ }
35
+
36
+ # Read local version
37
+ $LOCAL_VERSION = ""
38
+ try {
39
+ $pkgPath = Join-Path $PROJECT_DIR "package.json"
40
+ if (Test-Path $pkgPath) {
41
+ $pkg = Get-Content $pkgPath -Raw | ConvertFrom-Json
42
+ $LOCAL_VERSION = $pkg.version
43
+ }
44
+ } catch {}
45
+
46
+ if ($Help) {
47
+ Write-Host "Usage:"
48
+ Write-Host " .\upgrade-via-npm.ps1 # upgrade to latest (default)"
49
+ Write-Host " .\upgrade-via-npm.ps1 -Version [version] # upgrade to specific version"
50
+ Write-Host " .\upgrade-via-npm.ps1 -SelfVersion # upgrade to repo version ($LOCAL_VERSION)"
51
+ Write-Host ""
52
+ Write-Host " -Pkg [scope/name] Custom npm package (e.g. ryantest/openclaw-qqbot)"
53
+ Write-Host " -AppId [appid] QQ bot appid (required on first install)"
54
+ Write-Host " -Secret [secret] QQ bot secret (required on first install)"
55
+ exit 0
56
+ }
57
+
58
+ # Determine install source
59
+ $INSTALL_SRC = ""
60
+ if ($Tag) {
61
+ $INSTALL_SRC = "${PKG_NAME}@${Tag}"
62
+ } elseif ($Version) {
63
+ $INSTALL_SRC = "${PKG_NAME}@${Version}"
64
+ } elseif ($SelfVersion) {
65
+ if (-not $LOCAL_VERSION) {
66
+ Write-Host "[ERROR] Cannot read version from package.json" -ForegroundColor Red
67
+ exit 1
68
+ }
69
+ $INSTALL_SRC = "${PKG_NAME}@${LOCAL_VERSION}"
70
+ } else {
71
+ $INSTALL_SRC = "${PKG_NAME}@latest"
72
+ }
73
+
74
+ # Environment variable fallback
75
+ if (-not $AppId) { $AppId = $env:QQBOT_APPID }
76
+ if (-not $Secret) { $Secret = $env:QQBOT_SECRET }
77
+ if ((-not $AppId) -and (-not $Secret) -and $env:QQBOT_TOKEN) {
78
+ $parts = $env:QQBOT_TOKEN -split ":", 2
79
+ $AppId = $parts[0]
80
+ $Secret = $parts[1]
81
+ }
82
+
83
+ # Detect CLI
84
+ $CMD = ""
85
+ foreach ($name in @("openclaw", "clawdbot", "moltbot")) {
86
+ try {
87
+ $null = Get-Command $name -ErrorAction Stop
88
+ $CMD = $name
89
+ break
90
+ } catch {}
91
+ }
92
+ if (-not $CMD) {
93
+ Write-Host "[ERROR] openclaw / clawdbot / moltbot not found" -ForegroundColor Red
94
+ exit 1
95
+ }
96
+
97
+ $HOME_DIR = $env:USERPROFILE
98
+ if (-not $HOME_DIR) { $HOME_DIR = [Environment]::GetFolderPath("UserProfile") }
99
+ $EXTENSIONS_DIR = Join-Path (Join-Path $HOME_DIR ".$CMD") "extensions"
100
+
101
+ Write-Host "==========================================="
102
+ Write-Host " qqbot npm upgrade: $INSTALL_SRC"
103
+ Write-Host "==========================================="
104
+ Write-Host ""
105
+
106
+ # [1/3] Download and extract new version
107
+ Write-Host "[1/5] Downloading new version..."
108
+ $TMPDIR_PACK = Join-Path ([System.IO.Path]::GetTempPath()) "qqbot-pack-$([guid]::NewGuid().ToString('N').Substring(0,8))"
109
+ $EXTRACT_DIR = Join-Path ([System.IO.Path]::GetTempPath()) "qqbot-extract-$([guid]::NewGuid().ToString('N').Substring(0,8))"
110
+ New-Item -ItemType Directory -Path $TMPDIR_PACK -Force | Out-Null
111
+ New-Item -ItemType Directory -Path $EXTRACT_DIR -Force | Out-Null
112
+
113
+ try {
114
+ Push-Location $TMPDIR_PACK
115
+
116
+ # Multi-registry fallback
117
+ $PACK_OK = $false
118
+ $registries = @("https://registry.npmjs.org/", "https://registry.npmmirror.com/", "")
119
+ foreach ($registry in $registries) {
120
+ try {
121
+ if ($registry) {
122
+ Write-Host " Trying registry: $registry"
123
+ & npm pack $INSTALL_SRC --registry $registry --quiet 2>&1 | Out-Null
124
+ } else {
125
+ Write-Host " Trying default registry..."
126
+ & npm pack $INSTALL_SRC --quiet 2>&1 | Out-Null
127
+ }
128
+ if ($LASTEXITCODE -eq 0) {
129
+ $PACK_OK = $true
130
+ break
131
+ }
132
+ } catch {}
133
+ }
134
+
135
+ if (-not $PACK_OK) {
136
+ Write-Host "[ERROR] npm pack failed (all registries unavailable)" -ForegroundColor Red
137
+ exit 1
138
+ }
139
+
140
+ $TGZ_FILE = Get-ChildItem -Path $TMPDIR_PACK -Filter "*.tgz" | Select-Object -First 1
141
+ if (-not $TGZ_FILE) {
142
+ Write-Host "[ERROR] Downloaded tgz file not found" -ForegroundColor Red
143
+ exit 1
144
+ }
145
+ Write-Host " Downloaded: $($TGZ_FILE.Name)"
146
+
147
+ # Extract tgz (tar is built-in on Windows 10+)
148
+ & tar xzf $TGZ_FILE.FullName -C $EXTRACT_DIR
149
+ $PACKAGE_DIR = Join-Path $EXTRACT_DIR "package"
150
+ if (-not (Test-Path $PACKAGE_DIR)) {
151
+ Write-Host "[ERROR] Extraction failed, package directory not found" -ForegroundColor Red
152
+ exit 1
153
+ }
154
+
155
+ Pop-Location
156
+
157
+ # Prepare staging directory
158
+ $STAGING_DIR = Join-Path (Split-Path $EXTENSIONS_DIR -Parent) ".qqbot-upgrade-staging"
159
+ if (Test-Path $STAGING_DIR) { Remove-Item -Recurse -Force $STAGING_DIR }
160
+ Copy-Item -Recurse -Force $PACKAGE_DIR $STAGING_DIR
161
+
162
+ # Check bundled dependencies
163
+ $nmDir = Join-Path $STAGING_DIR "node_modules"
164
+ if (Test-Path $nmDir) {
165
+ $bundledCount = (Get-ChildItem -Directory $nmDir -ErrorAction SilentlyContinue | Measure-Object).Count
166
+ # Count scoped packages
167
+ Get-ChildItem -Directory $nmDir -Filter "@*" -ErrorAction SilentlyContinue | ForEach-Object {
168
+ $bundledCount += (Get-ChildItem -Directory $_.FullName -ErrorAction SilentlyContinue | Measure-Object).Count - 1
169
+ }
170
+ Write-Host " Bundled dependencies ready (${bundledCount} packages)"
171
+ } else {
172
+ Write-Host " [WARN] Bundled node_modules not found, installing dependencies..."
173
+ Push-Location $STAGING_DIR
174
+ try { & npm install --omit=dev --omit=peer --ignore-scripts --quiet 2>&1 | Out-Null } catch {}
175
+ Pop-Location
176
+ }
177
+
178
+ } finally {
179
+ # Clean up temp files
180
+ if (Test-Path $TMPDIR_PACK) { Remove-Item -Recurse -Force $TMPDIR_PACK -ErrorAction SilentlyContinue }
181
+ if (Test-Path $EXTRACT_DIR) { Remove-Item -Recurse -Force $EXTRACT_DIR -ErrorAction SilentlyContinue }
182
+ }
183
+
184
+ # ── Preflight: validate new package before writing to extensions ──
185
+ Write-Host ""
186
+ Write-Host "[2/5] Preflight checks..."
187
+ $PreflightOK = $true
188
+
189
+ # (a) package.json exists and has version
190
+ $StagingPkg = Join-Path $STAGING_DIR "package.json"
191
+ $StagingVersion = ""
192
+ if (-not (Test-Path $StagingPkg)) {
193
+ Write-Host " [FAIL] New package missing package.json" -ForegroundColor Red
194
+ $PreflightOK = $false
195
+ } else {
196
+ try {
197
+ $spkg = Get-Content $StagingPkg -Raw | ConvertFrom-Json
198
+ $StagingVersion = $spkg.version
199
+ if (-not $StagingVersion) { throw "no version" }
200
+ Write-Host " [OK] Version: $StagingVersion"
201
+ } catch {
202
+ Write-Host " [FAIL] package.json unreadable or missing version" -ForegroundColor Red
203
+ $PreflightOK = $false
204
+ }
205
+ }
206
+
207
+ # (b) Entry file exists
208
+ $EntryFile = ""
209
+ foreach ($candidate in @("dist\index.js", "index.js")) {
210
+ if (Test-Path (Join-Path $STAGING_DIR $candidate)) {
211
+ $EntryFile = $candidate
212
+ break
213
+ }
214
+ }
215
+ if (-not $EntryFile) {
216
+ Write-Host " [FAIL] Missing entry file (dist\index.js or index.js)" -ForegroundColor Red
217
+ $PreflightOK = $false
218
+ } else {
219
+ Write-Host " [OK] Entry file: $EntryFile"
220
+ }
221
+
222
+ # (c) Core directory dist/src
223
+ $CoreSrcDir = Join-Path $STAGING_DIR "dist" "src"
224
+ if (-not (Test-Path $CoreSrcDir)) {
225
+ Write-Host " [FAIL] Missing core directory dist\src\" -ForegroundColor Red
226
+ $PreflightOK = $false
227
+ } else {
228
+ $CoreJsCount = (Get-ChildItem -Path $CoreSrcDir -Filter "*.js" -File -Recurse -ErrorAction SilentlyContinue | Measure-Object).Count
229
+ Write-Host " [OK] dist\src\ contains $CoreJsCount JS files"
230
+ if ($CoreJsCount -lt 5) {
231
+ Write-Host " [FAIL] JS file count too low (expected >= 5, got $CoreJsCount)" -ForegroundColor Red
232
+ $PreflightOK = $false
233
+ }
234
+ }
235
+
236
+ # (d) Critical module files
237
+ $MissingModules = @()
238
+ foreach ($mod in @("dist\src\gateway.js", "dist\src\api.js", "dist\src\admin-resolver.js")) {
239
+ if (-not (Test-Path (Join-Path $STAGING_DIR $mod))) {
240
+ $MissingModules += $mod
241
+ }
242
+ }
243
+ if ($MissingModules.Count -gt 0) {
244
+ Write-Host " [FAIL] Missing critical modules: $($MissingModules -join ', ')" -ForegroundColor Red
245
+ $PreflightOK = $false
246
+ } else {
247
+ Write-Host " [OK] Critical modules intact"
248
+ }
249
+
250
+ # (e) Bundled node_modules health check
251
+ $nmDir = Join-Path $STAGING_DIR "node_modules"
252
+ if (Test-Path $nmDir) {
253
+ $BundledOK = $true
254
+ foreach ($dep in @("ws", "silk-wasm")) {
255
+ if (-not (Test-Path (Join-Path $nmDir $dep))) {
256
+ Write-Host " [WARN] Bundled dependency missing: $dep" -ForegroundColor Yellow
257
+ $BundledOK = $false
258
+ }
259
+ }
260
+ if ($BundledOK) {
261
+ Write-Host " [OK] Core bundled dependencies intact"
262
+ }
263
+ }
264
+
265
+ # (f) Version sanity check
266
+ if ($StagingVersion) {
267
+ $StagingMajor = ($StagingVersion -split "\.")[0]
268
+ if ($StagingMajor -eq "0") {
269
+ Write-Host " [WARN] Major version is 0 ($StagingVersion), may not be a production release" -ForegroundColor Yellow
270
+ }
271
+ }
272
+
273
+ # Preflight result
274
+ if (-not $PreflightOK) {
275
+ Write-Host ""
276
+ Write-Host "[ABORT] Preflight checks failed, upgrade cancelled (old version unaffected)" -ForegroundColor Red
277
+ Remove-Item -Recurse -Force $STAGING_DIR -ErrorAction SilentlyContinue
278
+ exit 1
279
+ }
280
+ Write-Host " [OK] All preflight checks passed"
281
+
282
+ # [3/5] Replace plugin directory (in-place overwrite to avoid file-lock issues)
283
+ Write-Host ""
284
+ Write-Host "[3/5] Replacing plugin directory..."
285
+ if (-not (Test-Path $EXTENSIONS_DIR)) { New-Item -ItemType Directory -Path $EXTENSIONS_DIR -Force | Out-Null }
286
+ $TARGET_DIR = Join-Path $EXTENSIONS_DIR "openclaw-qqbot"
287
+
288
+ if (-not (Test-Path $TARGET_DIR)) {
289
+ # Fresh install: just move staging into place
290
+ Move-Item -Path $STAGING_DIR -Destination $TARGET_DIR
291
+ } else {
292
+ # In-place overwrite using robocopy /MIR (mirrors source to dest, works even with locked files)
293
+ Write-Host " Overwriting in-place with robocopy /MIR ..."
294
+ $roboArgs = @($STAGING_DIR, $TARGET_DIR, "/MIR", "/NFL", "/NDL", "/NJH", "/NJS", "/NP", "/R:3", "/W:2")
295
+ $roboResult = & robocopy @roboArgs 2>&1
296
+ $roboExit = $LASTEXITCODE
297
+ # robocopy exit codes: 0-7 = success (various levels of copy), 8+ = error
298
+ if ($roboExit -ge 8) {
299
+ Write-Host " robocopy failed (exit $roboExit), falling back to Copy-Item..." -ForegroundColor Yellow
300
+ # Fallback: recursive Copy-Item -Force (overwrites files even if target exists)
301
+ try {
302
+ Copy-Item -Path (Join-Path $STAGING_DIR "*") -Destination $TARGET_DIR -Recurse -Force -ErrorAction Stop
303
+ Write-Host " Copy-Item fallback succeeded"
304
+ } catch {
305
+ Write-Host " [ERROR] Copy-Item also failed: $($_.Exception.Message)" -ForegroundColor Red
306
+ exit 1
307
+ }
308
+ } else {
309
+ Write-Host " robocopy completed (exit $roboExit)"
310
+ }
311
+ # Clean up staging
312
+ Remove-Item -Recurse -Force $STAGING_DIR -ErrorAction SilentlyContinue
313
+ }
314
+
315
+ # Clean up leftover directories
316
+ foreach ($leftover in @("openclaw-qqbot.staging", ".qqbot-upgrade-staging", ".qqbot-upgrade-old", ".openclaw-qqbot-new")) {
317
+ $p = Join-Path $EXTENSIONS_DIR $leftover
318
+ if (Test-Path $p) { Remove-Item -Recurse -Force $p -ErrorAction SilentlyContinue }
319
+ }
320
+ $oldDir = Join-Path (Split-Path $EXTENSIONS_DIR -Parent) ".qqbot-upgrade-old"
321
+ if (Test-Path $oldDir) { Remove-Item -Recurse -Force $oldDir -ErrorAction SilentlyContinue }
322
+ foreach ($legacyName in @("qqbot", "openclaw-qq")) {
323
+ $p = Join-Path $EXTENSIONS_DIR $legacyName
324
+ if (Test-Path $p) { Remove-Item -Recurse -Force $p -ErrorAction SilentlyContinue }
325
+ }
326
+ Write-Host " Installed to: $TARGET_DIR"
327
+
328
+ # Execute postinstall script to create openclaw SDK symlink
329
+ # (upgrade-via-npm is pure file operation, npm install is not run, so postinstall won't trigger automatically)
330
+ $PostinstallScript = Join-Path $TARGET_DIR "scripts" "postinstall-link-sdk.js"
331
+ if (Test-Path $PostinstallScript) {
332
+ Write-Host " Running postinstall: creating openclaw SDK symlink..."
333
+ try {
334
+ Push-Location $TARGET_DIR
335
+ $postOutput = & node $PostinstallScript 2>&1
336
+ Pop-Location
337
+ if ($postOutput) { Write-Host " $postOutput" }
338
+ } catch {
339
+ Write-Host " [WARN] postinstall script failed (non-fatal)" -ForegroundColor Yellow
340
+ try { Pop-Location } catch {}
341
+ }
342
+ # Verify symlink creation
343
+ $symlinkPath = Join-Path $TARGET_DIR "node_modules" "openclaw"
344
+ if (Test-Path $symlinkPath) {
345
+ Write-Host " [OK] openclaw SDK symlink ready"
346
+ } else {
347
+ Write-Host " [WARN] openclaw SDK symlink not created, attempting manual fallback..." -ForegroundColor Yellow
348
+ $cliDataDir = Split-Path $EXTENSIONS_DIR -Parent
349
+ $cliName = (Split-Path $cliDataDir -Leaf) -replace '^\.',''
350
+ try {
351
+ $globalRoot = (& npm root -g 2>$null).Trim()
352
+ $globalPkg = Join-Path $globalRoot $cliName
353
+ if ($globalRoot -and (Test-Path $globalPkg)) {
354
+ $nmDir = Join-Path $TARGET_DIR "node_modules"
355
+ if (-not (Test-Path $nmDir)) { New-Item -ItemType Directory -Path $nmDir -Force | Out-Null }
356
+ New-Item -ItemType Junction -Path $symlinkPath -Target $globalPkg -Force | Out-Null
357
+ Write-Host " [OK] Manual symlink created: -> $globalPkg"
358
+ } else {
359
+ Write-Host " [ERROR] Cannot locate global $cliName installation (npm root -g: $globalRoot)" -ForegroundColor Red
360
+ }
361
+ } catch {
362
+ Write-Host " [ERROR] Manual symlink creation also failed: $($_.Exception.Message)" -ForegroundColor Red
363
+ }
364
+ }
365
+ } else {
366
+ Write-Host " [WARN] postinstall script not found, skipping symlink creation" -ForegroundColor Yellow
367
+ }
368
+
369
+ # [4/5] Verify installation
370
+ Write-Host ""
371
+ Write-Host "[4/5] Verifying installation..."
372
+ $NEW_VERSION = "unknown"
373
+ try {
374
+ $newPkgPath = Join-Path $TARGET_DIR "package.json"
375
+ if (Test-Path $newPkgPath) {
376
+ $newPkg = Get-Content $newPkgPath -Raw | ConvertFrom-Json
377
+ if ($newPkg.version) { $NEW_VERSION = $newPkg.version }
378
+ }
379
+ } catch {}
380
+
381
+ Write-Host "QQBOT_NEW_VERSION=$NEW_VERSION"
382
+
383
+ if ($NEW_VERSION -ne "unknown") {
384
+ Write-Host "QQBOT_REPORT=QQBot upgrade complete: v${NEW_VERSION}"
385
+ } else {
386
+ Write-Host "QQBOT_REPORT=[WARN] QQBot upgrade status unknown, cannot confirm new version"
387
+ }
388
+
389
+ Write-Host ""
390
+ Write-Host "==========================================="
391
+ Write-Host " File installation complete"
392
+ Write-Host "==========================================="
393
+
394
+ # --NoRestart mode
395
+ if ($NoRestart) {
396
+ Write-Host ""
397
+ Write-Host "[Skip restart] -NoRestart specified, exiting for caller to trigger gateway restart"
398
+ exit 0
399
+ }
400
+
401
+ # [配置] Configure appid/secret
402
+ if ($AppId -and $Secret) {
403
+ Write-Host ""
404
+ Write-Host "[Config] Writing qqbot channel config..."
405
+ $DESIRED_TOKEN = "${AppId}:${Secret}"
406
+
407
+ try {
408
+ & $CMD channels add --channel qqbot --token $DESIRED_TOKEN 2>&1 | Out-Null
409
+ if ($LASTEXITCODE -eq 0) {
410
+ Write-Host " Channel config saved"
411
+ } else { throw "channels add failed" }
412
+ } catch {
413
+ Write-Host " [WARN] $CMD channels add failed, trying direct config edit..." -ForegroundColor Yellow
414
+ $CONFIG_FILE = Join-Path (Join-Path $HOME_DIR ".$CMD") "$CMD.json"
415
+ if (Test-Path $CONFIG_FILE) {
416
+ try {
417
+ $cfg = Get-Content $CONFIG_FILE -Raw | ConvertFrom-Json
418
+ if (-not $cfg.channels) { $cfg | Add-Member -NotePropertyName channels -NotePropertyValue @{} }
419
+ if (-not $cfg.channels.qqbot) { $cfg.channels | Add-Member -NotePropertyName qqbot -NotePropertyValue @{} }
420
+ $cfg.channels.qqbot | Add-Member -NotePropertyName appId -NotePropertyValue $AppId -Force
421
+ $cfg.channels.qqbot | Add-Member -NotePropertyName clientSecret -NotePropertyValue $Secret -Force
422
+ $cfg | ConvertTo-Json -Depth 10 | Set-Content $CONFIG_FILE -Encoding UTF8
423
+ Write-Host " Channel config saved (direct file edit)"
424
+ } catch {
425
+ Write-Host " [ERROR] Config write failed, please configure manually:" -ForegroundColor Red
426
+ Write-Host " $CMD channels add --channel qqbot --token `"${AppId}:${Secret}`""
427
+ }
428
+ }
429
+ }
430
+ } elseif ($AppId -or $Secret) {
431
+ Write-Host ""
432
+ Write-Host "[WARN] -AppId and -Secret must be provided together" -ForegroundColor Yellow
433
+ }
434
+
435
+ # [5/5] Restart gateway
436
+ Write-Host ""
437
+
438
+ # Manual upgrade: write startup-marker before restart to prevent bot from sending duplicate notification
439
+ if ($NEW_VERSION -and $NEW_VERSION -ne "unknown") {
440
+ $MarkerDir = Join-Path $HOME_DIR ".openclaw" "qqbot" "data"
441
+ if (-not (Test-Path $MarkerDir)) { New-Item -ItemType Directory -Path $MarkerDir -Force | Out-Null }
442
+ $MarkerFile = Join-Path $MarkerDir "startup-marker.json"
443
+ $Now = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
444
+ @{ version = $NEW_VERSION; startedAt = $Now; greetedAt = $Now } | ConvertTo-Json -Compress | Set-Content $MarkerFile -Encoding UTF8
445
+ }
446
+
447
+ Write-Host "[Restart] Restarting gateway..."
448
+ try {
449
+ & $CMD gateway restart 2>&1 | Out-Null
450
+ if ($LASTEXITCODE -eq 0) {
451
+ Write-Host " Gateway restarted"
452
+ # Print the same upgrade greeting as bot notification (no need to push via bot in manual upgrade)
453
+ if ($NEW_VERSION -and $NEW_VERSION -ne "unknown") {
454
+ Write-Host ""
455
+ Write-Host "🎉 QQBot 插件已更新至 v${NEW_VERSION},在线等候你的吩咐。"
456
+ }
457
+ } else { throw "restart failed" }
458
+ } catch {
459
+ Write-Host " [WARN] Gateway restart failed, please run manually: $CMD gateway restart" -ForegroundColor Yellow
460
+ }