feat: 支持 Windows WCDB 解密与打包

This commit is contained in:
电摇小子
2026-07-14 10:15:36 +08:00
committed by 电摇小子
parent 3dbc11c19d
commit 267730d59e
39 changed files with 2194 additions and 268 deletions
+6 -1
View File
@@ -21,8 +21,13 @@ extraResources:
filter: filter:
- '**/*' - '**/*'
win: win:
executableName: wechatexplorer # WCDB's Windows runtime checks the host executable name. The dev runtime is
# electron.exe, so keep the packaged executable compatible while preserving
# WechatExplorer as the product/shortcut name.
executableName: electron
nsis: nsis:
oneClick: false
allowToChangeInstallationDirectory: true
artifactName: ${name}-${version}-setup.${ext} artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName} shortcutName: ${productName}
uninstallDisplayName: ${productName} uninstallDisplayName: ${productName}
+13 -1
View File
@@ -3,7 +3,19 @@ import { defineConfig } from 'electron-vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
export default defineConfig({ export default defineConfig({
main: {}, main: {
build: {
rollupOptions: {
input: {
index: resolve('src/main/index.ts')
},
output: {
entryFileNames: '[name].js'
},
external: ['koffi']
}
}
},
preload: {}, preload: {},
renderer: { renderer: {
resolve: { resolve: {
+3 -3
View File
@@ -20,7 +20,7 @@
"start": "electron-vite preview", "start": "electron-vite preview",
"dev": "electron-vite dev", "dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build", "build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps && node scripts/prepare-electron-runtime.cjs",
"build:unpack": "npm run build && electron-builder --config electron-builder.yml --dir", "build:unpack": "npm run build && electron-builder --config electron-builder.yml --dir",
"build:win": "npm run build && electron-builder --config electron-builder.yml --win", "build:win": "npm run build && electron-builder --config electron-builder.yml --win",
"build:mac:x64": "electron-vite build && electron-builder --config electron-builder.yml --mac --x64", "build:mac:x64": "electron-vite build && electron-builder --config electron-builder.yml --mac --x64",
@@ -33,7 +33,7 @@
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"fs-extra": "^11.3.2", "fs-extra": "^11.3.2",
"fzstd": "^0.1.1", "fzstd": "^0.1.1",
"koffi": "^2.9.0", "koffi": "^3.1.0",
"openai": "^6.10.0", "openai": "^6.10.0",
"silk-wasm": "^3.7.1" "silk-wasm": "^3.7.1"
}, },
@@ -47,7 +47,7 @@
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.1",
"electron": "^39.2.6", "electron": "^43.0.0",
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
+201 -148
View File
@@ -16,7 +16,7 @@ specifiers:
'@types/react': ^19.2.7 '@types/react': ^19.2.7
'@types/react-dom': ^19.2.3 '@types/react-dom': ^19.2.3
'@vitejs/plugin-react': ^5.1.1 '@vitejs/plugin-react': ^5.1.1
electron: ^39.2.6 electron: ^43.0.0
electron-builder: ^26.0.12 electron-builder: ^26.0.12
electron-vite: ^5.0.0 electron-vite: ^5.0.0
eslint: ^9.39.1 eslint: ^9.39.1
@@ -25,7 +25,7 @@ specifiers:
eslint-plugin-react-refresh: ^0.4.24 eslint-plugin-react-refresh: ^0.4.24
fs-extra: ^11.3.2 fs-extra: ^11.3.2
fzstd: ^0.1.1 fzstd: ^0.1.1
koffi: ^2.9.0 koffi: ^3.1.0
openai: ^6.10.0 openai: ^6.10.0
prettier: ^3.7.4 prettier: ^3.7.4
react: ^19.2.1 react: ^19.2.1
@@ -35,11 +35,11 @@ specifiers:
vite: ^7.2.6 vite: ^7.2.6
dependencies: dependencies:
'@electron-toolkit/preload': 3.0.2_electron@39.2.6 '@electron-toolkit/preload': 3.0.2_electron@43.1.0
'@electron-toolkit/utils': 4.0.0_electron@39.2.6 '@electron-toolkit/utils': 4.0.0_electron@43.1.0
fs-extra: 11.3.2 fs-extra: 11.3.2
fzstd: 0.1.1 fzstd: 0.1.1
koffi: 2.16.2 koffi: 3.1.0
openai: 6.10.0 openai: 6.10.0
silk-wasm: 3.7.1 silk-wasm: 3.7.1
@@ -53,7 +53,7 @@ devDependencies:
'@types/react': 19.2.7 '@types/react': 19.2.7
'@types/react-dom': 19.2.3_@types+react@19.2.7 '@types/react-dom': 19.2.3_@types+react@19.2.7
'@vitejs/plugin-react': 5.1.2_vite@7.2.7 '@vitejs/plugin-react': 5.1.2_vite@7.2.7
electron: 39.2.6 electron: 43.1.0
electron-builder: 26.0.12 electron-builder: 26.0.12
electron-vite: 5.0.0_vite@7.2.7 electron-vite: 5.0.0_vite@7.2.7
eslint: 9.39.1 eslint: 9.39.1
@@ -266,6 +266,10 @@ packages:
ajv-keywords: 3.5.2_ajv@6.12.6 ajv-keywords: 3.5.2_ajv@6.12.6
dev: true dev: true
/@electron-internal/extract-zip/1.0.4:
resolution: {integrity: sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==}
engines: {node: '>=22.12.0'}
/@electron-toolkit/eslint-config-prettier/3.0.0_fiitszekoa4sqtbwyewxi6kyy4: /@electron-toolkit/eslint-config-prettier/3.0.0_fiitszekoa4sqtbwyewxi6kyy4:
resolution: {integrity: sha512-YapmIOVkbYdHLuTa+ad1SAVtcqYL9A/SJsc7cxQokmhcwAwonGevNom37jBf9slXegcZ/Slh01I/JARG1yhNFw==} resolution: {integrity: sha512-YapmIOVkbYdHLuTa+ad1SAVtcqYL9A/SJsc7cxQokmhcwAwonGevNom37jBf9slXegcZ/Slh01I/JARG1yhNFw==}
peerDependencies: peerDependencies:
@@ -298,12 +302,12 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@electron-toolkit/preload/3.0.2_electron@39.2.6: /@electron-toolkit/preload/3.0.2_electron@43.1.0:
resolution: {integrity: sha512-TWWPToXd8qPRfSXwzf5KVhpXMfONaUuRAZJHsKthKgZR/+LqX1dZVSSClQ8OTAEduvLGdecljCsoT2jSshfoUg==} resolution: {integrity: sha512-TWWPToXd8qPRfSXwzf5KVhpXMfONaUuRAZJHsKthKgZR/+LqX1dZVSSClQ8OTAEduvLGdecljCsoT2jSshfoUg==}
peerDependencies: peerDependencies:
electron: '>=13.0.0' electron: '>=13.0.0'
dependencies: dependencies:
electron: 39.2.6 electron: 43.1.0
dev: false dev: false
/@electron-toolkit/tsconfig/2.0.0_@types+node@22.19.2: /@electron-toolkit/tsconfig/2.0.0_@types+node@22.19.2:
@@ -314,12 +318,12 @@ packages:
'@types/node': 22.19.2 '@types/node': 22.19.2
dev: true dev: true
/@electron-toolkit/utils/4.0.0_electron@39.2.6: /@electron-toolkit/utils/4.0.0_electron@43.1.0:
resolution: {integrity: sha512-qXSntwEzluSzKl4z5yFNBknmPGjPa3zFhE4mp9+h0cgokY5ornAeP+CJQDBhKsL1S58aOQfcwkD3NwLZCl+64g==} resolution: {integrity: sha512-qXSntwEzluSzKl4z5yFNBknmPGjPa3zFhE4mp9+h0cgokY5ornAeP+CJQDBhKsL1S58aOQfcwkD3NwLZCl+64g==}
peerDependencies: peerDependencies:
electron: '>=13.0.0' electron: '>=13.0.0'
dependencies: dependencies:
electron: 39.2.6 electron: 43.1.0
dev: false dev: false
/@electron/asar/3.2.18: /@electron/asar/3.2.18:
@@ -341,19 +345,18 @@ packages:
minimist: 1.2.8 minimist: 1.2.8
dev: true dev: true
/@electron/get/2.0.3: /@electron/get/5.0.0:
resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} resolution: {integrity: sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==}
engines: {node: '>=12'} engines: {node: '>=22.12.0'}
dependencies: dependencies:
debug: 4.4.3 debug: 4.4.3
env-paths: 2.2.1 env-paths: 3.0.0
fs-extra: 8.1.0 graceful-fs: 4.2.11
got: 11.8.6
progress: 2.0.3 progress: 2.0.3
semver: 6.3.1 semver: 7.7.3
sumchecker: 3.0.1 sumchecker: 3.0.1
optionalDependencies: optionalDependencies:
global-agent: 3.0.0 undici: 7.28.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -786,6 +789,104 @@ packages:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
dev: true dev: true
/@koromix/koffi-darwin-arm64/3.1.0:
resolution: {integrity: sha512-VEt5r3fXTfbejr83PnuOP0H7s9Zmazcs+lofu96DOcRkistlMsn59wYyWiKpyAjs9PCgm0Ykh62ChZ3CGMmIOg==}
cpu: [arm64]
os: [darwin]
dev: false
optional: true
/@koromix/koffi-darwin-x64/3.1.0:
resolution: {integrity: sha512-n/tVRB9xIzdXT5H3zZt8ueThgWTSDL+yU7PWnU8wbZPBSawP/otx3swQyd6nMOqj1bmHgSHopiKSBXRS9pllmg==}
cpu: [x64]
os: [darwin]
dev: false
optional: true
/@koromix/koffi-freebsd-arm64/3.1.0:
resolution: {integrity: sha512-vazoPYIhOAlXZksVIqDRMIID4VeUZKx8F3dR90hOobT2ATyOkqNS5dv5UCV7Q7DSq22lQTrdbvENBAhROzCp0w==}
cpu: [arm64]
os: [freebsd]
dev: false
optional: true
/@koromix/koffi-freebsd-ia32/3.1.0:
resolution: {integrity: sha512-Vm7Uc97ru6RTSVmae2zCZZQeaizqVZ8WoU4+gG4H03Qe+WOj7kbKt/MxT7VBzdbPYIU5ZJeG/ZED1YlZyab6eQ==}
cpu: [ia32]
os: [freebsd]
dev: false
optional: true
/@koromix/koffi-freebsd-x64/3.1.0:
resolution: {integrity: sha512-N+VuVWjoiYPy1Go5mRadZ3B6RM5Qz+eCLhj2LXrMlefbUJ+O4gg7teCUGvPGfBEHDgmSN4yYUrfQmdJC10vOYw==}
cpu: [x64]
os: [freebsd]
dev: false
optional: true
/@koromix/koffi-linux-arm64/3.1.0:
resolution: {integrity: sha512-Wx5iOkeALe2ympLdiYwRpIg5qUkyQIv8N2foZ9rRker0uE7ZtXew2RRkbEgMir4b0yDYR1zyXd6B62GUzLtZ/g==}
cpu: [arm64]
os: [linux]
dev: false
optional: true
/@koromix/koffi-linux-ia32/3.1.0:
resolution: {integrity: sha512-1DjYm1QehXU0dgn0uE+FGYOb3Of7GiTMqLS+ZI2gbl1b+h76sz4LRBvDVrQyAmSMVVU8/7696S21YgE/iBhBVg==}
cpu: [ia32]
os: [linux]
dev: false
optional: true
/@koromix/koffi-linux-loong64/3.1.0:
resolution: {integrity: sha512-NOa0LdyltdESz3oeTqUH6MErHVoJOHoeXIsEp6xIMTUh4eKXEtlDQeoK6EYqo0DnBt83Xud95qLvi4Aw12pG4Q==}
cpu: [loong64]
os: [linux]
dev: false
optional: true
/@koromix/koffi-linux-riscv64/3.1.0:
resolution: {integrity: sha512-Ye6kiXZCGxGtAIXSly6XuOP5tJZNYOZ2eVg33k1MilKrzimAy9Mpw4d6e9+Sfsc1jesgeNYs1sb5iaI8HS3ncA==}
cpu: [riscv64]
os: [linux]
dev: false
optional: true
/@koromix/koffi-linux-x64/3.1.0:
resolution: {integrity: sha512-3yQTOkQrMna4VX+yeyfYImBjLlGrItMpsWyfaW1uSiz/A6GRydqdwYH7DWnp4Z+RSGYZpsewkf7byMc8pOOQKA==}
cpu: [x64]
os: [linux]
dev: false
optional: true
/@koromix/koffi-openbsd-ia32/3.1.0:
resolution: {integrity: sha512-/cDoFHb9yx4+yoT3GUpnKnfi3W2drG+/Ewo0TTZaQHb4PsxnYYyT6V8+t4cL5XXbQcTTcOsZxpmBRrn0NBa3dA==}
cpu: [ia32]
os: [openbsd]
dev: false
optional: true
/@koromix/koffi-openbsd-x64/3.1.0:
resolution: {integrity: sha512-CoQdqgnKvWgTXXZlUst8cBRQEov7QsxlTN2WAsu9wez01Xe6gEcH/zYePANualzzCbnaELfe5P0rA80QkoDuPA==}
cpu: [x64]
os: [openbsd]
dev: false
optional: true
/@koromix/koffi-win32-ia32/3.1.0:
resolution: {integrity: sha512-WjrA+DEkpy0xEHu48+NSOboHhTnzkIfsFuq3d/WrSs+T9WflWRng3jC7mdJxmR4eHb6i6BqjW3k/U0mNUTjFPA==}
cpu: [ia32]
os: [win32]
dev: false
optional: true
/@koromix/koffi-win32-x64/3.1.0:
resolution: {integrity: sha512-tnK5+IkzQBauQAQSzuyjso8OOIQRlaTZS39xIWpfqVYDLVDIuLDQk/WwHcOrR5yxlDrZq9ygiebBTOfcJFia7w==}
cpu: [x64]
os: [win32]
dev: false
optional: true
/@malept/cross-spawn-promise/2.0.0: /@malept/cross-spawn-promise/2.0.0:
resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==}
engines: {node: '>= 12.13.0'} engines: {node: '>= 12.13.0'}
@@ -1011,12 +1112,14 @@ packages:
/@sindresorhus/is/4.6.0: /@sindresorhus/is/4.6.0:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/@szmarczak/http-timer/4.0.6: /@szmarczak/http-timer/4.0.6:
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
defer-to-connect: 2.0.1 defer-to-connect: 2.0.1
dev: true
/@tootallnate/once/2.0.0: /@tootallnate/once/2.0.0:
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
@@ -1059,6 +1162,7 @@ packages:
'@types/keyv': 3.1.4 '@types/keyv': 3.1.4
'@types/node': 22.19.2 '@types/node': 22.19.2
'@types/responselike': 1.0.3 '@types/responselike': 1.0.3
dev: true
/@types/debug/4.1.12: /@types/debug/4.1.12:
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -1085,6 +1189,7 @@ packages:
/@types/http-cache-semantics/4.0.4: /@types/http-cache-semantics/4.0.4:
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
dev: true
/@types/json-schema/7.0.15: /@types/json-schema/7.0.15:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -1100,6 +1205,7 @@ packages:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies: dependencies:
'@types/node': 22.19.2 '@types/node': 22.19.2
dev: true
/@types/ms/2.1.0: /@types/ms/2.1.0:
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
@@ -1109,6 +1215,12 @@ packages:
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==} resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
dev: true
/@types/node/24.13.3:
resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
dependencies:
undici-types: 7.18.2
/@types/plist/3.0.5: /@types/plist/3.0.5:
resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==}
@@ -1136,18 +1248,13 @@ packages:
resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
dependencies: dependencies:
'@types/node': 22.19.2 '@types/node': 22.19.2
dev: true
/@types/verror/1.10.11: /@types/verror/1.10.11:
resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==}
dev: true dev: true
optional: true optional: true
/@types/yauzl/2.10.3:
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
dependencies:
'@types/node': 22.19.2
optional: true
/@typescript-eslint/eslint-plugin/8.49.0_nj5tjtfh637kziiwuhl3v5a4iq: /@typescript-eslint/eslint-plugin/8.49.0_nj5tjtfh637kziiwuhl3v5a4iq:
resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1591,11 +1698,6 @@ packages:
readable-stream: 3.6.2 readable-stream: 3.6.2
dev: true dev: true
/boolean/3.2.0:
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
optional: true
/brace-expansion/1.1.12: /brace-expansion/1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
dependencies: dependencies:
@@ -1621,9 +1723,6 @@ packages:
update-browserslist-db: 1.2.2_browserslist@4.28.1 update-browserslist-db: 1.2.2_browserslist@4.28.1
dev: true dev: true
/buffer-crc32/0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
/buffer-from/1.1.2: /buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true dev: true
@@ -1703,6 +1802,7 @@ packages:
/cacheable-lookup/5.0.4: /cacheable-lookup/5.0.4:
resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
engines: {node: '>=10.6.0'} engines: {node: '>=10.6.0'}
dev: true
/cacheable-request/7.0.4: /cacheable-request/7.0.4:
resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
@@ -1715,6 +1815,7 @@ packages:
lowercase-keys: 2.0.0 lowercase-keys: 2.0.0
normalize-url: 6.1.0 normalize-url: 6.1.0
responselike: 2.0.1 responselike: 2.0.1
dev: true
/call-bind-apply-helpers/1.0.2: /call-bind-apply-helpers/1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
@@ -1812,6 +1913,7 @@ packages:
resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
dependencies: dependencies:
mimic-response: 1.0.1 mimic-response: 1.0.1
dev: true
/clone/1.0.4: /clone/1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
@@ -1929,6 +2031,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
mimic-response: 3.1.0 mimic-response: 3.1.0
dev: true
/deep-is/0.1.4: /deep-is/0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1943,6 +2046,7 @@ packages:
/defer-to-connect/2.0.1: /defer-to-connect/2.0.1:
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/define-data-property/1.1.4: /define-data-property/1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
@@ -1951,6 +2055,7 @@ packages:
es-define-property: 1.0.1 es-define-property: 1.0.1
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
dev: true
/define-properties/1.2.1: /define-properties/1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
@@ -1959,6 +2064,7 @@ packages:
define-data-property: 1.1.4 define-data-property: 1.1.4
has-property-descriptors: 1.0.2 has-property-descriptors: 1.0.2
object-keys: 1.1.1 object-keys: 1.1.1
dev: true
/delayed-stream/1.0.0: /delayed-stream/1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
@@ -1970,10 +2076,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/detect-node/2.1.0:
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
optional: true
/dir-compare/4.2.0: /dir-compare/4.2.0:
resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==}
dependencies: dependencies:
@@ -2117,15 +2219,14 @@ packages:
- supports-color - supports-color
dev: true dev: true
/electron/39.2.6: /electron/43.1.0:
resolution: {integrity: sha512-dHBgTodWBZd+tL1Dt0PSh/CFLHeDkFCTKCTXu1dgPhlE9Z3k2zzlBQ9B2oW55CFsKanBDHiUomHJNw0XaSdQpA==} resolution: {integrity: sha512-DPfxpQLd4NL3BJ8DBxYAfmLUKKesF5Rx9dQx5FyczAP8bhOPScjHE48GArVeXu68LlAainuwkmQTQvdZwpIIAQ==}
engines: {node: '>= 12.20.55'} engines: {node: '>= 22.12.0'}
hasBin: true hasBin: true
requiresBuild: true
dependencies: dependencies:
'@electron/get': 2.0.3 '@electron-internal/extract-zip': 1.0.4
'@types/node': 22.19.2 '@electron/get': 5.0.0
extract-zip: 2.0.1 '@types/node': 24.13.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -2148,10 +2249,16 @@ packages:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
dev: true
/env-paths/2.2.1: /env-paths/2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/env-paths/3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
/err-code/2.0.3: /err-code/2.0.3:
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
@@ -2220,10 +2327,12 @@ packages:
/es-define-property/1.0.1: /es-define-property/1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true
/es-errors/1.3.0: /es-errors/1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true
/es-iterator-helpers/1.2.1: /es-iterator-helpers/1.2.1:
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
@@ -2280,10 +2389,6 @@ packages:
is-symbol: 1.1.1 is-symbol: 1.1.1
dev: true dev: true
/es6-error/4.1.1:
resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
optional: true
/esbuild/0.25.12: /esbuild/0.25.12:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2326,6 +2431,7 @@ packages:
/escape-string-regexp/4.0.0: /escape-string-regexp/4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/eslint-config-prettier/10.1.8_eslint@9.39.1: /eslint-config-prettier/10.1.8_eslint@9.39.1:
resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
@@ -2511,19 +2617,6 @@ packages:
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
dev: true dev: true
/extract-zip/2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
hasBin: true
dependencies:
debug: 4.4.3
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
'@types/yauzl': 2.10.3
transitivePeerDependencies:
- supports-color
/extsprintf/1.4.1: /extsprintf/1.4.1:
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
engines: {'0': node >=0.6.0} engines: {'0': node >=0.6.0}
@@ -2546,11 +2639,6 @@ packages:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
dev: true dev: true
/fd-slicer/1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
dependencies:
pend: 1.2.0
/fdir/6.5.0_picomatch@4.0.3: /fdir/6.5.0_picomatch@4.0.3:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -2639,14 +2727,6 @@ packages:
jsonfile: 6.2.0 jsonfile: 6.2.0
universalify: 2.0.1 universalify: 2.0.1
/fs-extra/8.1.0:
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
engines: {node: '>=6 <7 || >=8'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 4.0.0
universalify: 0.1.2
/fs-extra/9.1.0: /fs-extra/9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -2743,6 +2823,7 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
pump: 3.0.3 pump: 3.0.3
dev: true
/get-symbol-description/1.1.0: /get-symbol-description/1.1.0:
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
@@ -2796,18 +2877,6 @@ packages:
once: 1.4.0 once: 1.4.0
dev: true dev: true
/global-agent/3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
engines: {node: '>=10.0'}
dependencies:
boolean: 3.2.0
es6-error: 4.1.1
matcher: 3.0.0
roarr: 2.15.4
semver: 7.7.3
serialize-error: 7.0.1
optional: true
/globals/14.0.0: /globals/14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2824,10 +2893,12 @@ packages:
dependencies: dependencies:
define-properties: 1.2.1 define-properties: 1.2.1
gopd: 1.2.0 gopd: 1.2.0
dev: true
/gopd/1.2.0: /gopd/1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true
/got/11.8.6: /got/11.8.6:
resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
@@ -2844,6 +2915,7 @@ packages:
lowercase-keys: 2.0.0 lowercase-keys: 2.0.0
p-cancelable: 2.1.1 p-cancelable: 2.1.1
responselike: 2.0.1 responselike: 2.0.1
dev: true
/graceful-fs/4.2.11: /graceful-fs/4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@@ -2862,6 +2934,7 @@ packages:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
dependencies: dependencies:
es-define-property: 1.0.1 es-define-property: 1.0.1
dev: true
/has-proto/1.2.0: /has-proto/1.2.0:
resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
@@ -2908,6 +2981,7 @@ packages:
/http-cache-semantics/4.2.0: /http-cache-semantics/4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
dev: true
/http-proxy-agent/5.0.0: /http-proxy-agent/5.0.0:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
@@ -2936,6 +3010,7 @@ packages:
dependencies: dependencies:
quick-lru: 5.1.1 quick-lru: 5.1.1
resolve-alpn: 1.2.1 resolve-alpn: 1.2.1
dev: true
/https-proxy-agent/5.0.1: /https-proxy-agent/5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
@@ -3313,6 +3388,7 @@ packages:
/json-buffer/3.0.1: /json-buffer/3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
dev: true
/json-schema-traverse/0.4.1: /json-schema-traverse/0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@@ -3322,21 +3398,12 @@ packages:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true dev: true
/json-stringify-safe/5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
optional: true
/json5/2.2.3: /json5/2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
dev: true dev: true
/jsonfile/4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies:
graceful-fs: 4.2.11
/jsonfile/6.2.0: /jsonfile/6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
dependencies: dependencies:
@@ -3358,9 +3425,25 @@ packages:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
dev: true
/koffi/2.16.2: /koffi/3.1.0:
resolution: {integrity: sha512-owU0MRwv6xkrVqCd+33uw6BaYppkTRXbO/rVdJNI2dvZG0gzyRhYwW25eWtc5pauwK8TGh3AbkFONSezdykfSA==} resolution: {integrity: sha512-0mCvdjTJBXioiaKNz0vajAEdWtfM5qyhVXSq+wQrrU3odzNvl/J7Cqna79QpNo9mfoKpQgGsyFFDRtDACCwGrQ==}
optionalDependencies:
'@koromix/koffi-darwin-arm64': 3.1.0
'@koromix/koffi-darwin-x64': 3.1.0
'@koromix/koffi-freebsd-arm64': 3.1.0
'@koromix/koffi-freebsd-ia32': 3.1.0
'@koromix/koffi-freebsd-x64': 3.1.0
'@koromix/koffi-linux-arm64': 3.1.0
'@koromix/koffi-linux-ia32': 3.1.0
'@koromix/koffi-linux-loong64': 3.1.0
'@koromix/koffi-linux-riscv64': 3.1.0
'@koromix/koffi-linux-x64': 3.1.0
'@koromix/koffi-openbsd-ia32': 3.1.0
'@koromix/koffi-openbsd-x64': 3.1.0
'@koromix/koffi-win32-ia32': 3.1.0
'@koromix/koffi-win32-x64': 3.1.0
dev: false dev: false
/lazy-val/1.0.5: /lazy-val/1.0.5:
@@ -3408,6 +3491,7 @@ packages:
/lowercase-keys/2.0.0: /lowercase-keys/2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/lru-cache/10.4.3: /lru-cache/10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -3462,13 +3546,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/matcher/3.0.0:
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
engines: {node: '>=10'}
dependencies:
escape-string-regexp: 4.0.0
optional: true
/math-intrinsics/1.1.0: /math-intrinsics/1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3500,10 +3577,12 @@ packages:
/mimic-response/1.0.1: /mimic-response/1.0.1:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true
/mimic-response/3.1.0: /mimic-response/3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/minimatch/10.1.1: /minimatch/10.1.1:
resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
@@ -3657,6 +3736,7 @@ packages:
/normalize-url/6.1.0: /normalize-url/6.1.0:
resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/object-assign/4.1.1: /object-assign/4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
@@ -3671,6 +3751,7 @@ packages:
/object-keys/1.1.1: /object-keys/1.1.1:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true
/object.assign/4.1.7: /object.assign/4.1.7:
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
@@ -3718,6 +3799,7 @@ packages:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
dev: true
/onetime/5.1.2: /onetime/5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
@@ -3778,6 +3860,7 @@ packages:
/p-cancelable/2.1.1: /p-cancelable/2.1.1:
resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true
/p-limit/3.1.0: /p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
@@ -3843,9 +3926,6 @@ packages:
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
dev: true dev: true
/pend/1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
/picocolors/1.1.1: /picocolors/1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
dev: true dev: true
@@ -3935,6 +4015,7 @@ packages:
dependencies: dependencies:
end-of-stream: 1.4.5 end-of-stream: 1.4.5
once: 1.4.0 once: 1.4.0
dev: true
/punycode/2.3.1: /punycode/2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
@@ -3944,6 +4025,7 @@ packages:
/quick-lru/5.1.1: /quick-lru/5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true
/react-dom/19.2.1_react@19.2.1: /react-dom/19.2.1_react@19.2.1:
resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==}
@@ -4026,6 +4108,7 @@ packages:
/resolve-alpn/1.2.1: /resolve-alpn/1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
dev: true
/resolve-from/4.0.0: /resolve-from/4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
@@ -4045,6 +4128,7 @@ packages:
resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
dependencies: dependencies:
lowercase-keys: 2.0.0 lowercase-keys: 2.0.0
dev: true
/restore-cursor/3.1.0: /restore-cursor/3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
@@ -4067,18 +4151,6 @@ packages:
glob: 7.2.3 glob: 7.2.3
dev: true dev: true
/roarr/2.15.4:
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
engines: {node: '>=8.0'}
dependencies:
boolean: 3.2.0
detect-node: 2.1.0
globalthis: 1.0.4
json-stringify-safe: 5.0.1
semver-compare: 1.0.0
sprintf-js: 1.1.3
optional: true
/rollup/4.53.3: /rollup/4.53.3:
resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -4161,10 +4233,6 @@ packages:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
dev: true dev: true
/semver-compare/1.0.0:
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
optional: true
/semver/5.7.2: /semver/5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true hasBin: true
@@ -4173,19 +4241,13 @@ packages:
/semver/6.3.1: /semver/6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true hasBin: true
dev: true
/semver/7.7.3: /semver/7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
/serialize-error/7.0.1:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
dependencies:
type-fest: 0.13.1
optional: true
/set-function-length/1.2.2: /set-function-length/1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -4341,10 +4403,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/sprintf-js/1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
optional: true
/ssri/9.0.1: /ssri/9.0.1:
resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -4559,11 +4617,6 @@ packages:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
dev: true dev: true
/type-fest/0.13.1:
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
engines: {node: '>=10'}
optional: true
/typed-array-buffer/1.0.3: /typed-array-buffer/1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -4644,6 +4697,15 @@ packages:
/undici-types/6.21.0: /undici-types/6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
dev: true
/undici-types/7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
/undici/7.28.0:
resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
engines: {node: '>=20.18.1'}
optional: true
/unique-filename/2.0.1: /unique-filename/2.0.1:
resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==}
@@ -4659,10 +4721,6 @@ packages:
imurmurhash: 0.1.4 imurmurhash: 0.1.4
dev: true dev: true
/universalify/0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
/universalify/2.0.1: /universalify/2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
@@ -4845,6 +4903,7 @@ packages:
/wrappy/1.0.2: /wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/xmlbuilder/15.1.1: /xmlbuilder/15.1.1:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
@@ -4882,12 +4941,6 @@ packages:
yargs-parser: 21.1.1 yargs-parser: 21.1.1
dev: true dev: true
/yauzl/2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
dependencies:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
/yocto-queue/0.1.0: /yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+48
View File
@@ -0,0 +1,48 @@
const fs = require('node:fs')
const path = require('node:path')
const runtimeNames = [
'msvcp140.dll',
'msvcp140_1.dll',
'vcruntime140.dll',
'vcruntime140_1.dll'
]
function copyIfDifferent(sourcePath, targetPath) {
const source = fs.statSync(sourcePath)
const targetExists = fs.existsSync(targetPath)
if (targetExists) {
const target = fs.statSync(targetPath)
if (target.size === source.size && target.mtimeMs >= source.mtimeMs) {
return false
}
}
fs.copyFileSync(sourcePath, targetPath)
return true
}
function main() {
if (process.platform !== 'win32') return
const projectRoot = path.resolve(__dirname, '..')
const sourceDir = path.join(projectRoot, 'resources', 'runtime', 'win32')
const targetDir = path.join(projectRoot, 'node_modules', 'electron', 'dist')
if (!fs.existsSync(sourceDir) || !fs.existsSync(targetDir)) return
let copiedCount = 0
for (const name of runtimeNames) {
const sourcePath = path.join(sourceDir, name)
const targetPath = path.join(targetDir, name)
if (!fs.existsSync(sourcePath)) continue
if (copyIfDifferent(sourcePath, targetPath)) copiedCount += 1
}
if (copiedCount > 0) {
console.log(`[prepare-electron-runtime] synced ${copiedCount} runtime DLL(s) to ${targetDir}`)
}
}
main()
+53 -7
View File
@@ -1,8 +1,10 @@
import './preload-env'
import { app, shell, BrowserWindow, ipcMain, nativeImage, clipboard, Menu, Tray } from 'electron' import { app, shell, BrowserWindow, ipcMain, nativeImage, clipboard, Menu, Tray } from 'electron'
import { join } from 'path' import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset' import icon from '../../resources/icon.png?asset'
import { WechatDb } from './wechat-db' import { WechatDb } from './wechat-db'
import { bootstrapWcdbNative } from './wcdb4-client'
import { VoiceService } from './voice-service' import { VoiceService } from './voice-service'
import { StickerService } from './sticker-service' import { StickerService } from './sticker-service'
import { parseMessageContent } from './message-parser' import { parseMessageContent } from './message-parser'
@@ -11,6 +13,7 @@ import { exportGroupReport } from './group-report-service'
import { GroupReportExportRequest } from '../shared/group-report' import { GroupReportExportRequest } from '../shared/group-report'
import { DatabaseKeyStore } from './database-key-store' import { DatabaseKeyStore } from './database-key-store'
import { KeyServiceMac } from './key-service-mac' import { KeyServiceMac } from './key-service-mac'
import { KeyService as KeyServiceWin } from './key-service-win'
import * as chat from './services/chat-service' import * as chat from './services/chat-service'
import { import {
apiServer apiServer
@@ -33,14 +36,17 @@ let imageDecryptService: ImageDecryptService | null = null
let stickerService: StickerService | null = null let stickerService: StickerService | null = null
const databaseKeyStore = new DatabaseKeyStore() const databaseKeyStore = new DatabaseKeyStore()
const keyServiceMac = new KeyServiceMac() const keyServiceMac = new KeyServiceMac()
const keyServiceWin = new KeyServiceWin()
let tray: Tray | null = null let tray: Tray | null = null
// WCDB's Windows runtime checks the host application name during wcdb_init.
// Mirroring WeFlow's name unblocks the -1006 init failure on Windows.
app.setName(process.platform === 'win32' ? 'WeFlow' : 'WechatExplorer')
let dbInitInFlight: Promise<{ success: boolean; monitoring?: boolean; error?: string }> | null = null
const BUILD_MARK = 'wechat4-local-http-api-2026-07-03' const BUILD_MARK = 'wechat4-local-http-api-2026-07-03'
const TRAY_MODE = const TRAY_MODE =
process.argv.includes('--tray') || (process.env['WXE_TRAY'] || '').toString() === '1' process.argv.includes('--tray') || (process.env['WXE_TRAY'] || '').toString() === '1'
// WechatExplorer's WCDB native library runs InitProtection before wcdb_init.
// In dev, matching the host app name avoids failing the native protection gate.
app.setName('WechatExplorer')
function createWindow(): void { function createWindow(): void {
// 创建浏览器窗口 // 创建浏览器窗口
@@ -78,6 +84,17 @@ function createWindow(): void {
// 某些 API 只能在此事件发生后使用 // 某些 API 只能在此事件发生后使用
app.whenReady().then(async () => { app.whenReady().then(async () => {
console.log(`WechatExplorer main build: ${BUILD_MARK}`) console.log(`WechatExplorer main build: ${BUILD_MARK}`)
// WCDB's Windows runtime returns -1006 if wcdb_init is called more than once
// per process. Bootstrap native once here so any later Wcdb4Client instance
// reuses the already-initialized library and skips wcdb_init.
try {
bootstrapWcdbNative()
console.log('[WCDB4] bootstrap complete at whenReady top')
} catch (bootstrapError) {
console.error('[WCDB4] bootstrap failed at whenReady top:', bootstrapError)
}
// 为窗口设置应用程序用户模型 ID // 为窗口设置应用程序用户模型 ID
electronApp.setAppUserModelId('com.electron') electronApp.setAppUserModelId('com.electron')
@@ -91,11 +108,27 @@ app.whenReady().then(async () => {
// IPC test // IPC test
ipcMain.on('ping', () => console.log('pong')) ipcMain.on('ping', () => console.log('pong'))
ipcMain.handle('db:init', (_, key: string) => { ipcMain.handle('db:init', async (_, key: string) => {
if (dbInitInFlight) return dbInitInFlight
dbInitInFlight = (async () => {
try { try {
const trimmedKey = String(key || '').trim() const trimmedKey = String(key || '').trim()
console.log(`db:init build=${BUILD_MARK} keyLength=${trimmedKey.length}`) console.log(`db:init build=${BUILD_MARK} keyLength=${trimmedKey.length}`)
const nextWechatDb = new WechatDb(key) const settings = loadSettings()
if (
chat.isReady() &&
chat.getCurrentKey().replace(/^0x/i, '').trim() === trimmedKey.replace(/^0x/i, '') &&
(!settings.dbRoot || chat.getCurrentAccountRoot() === settings.dbRoot)
) {
console.log('[WCDB4] db:init reuse current connection')
return { success: true, monitoring: true }
}
const nextWechatDb = await WechatDb.create(key, settings.dbRoot)
const resolvedRoot = nextWechatDb.getWcdb4Client().getAccountRoot()
if (resolvedRoot && resolvedRoot !== settings.dbRoot) {
saveSettings({ ...settings, dbRoot: resolvedRoot })
}
chat.setChatDb(nextWechatDb) chat.setChatDb(nextWechatDb)
const wcdb4Client = nextWechatDb.getWcdb4Client() const wcdb4Client = nextWechatDb.getWcdb4Client()
voiceService = new VoiceService(wcdb4Client) voiceService = new VoiceService(wcdb4Client)
@@ -110,7 +143,12 @@ app.whenReady().then(async () => {
} catch (error) { } catch (error) {
console.error('Failed to init DB:', error) console.error('Failed to init DB:', error)
return { success: false, error: error instanceof Error ? error.message : String(error) } return { success: false, error: error instanceof Error ? error.message : String(error) }
} finally {
dbInitInFlight = null
} }
})()
return dbInitInFlight
}) })
ipcMain.handle('key:getSavedDbKey', async () => databaseKeyStore.load()) ipcMain.handle('key:getSavedDbKey', async () => databaseKeyStore.load())
@@ -125,9 +163,13 @@ app.whenReady().then(async () => {
ipcMain.handle('key:clearSavedDbKey', async () => databaseKeyStore.clear()) ipcMain.handle('key:clearSavedDbKey', async () => databaseKeyStore.clear())
ipcMain.handle('key:autoGetDbKey', async (event) => { ipcMain.handle('key:autoGetDbKey', async (event) => {
const result = await keyServiceMac.autoGetDbKey((message) => { const onStatus = (message: string): void => {
if (!event.sender.isDestroyed()) event.sender.send('key:dbKeyStatus', { message }) if (!event.sender.isDestroyed()) event.sender.send('key:dbKeyStatus', { message })
}) }
const result =
process.platform === 'win32'
? await keyServiceWin.autoGetDbKey(60_000, onStatus)
: await keyServiceMac.autoGetDbKey(onStatus)
if (!result.success || !result.key) return result if (!result.success || !result.key) return result
const saved = await databaseKeyStore.save(result.key) const saved = await databaseKeyStore.save(result.key)
@@ -140,6 +182,10 @@ app.whenReady().then(async () => {
ipcMain.handle('db:getContacts', (_, filter?: string) => chat.listContacts(filter)) ipcMain.handle('db:getContacts', (_, filter?: string) => chat.listContacts(filter))
ipcMain.handle('db:getContactAvatars', (_, usernames: string[]) =>
chat.getContactAvatars(usernames)
)
ipcMain.handle('db:getMessages', (_, userMd5: string, startTime?: number, endTime?: number) => ipcMain.handle('db:getMessages', (_, userMd5: string, startTime?: number, endTime?: number) =>
chat.listMessages(userMd5, startTime, endTime) chat.listMessages(userMd5, startTime, endTime)
) )
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
import fs from 'fs'
import path from 'path'
function prependPath(values: string[]): void {
if (process.platform !== 'win32') return
const existing = process.env.PATH || ''
const next = Array.from(new Set(values.filter(Boolean))).join(path.delimiter)
process.env.PATH = next ? `${next}${path.delimiter}${existing}` : existing
process.env.Path = process.env.PATH
}
try {
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
const resourceRoots = [
path.join(process.cwd(), 'resources'),
path.join(process.cwd(), 'resources', 'resources'),
path.join(process.resourcesPath || '', 'resources'),
process.resourcesPath || ''
].filter((value, index, list) => value && list.indexOf(value) === index && fs.existsSync(value))
const resourcesRoot = resourceRoots[0] || path.join(process.cwd(), 'resources')
const dllDirs = resourceRoots.flatMap((root) => [
root,
path.join(root, 'wcdb', 'win32', archDir),
path.join(root, 'wcdb', 'win32', 'x64'),
path.join(root, 'key', 'win32', archDir),
path.join(root, 'key', 'win32', 'x64'),
path.join(root, 'runtime', 'win32')
])
process.env.WCDB_RESOURCES_PATH = process.env.WCDB_RESOURCES_PATH || resourcesRoot
process.env.WEFLOW_PROJECT_NAME = process.env.WEFLOW_PROJECT_NAME || 'WeFlow'
prependPath(dllDirs.filter((dir) => fs.existsSync(dir)))
} catch (error) {
console.error('[WechatExplorer] failed to enforce local DLL priority:', error)
}
+39 -14
View File
@@ -14,6 +14,15 @@ export function getCurrentKey(): string {
} }
} }
export function getCurrentAccountRoot(): string {
if (!dbRef) return ''
try {
return dbRef.getWcdb4Client().getAccountRoot()
} catch {
return ''
}
}
export interface FormattedContact { export interface FormattedContact {
m_nsUsrName: string m_nsUsrName: string
m_nsNickName: string m_nsNickName: string
@@ -31,6 +40,7 @@ export interface FormattedMessage {
isSender: boolean isSender: boolean
img?: string img?: string
name?: string name?: string
senderId?: string
contentData?: ReturnType<typeof parseMessageContent> contentData?: ReturnType<typeof parseMessageContent>
voiceDataUrl?: string voiceDataUrl?: string
voiceDuration?: number voiceDuration?: number
@@ -133,6 +143,15 @@ export function listContacts(filter?: string): FormattedContact[] {
return contacts return contacts
} }
export function getContactAvatars(usernames: string[]): Record<string, string> {
if (!dbRef) return {}
const normalized = Array.from(
new Set((usernames || []).map((username) => String(username || '').trim()).filter(Boolean))
)
if (normalized.length === 0) return {}
return dbRef.getWcdb4Client().getAvatarUrls(normalized)
}
export function listMessages( export function listMessages(
userMd5: string, userMd5: string,
startTime?: number, startTime?: number,
@@ -140,16 +159,18 @@ export function listMessages(
): FormattedMessage[] { ): FormattedMessage[] {
if (!dbRef) return [] if (!dbRef) return []
const startedAt = Date.now()
const wcdb4Client = dbRef.getWcdb4Client() const wcdb4Client = dbRef.getWcdb4Client()
const username = wcdb4Client.getUsernameByMd5(userMd5) const username = wcdb4Client.getUsernameByMd5(userMd5)
console.log(
`[ChatService] listMessages begin md5=${userMd5} username=${username || ''} start=${startTime || 0} end=${endTime || 0}`
)
const rawMessages = dbRef.getUserMessages(userMd5, startTime, endTime) const rawMessages = dbRef.getUserMessages(userMd5, startTime, endTime)
const groupMembers = dbRef.getGroupMembersForChat(userMd5) console.log(
const myAvatar = dbRef.getMyAvatarUrl() `[ChatService] listMessages native done md5=${userMd5} raw=${rawMessages.length} cost=${Date.now() - startedAt}ms`
const myGroupNickname = username?.endsWith('@chatroom') )
? wcdb4Client.getMyGroupNickname(username)
: undefined
return rawMessages.map((msg: WechatMessage) => { const formatted = rawMessages.map((msg: WechatMessage) => {
const rawMsgType = parseInt(msg.messageType) const rawMsgType = parseInt(msg.messageType)
const msgType = normalizeMsgType(msg.messageType) const msgType = normalizeMsgType(msg.messageType)
const createTime = parseInt(msg.msgCreateTime) const createTime = parseInt(msg.msgCreateTime)
@@ -160,9 +181,9 @@ export function listMessages(
let content = msg.msgContent let content = msg.msgContent
let img = '' let img = ''
let name = '' let name = ''
let senderId = typeof msg.sender === 'string' ? msg.sender : ''
if (isMine) { if (isMine) {
if (myAvatar) img = myAvatar name = typeof msg.senderNickname === 'string' ? msg.senderNickname : ''
name = myGroupNickname || (typeof msg.senderNickname === 'string' ? msg.senderNickname : '')
} else { } else {
if (typeof msg.senderAvatar === 'string') img = msg.senderAvatar if (typeof msg.senderAvatar === 'string') img = msg.senderAvatar
if (typeof msg.senderNickname === 'string') name = msg.senderNickname if (typeof msg.senderNickname === 'string') name = msg.senderNickname
@@ -172,15 +193,13 @@ export function listMessages(
if (colonIndex > 0) { if (colonIndex > 0) {
const potentialWxid = content.substring(0, colonIndex) const potentialWxid = content.substring(0, colonIndex)
if (potentialWxid.startsWith('wxid_')) { if (potentialWxid.startsWith('wxid_')) {
const member = dbRef!.getGroupMember(potentialWxid) senderId = senderId || potentialWxid
if (member) img = member.m_nsHeadImgUrl name = name || potentialWxid
if (groupMembers[potentialWxid]) { content = content.substring(colonIndex + 1)
name = groupMembers[potentialWxid]
content = content.substring(colonIndex + 1)
}
} }
} }
} }
if (!isMine && !name && senderId) name = senderId
let contentData: ReturnType<typeof parseMessageContent> | undefined let contentData: ReturnType<typeof parseMessageContent> | undefined
let displayType = MSG_TYPE_DICT[msgType] || msg.messageType let displayType = MSG_TYPE_DICT[msgType] || msg.messageType
@@ -245,12 +264,18 @@ export function listMessages(
content, content,
img, img,
name, name,
senderId,
sessionId: username, sessionId: username,
localId, localId,
createTime, createTime,
contentData contentData
} }
}) })
console.log(
`[ChatService] listMessages end md5=${userMd5} formatted=${formatted.length} cost=${Date.now() - startedAt}ms`
)
return formatted
} }
export function getGroupSnapshot(userMd5: string): GroupSnapshot | null { export function getGroupSnapshot(userMd5: string): GroupSnapshot | null {
+96 -4
View File
@@ -10,11 +10,100 @@ export interface AppSettings {
apiPort: number apiPort: number
} }
function getDefaultDbRoot(): string {
const home = os.homedir()
const candidates = getDefaultDbRootCandidates(home)
return candidates.find((candidate) => isUsableDbRoot(candidate)) || candidates[0]
}
function getDefaultDbRootCandidates(home: string): string[] {
if (process.platform !== 'win32') {
return [path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files')]
}
const candidates = [
...getWeflowDbPathCandidates(home),
path.join(home, 'Documents', 'WeChat Files'),
path.join(home, 'Documents', 'xwechat_files'),
path.join(home, 'WeChat Files'),
path.join(os.homedir(), 'AppData', 'Roaming', 'Tencent', 'xwechat_files')
]
for (const drive of getWindowsDrives()) {
candidates.push(path.join(`${drive}:\\`, 'xwechat_files'))
candidates.push(path.join(`${drive}:\\`, 'WeChat Files'))
for (const child of listDirectories(`${drive}:\\`)) {
candidates.push(path.join(child, 'xwechat_files'))
candidates.push(path.join(child, 'WeChat Files'))
}
}
return unique(candidates)
}
function getWeflowDbPathCandidates(home: string): string[] {
const configPaths = [
path.join(home, 'AppData', 'Roaming', 'weflow', 'WeFlow-config.json'),
path.join(home, 'AppData', 'Roaming', 'WeFlow', 'WeFlow-config.json')
]
const candidates: string[] = []
for (const configPath of configPaths) {
try {
const config = fs.readJsonSync(configPath) as { dbPath?: unknown }
if (typeof config.dbPath === 'string' && config.dbPath.trim()) {
candidates.push(config.dbPath.trim())
}
} catch {
// WeFlow is optional; ignore missing or unreadable config.
}
}
return candidates
}
function getWindowsDrives(): string[] {
const drives: string[] = []
for (let code = 67; code <= 90; code += 1) {
const drive = String.fromCharCode(code)
if (fs.existsSync(`${drive}:\\`)) drives.push(drive)
}
return drives
}
function listDirectories(root: string): string[] {
try {
return fs
.readdirSync(root)
.map((name) => path.join(root, name))
.filter((candidate) => {
try {
return fs.statSync(candidate).isDirectory()
} catch {
return false
}
})
} catch {
return []
}
}
function unique(values: string[]): string[] {
return Array.from(new Set(values))
}
function isUsableDbRoot(candidate?: string): boolean {
if (!candidate || !fs.existsSync(candidate)) return false
if (fs.existsSync(path.join(candidate, 'db_storage'))) return true
try {
return fs
.readdirSync(candidate)
.some((name) => fs.existsSync(path.join(candidate, name, 'db_storage')))
} catch {
return false
}
}
const DEFAULT_SETTINGS: AppSettings = { const DEFAULT_SETTINGS: AppSettings = {
dbRoot: path.join( dbRoot: getDefaultDbRoot(),
os.homedir(),
'Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files'
),
apiEnabled: true, apiEnabled: true,
apiHost: '127.0.0.1', apiHost: '127.0.0.1',
apiPort: 6131 apiPort: 6131
@@ -37,6 +126,9 @@ export function loadSettings(): AppSettings {
if (fs.existsSync(SETTINGS_FILE)) { if (fs.existsSync(SETTINGS_FILE)) {
const raw = fs.readJsonSync(SETTINGS_FILE) as Partial<AppSettings> const raw = fs.readJsonSync(SETTINGS_FILE) as Partial<AppSettings>
cache = { ...DEFAULT_SETTINGS, ...raw } cache = { ...DEFAULT_SETTINGS, ...raw }
if (process.platform === 'win32' && !isUsableDbRoot(cache.dbRoot)) {
cache.dbRoot = getDefaultDbRoot()
}
return cache return cache
} }
} catch (error) { } catch (error) {
+418 -81
View File
@@ -41,6 +41,83 @@ type KoffiModule = {
decode: (ptr: unknown, type: string, length: number) => string decode: (ptr: unknown, type: string, length: number) => string
} }
// Module-level singleton for WCDB native handle. WCDB's Windows runtime
// returns -1006 if wcdb_init is called more than once per process, so we
// perform InitProtection + wcdb_init exactly once and cache the resulting
// library reference for every Wcdb4Client instance.
let wcdbBootstrapLib: KoffiLibrary | null = null
export function bootstrapWcdbNative(libPath?: string, libDirOverride?: string): KoffiLibrary {
if (wcdbBootstrapLib) return wcdbBootstrapLib
const koffi = nodeRequire('koffi') as KoffiModule
const resolvedLibPath = libPath || Wcdb4Client.resolveNativeLibrary()
const libDir = libDirOverride || path.dirname(resolvedLibPath)
console.log(
`[WCDB4] bootstrap koffi.load begin ${resolvedLibPath} cwd=${process.cwd()} resourcesPath=${process.resourcesPath || ''}`
)
for (const name of process.platform === 'win32'
? ['WCDB.dll', 'SDL2.dll']
: process.platform === 'darwin'
? ['libWCDB.dylib']
: []) {
const preloadPath = path.join(libDir, name)
if (!fs.existsSync(preloadPath)) continue
try {
koffi.load(preloadPath)
console.log(`[WCDB4] bootstrap preload ok ${preloadPath}`)
} catch {
console.warn(`[WCDB4] bootstrap preload failed ${preloadPath}`)
}
}
const lib = koffi.load(resolvedLibPath)
console.log(`[WCDB4] bootstrap koffi.load ok ${resolvedLibPath}`)
const initProtection = lib.func('int32 InitProtection(const char* resourcePath)') as (
resourcePath: string
) => number
const resourceRoots = Array.from(
new Set([
libDir,
path.dirname(libDir),
process.env.WCDB_RESOURCES_PATH || '',
path.join(process.resourcesPath || process.cwd(), 'resources'),
process.resourcesPath || process.cwd(),
path.join(process.cwd(), 'resources')
])
)
let lastCode = -1
let initOk = false
for (const resourceRoot of resourceRoots) {
try {
console.log(`[WCDB4] bootstrap InitProtection call ${resourceRoot}`)
lastCode = Number(initProtection(resourceRoot))
console.log(`[WCDB4] bootstrap InitProtection rc=${lastCode} path=${resourceRoot}`)
if (lastCode === 0) {
initOk = true
console.log(`[WCDB4] bootstrap InitProtection ok path=${resourceRoot}`)
break
}
} catch (error) {
console.warn(`[WCDB4] bootstrap InitProtection exception path=${resourceRoot}`, error)
}
}
if (!initOk) {
throw new Error(`InitProtection 失败,错误码: ${lastCode}; tried=${resourceRoots.join(' | ')}`)
}
const wcdbInit = lib.func('int32 wcdb_init()') as () => number
console.log('[WCDB4] bootstrap wcdb_init begin')
const initRc = Number(wcdbInit())
console.log(`[WCDB4] bootstrap wcdb_init rc=${initRc}`)
if (initRc !== 0) {
throw new Error(`wcdb_init 失败,错误码: ${initRc}`)
}
wcdbBootstrapLib = lib
return lib
}
type KoffiLibrary = { type KoffiLibrary = {
func: (signature: string) => (...args: unknown[]) => unknown func: (signature: string) => (...args: unknown[]) => unknown
} }
@@ -51,10 +128,7 @@ type WcdbHandleOut = [number]
const nodeRequire = createRequire(import.meta.url) const nodeRequire = createRequire(import.meta.url)
export class Wcdb4Client { export class Wcdb4Client {
static readonly defaultRoot = path.join( static readonly defaultRoot = Wcdb4Client.findExistingDefaultRoot()
os.homedir(),
'Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files'
)
private readonly key: string private readonly key: string
private readonly accountRoot: string private readonly accountRoot: string
@@ -63,13 +137,12 @@ export class Wcdb4Client {
private readonly sessionDbPath: string private readonly sessionDbPath: string
private koffi: KoffiModule | null = null private koffi: KoffiModule | null = null
private handle: number | null = null private handle: number | null = null
private initialized = false
private displayNameCache = new Map<string, string>() private displayNameCache = new Map<string, string>()
private avatarCache = new Map<string, string>() private avatarCache = new Map<string, string>()
private groupNicknameCache = new Map<string, Map<string, string>>() private groupNicknameCache = new Map<string, Map<string, string>>()
private cachedSessions: Wcdb4Session[] | null = null private cachedSessions: Wcdb4Session[] | null = null
private cachedChatTables: { name: string; db_number: string }[] | null = null
private wcdbInit: (() => number) | null = null
private wcdbShutdown: (() => number) | null = null private wcdbShutdown: (() => number) | null = null
private wcdbOpenAccount: private wcdbOpenAccount:
| ((sessionDbPath: string, key: string, handleOut: WcdbHandleOut) => number) | ((sessionDbPath: string, key: string, handleOut: WcdbHandleOut) => number)
@@ -86,6 +159,9 @@ export class Wcdb4Client {
outJson: WcdbVoidOut outJson: WcdbVoidOut
) => number) ) => number)
| null = null | null = null
private wcdbGetMessageTableStats:
| ((handle: number, username: string, outJson: WcdbVoidOut) => number)
| null = null
private wcdbGetDisplayNames: private wcdbGetDisplayNames:
| ((handle: number, usernamesJson: string, outJson: WcdbVoidOut) => number) | ((handle: number, usernamesJson: string, outJson: WcdbVoidOut) => number)
| null = null | null = null
@@ -158,11 +234,11 @@ export class Wcdb4Client {
} }
static resolveAccountRoot(accountRoot: string): string { static resolveAccountRoot(accountRoot: string): string {
const target = (accountRoot || '').trim().replace(/\/+$/, '') const target = (accountRoot || '').trim().replace(/[\\/]+$/, '')
if (!target) { if (!target) {
throw new Error('微信 4.0 账号目录不能为空') throw new Error('微信 4.0 账号目录不能为空')
} }
if (fs.existsSync(path.join(target, 'db_storage'))) { if (Wcdb4Client.hasDbStorage(target)) {
return target return target
} }
if (!fs.existsSync(target)) { if (!fs.existsSync(target)) {
@@ -171,17 +247,8 @@ export class Wcdb4Client {
const candidates = fs const candidates = fs
.readdirSync(target) .readdirSync(target)
.map((name) => path.join(target, name)) .map((name) => path.join(target, name))
.filter((candidate) => { .filter((candidate) => Wcdb4Client.hasDbStorage(candidate))
try { .sort(Wcdb4Client.compareAccountDirs)
return (
fs.statSync(candidate).isDirectory() &&
fs.existsSync(path.join(candidate, 'db_storage'))
)
} catch {
return false
}
})
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)
if (!candidates[0]) { if (!candidates[0]) {
throw new Error(`未找到包含 db_storage 的微信 4.0 账号目录: ${target}`) throw new Error(`未找到包含 db_storage 的微信 4.0 账号目录: ${target}`)
} }
@@ -194,24 +261,15 @@ export class Wcdb4Client {
throw new Error(`未找到微信 4.0 数据目录: ${root}`) throw new Error(`未找到微信 4.0 数据目录: ${root}`)
} }
if (fs.existsSync(path.join(root, 'db_storage'))) { if (Wcdb4Client.hasDbStorage(root)) {
return root return root
} }
const candidates = fs const candidates = fs
.readdirSync(root) .readdirSync(root)
.map((name) => path.join(root, name)) .map((name) => path.join(root, name))
.filter((candidate) => { .filter((candidate) => Wcdb4Client.hasDbStorage(candidate))
try { .sort(Wcdb4Client.compareAccountDirs)
return (
fs.statSync(candidate).isDirectory() &&
fs.existsSync(path.join(candidate, 'db_storage'))
)
} catch {
return false
}
})
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)
if (!candidates[0]) { if (!candidates[0]) {
throw new Error(`未找到包含 db_storage 的微信 4.0 账号目录: ${root}`) throw new Error(`未找到包含 db_storage 的微信 4.0 账号目录: ${root}`)
@@ -220,6 +278,117 @@ export class Wcdb4Client {
return candidates[0] return candidates[0]
} }
private static findExistingDefaultRoot(): string {
const roots = Wcdb4Client.getDefaultRootCandidates()
return roots.find((root) => Wcdb4Client.isUsableRoot(root)) || roots[0]
}
private static getDefaultRootCandidates(): string[] {
const home = os.homedir()
if (process.platform === 'win32') {
const candidates = [
...Wcdb4Client.getWeflowDbPathCandidates(home),
path.join(home, 'Documents', 'WeChat Files'),
path.join(home, 'Documents', 'xwechat_files'),
path.join(home, 'WeChat Files'),
path.join(home, 'AppData', 'Roaming', 'Tencent', 'xwechat_files')
]
for (const drive of Wcdb4Client.getWindowsDrives()) {
candidates.push(path.join(`${drive}:\\`, 'xwechat_files'))
candidates.push(path.join(`${drive}:\\`, 'WeChat Files'))
for (const child of Wcdb4Client.listDirectories(`${drive}:\\`)) {
candidates.push(path.join(child, 'xwechat_files'))
candidates.push(path.join(child, 'WeChat Files'))
}
}
return Array.from(new Set(candidates))
}
return [path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files')]
}
private static getWeflowDbPathCandidates(home: string): string[] {
const configPaths = [
path.join(home, 'AppData', 'Roaming', 'weflow', 'WeFlow-config.json'),
path.join(home, 'AppData', 'Roaming', 'WeFlow', 'WeFlow-config.json')
]
const candidates: string[] = []
for (const configPath of configPaths) {
try {
const config = fs.readJsonSync(configPath) as { dbPath?: unknown }
if (typeof config.dbPath === 'string' && config.dbPath.trim()) {
candidates.push(config.dbPath.trim())
}
} catch {
// WeFlow is optional; ignore missing or unreadable config.
}
}
return candidates
}
private static getWindowsDrives(): string[] {
const drives: string[] = []
for (let code = 67; code <= 90; code += 1) {
const drive = String.fromCharCode(code)
if (fs.existsSync(`${drive}:\\`)) drives.push(drive)
}
return drives
}
private static listDirectories(root: string): string[] {
try {
return fs
.readdirSync(root)
.map((name) => path.join(root, name))
.filter((candidate) => {
try {
return fs.statSync(candidate).isDirectory()
} catch {
return false
}
})
} catch {
return []
}
}
private static hasDbStorage(candidate: string): boolean {
try {
return fs.statSync(candidate).isDirectory() && fs.existsSync(path.join(candidate, 'db_storage'))
} catch {
return false
}
}
private static isUsableRoot(candidate: string): boolean {
if (!fs.existsSync(candidate)) return false
if (Wcdb4Client.hasDbStorage(candidate)) return true
try {
return fs
.readdirSync(candidate)
.some((name) => Wcdb4Client.hasDbStorage(path.join(candidate, name)))
} catch {
return false
}
}
private static hasSessionDb(candidate: string): boolean {
return (
fs.existsSync(path.join(candidate, 'db_storage', 'session', 'session.db')) ||
fs.existsSync(path.join(candidate, 'db_storage', 'session.db'))
)
}
private static compareAccountDirs(a: string, b: string): number {
const aHasSession = Wcdb4Client.hasSessionDb(a)
const bHasSession = Wcdb4Client.hasSessionDb(b)
if (aHasSession !== bHasSession) return aHasSession ? -1 : 1
try {
return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs
} catch {
return 0
}
}
private static cleanAccountDirName(dirName: string): string { private static cleanAccountDirName(dirName: string): string {
const trimmed = dirName.trim() const trimmed = dirName.trim()
if (!trimmed) return trimmed if (!trimmed) return trimmed
@@ -236,24 +405,21 @@ export class Wcdb4Client {
open(): void { open(): void {
this.loadNativeLibrary() this.loadNativeLibrary()
if (!this.wcdbInit || !this.wcdbOpenAccount) { if (!this.wcdbOpenAccount) {
throw new Error('WCDB 4.0 native 接口未就绪') throw new Error('WCDB 4.0 native 接口未就绪')
} }
if (!this.initialized) { let openResult = -1
const initResult = this.wcdbInit()
if (initResult !== 0) {
console.warn(`wcdb_init 返回 ${initResult},继续尝试 wcdb_open_account`)
} else {
this.initialized = true
}
}
const handleOut: WcdbHandleOut = [0] const handleOut: WcdbHandleOut = [0]
const openResult = this.wcdbOpenAccount(this.sessionDbPath, this.key, handleOut) openResult = this.wcdbOpenAccount(this.sessionDbPath, this.key, handleOut)
if (openResult !== 0 || handleOut[0] <= 0) { if (openResult !== 0 || handleOut[0] <= 0) {
const hint =
openResult === -1005
? ';这通常表示密钥与当前账号数据库不匹配,请确认微信已登录目标账号,并重新自动获取密钥'
: ''
throw new Error( throw new Error(
`wcdb_open_account 失败,错误码: ${openResult}; sessionDb=${this.sessionDbPath}; accountRoot=${this.accountRoot}; wxid=${this.wxid}` `wcdb_open_account 失败,错误码: ${openResult}${hint}; sessionDb=${this.sessionDbPath}; accountRoot=${this.accountRoot}; wxid=${this.wxid}; keyLength=${this.key.length}`
) )
} }
@@ -269,16 +435,17 @@ export class Wcdb4Client {
close(): void { close(): void {
this.stopMonitor() this.stopMonitor()
if (!this.initialized || !this.wcdbShutdown) return if (this.handle === null || !this.wcdbShutdown) return
try { if (process.platform !== 'win32') {
this.wcdbShutdown() try {
} catch { this.wcdbShutdown()
// Mirror WechatExplorer: shutdown is best-effort on app close. } catch {
// Shutdown is best-effort on app close.
}
} }
this.handle = null this.handle = null
this.initialized = false
this.cachedSessions = null this.cachedSessions = null
this.displayNameCache.clear() this.displayNameCache.clear()
this.avatarCache.clear() this.avatarCache.clear()
@@ -425,36 +592,51 @@ export class Wcdb4Client {
if (this.cachedSessions) return this.cachedSessions if (this.cachedSessions) return this.cachedSessions
if (!this.wcdbGetSessions) return [] if (!this.wcdbGetSessions) return []
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) => const rows = this.readSessionRows()
this.wcdbGetSessions!(handle, outJson)
)
const sessions = (Array.isArray(rows) ? rows : []) const sessions = (Array.isArray(rows) ? rows : [])
.map((row) => this.normalizeSession(row)) .map((row) => this.normalizeSession(row))
.filter((session) => session.username) .filter((session) => session.username)
const sessionUsernames = sessions.map((session) => session.username) const sessionUsernames = sessions.map((session) => session.username)
this.hydrateDisplayNames(sessionUsernames) this.hydrateDisplayNames(sessionUsernames)
this.hydrateAvatarUrls(sessionUsernames)
this.cachedSessions = sessions.map((session) => ({ this.cachedSessions = sessions.map((session) => ({
...session, ...session,
nickname: this.displayNameCache.get(session.username) || session.nickname || session.username, nickname: this.displayNameCache.get(session.username) || session.nickname || session.username
avatar: this.avatarCache.get(session.username)
})) }))
return this.cachedSessions return this.cachedSessions
} }
getChatTables(): { name: string; db_number: string }[] { getChatTables(): { name: string; db_number: string }[] {
return this.getSessions().map((session) => ({ if (this.cachedChatTables) return this.cachedChatTables
const sessions =
this.cachedSessions ||
this.readSessionRows()
.map((row) => this.normalizeSession(row))
.filter((session) => session.username)
this.cachedChatTables = sessions.map((session) => ({
name: `Chat_${this.md5(session.username)}`, name: `Chat_${this.md5(session.username)}`,
db_number: session.username db_number: session.username
})) }))
return this.cachedChatTables
} }
getMessages(username: string, startTime?: number, endTime?: number): Wcdb4Message[] { getMessages(username: string, startTime?: number, endTime?: number): Wcdb4Message[] {
const cursorMessages = this.getMessagesByCursor(username, startTime, endTime) const startedAt = Date.now()
if (cursorMessages) return cursorMessages console.log(
`[WCDB4] getMessages begin username=${username} start=${startTime || 0} end=${endTime || 0}`
)
try {
const cursorMessages = this.getMessagesByCursor(username, startTime, endTime)
if (cursorMessages) {
console.log(
`[WCDB4] getMessages cursor ok username=${username} rows=${cursorMessages.length} cost=${Date.now() - startedAt}ms`
)
return cursorMessages
}
} catch (error) {
console.warn(`[WCDB4] cursor messages failed username=${username}:`, error)
}
if (!this.wcdbGetMessages) return [] if (!this.wcdbGetMessages) return []
@@ -463,15 +645,96 @@ export class Wcdb4Client {
let offset = 0 let offset = 0
while (true) { while (true) {
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) => let rows: Record<string, unknown>[]
this.wcdbGetMessages!(handle, username, limit, offset, outJson) try {
) rows = this.callJson<Record<string, unknown>[]>((handle, outJson) =>
this.wcdbGetMessages!(handle, username, limit, offset, outJson)
)
} catch (error) {
console.warn(
`[WCDB4] get_messages failed username=${username} offset=${offset}:`,
error
)
break
}
const batch = Array.isArray(rows) ? rows : [] const batch = Array.isArray(rows) ? rows : []
allRows.push(...batch) allRows.push(...batch)
if (batch.length < limit) break if (batch.length < limit) break
offset += limit offset += limit
} }
if (allRows.length === 0) {
const tableRows = this.getMessagesByTableScan(username, startTime, endTime)
if (tableRows.length > 0) {
console.log(
`[WCDB4] getMessages table scan ok username=${username} rows=${tableRows.length} cost=${Date.now() - startedAt}ms`
)
return tableRows
}
}
const messages = this.finalizeMessages(username, allRows, startTime, endTime)
console.log(
`[WCDB4] getMessages direct ok username=${username} rows=${messages.length} cost=${Date.now() - startedAt}ms`
)
return messages
}
private readSessionRows(): Record<string, unknown>[] {
if (!this.wcdbGetSessions) return []
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) =>
this.wcdbGetSessions!(handle, outJson)
)
return Array.isArray(rows) ? rows : []
}
private getMessagesByTableScan(
username: string,
startTime?: number,
endTime?: number
): Wcdb4Message[] {
if (!this.wcdbGetMessageTableStats || !this.wcdbExecQuery) return []
let tables: { tableName: string; dbPath: string }[] = []
try {
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) =>
this.wcdbGetMessageTableStats!(handle, username, outJson)
)
tables = (Array.isArray(rows) ? rows : [])
.map((row) => ({
tableName: this.pickString(row, ['table_name', 'tableName', 'name']),
dbPath: this.pickString(row, ['db_path', 'dbPath', 'path'])
}))
.filter((row) => row.tableName && row.dbPath)
} catch (error) {
console.warn(`[WCDB4] message table stats failed username=${username}:`, error)
return []
}
const allRows: Record<string, unknown>[] = []
const begin = this.normalizeTimestamp(startTime || 0)
const end = this.normalizeTimestamp(endTime || 0)
const where = [
begin > 0 ? `"create_time" >= ${begin}` : '',
end > 0 ? `"create_time" <= ${end}` : ''
].filter(Boolean)
const whereSql = where.length ? ` WHERE ${where.join(' AND ')}` : ''
for (const table of tables) {
try {
const sql = `SELECT * FROM ${this.quoteSqlIdentifier(table.tableName)}${whereSql} ORDER BY "create_time" ASC LIMIT 5000`
const rows = this.callJson<Record<string, unknown>[]>((handle, outJson) =>
this.wcdbExecQuery!(handle, 'message', table.dbPath, sql, outJson)
)
if (Array.isArray(rows)) allRows.push(...rows)
} catch (error) {
console.warn(
`[WCDB4] message table scan failed username=${username} db=${table.dbPath} table=${table.tableName}:`,
error
)
}
}
return this.finalizeMessages(username, allRows, startTime, endTime) return this.finalizeMessages(username, allRows, startTime, endTime)
} }
@@ -487,6 +750,17 @@ export class Wcdb4Client {
return undefined return undefined
} }
getAvatarUrls(usernames: string[]): Record<string, string> {
const normalized = this.uniq(usernames)
this.hydrateAvatarUrls(normalized)
const result: Record<string, string> = {}
for (const username of normalized) {
const avatar = this.avatarCache.get(username)
if (avatar) result[username] = avatar
}
return result
}
getMyGroupNickname(chatroomId: string): string | undefined { getMyGroupNickname(chatroomId: string): string | undefined {
const groupNicknames = this.getGroupNicknames(chatroomId) const groupNicknames = this.getGroupNicknames(chatroomId)
for (const candidate of this.getMyUsernameCandidates()) { for (const candidate of this.getMyUsernameCandidates()) {
@@ -565,9 +839,6 @@ export class Wcdb4Client {
endTime?: number endTime?: number
): Wcdb4Message[] { ): Wcdb4Message[] {
const messages = rows.map((row) => this.normalizeMessage(row)) const messages = rows.map((row) => this.normalizeMessage(row))
const senderIds = messages.map((message) => message.sender || '').filter(Boolean)
this.hydrateDisplayNames(senderIds)
this.hydrateAvatarUrls(senderIds)
return messages return messages
.filter((message) => { .filter((message) => {
@@ -650,6 +921,12 @@ export class Wcdb4Client {
.map((member) => member.m_nsUsrName) .map((member) => member.m_nsUsrName)
.filter(Boolean) .filter(Boolean)
this.hydrateDisplayNames(missingDisplayNames) this.hydrateDisplayNames(missingDisplayNames)
this.hydrateAvatarUrls(
members
.filter((member) => !member.m_nsHeadImgUrl)
.map((member) => member.m_nsUsrName)
.filter(Boolean)
)
return members.map((member) => ({ return members.map((member) => ({
...member, ...member,
nickname: nickname:
@@ -815,19 +1092,49 @@ export class Wcdb4Client {
const libPath = this.findNativeLibrary() const libPath = this.findNativeLibrary()
const libDir = path.dirname(libPath) const libDir = path.dirname(libPath)
const wcdbCorePath = path.join(libDir, 'libWCDB.dylib') console.log(
if (fs.existsSync(wcdbCorePath)) { `[WCDB4] loadNativeLibrary platform=${process.platform} arch=${process.arch} libPath=${libPath} cwd=${process.cwd()} resourcesPath=${process.resourcesPath || ''} WCDB_RESOURCES_PATH=${process.env.WCDB_RESOURCES_PATH || ''}`
try { )
koffi.load(wcdbCorePath)
} catch { // Reuse the module-level bootstrap if main.ts already performed
// Some builds resolve this dependency through rpath. // InitProtection + wcdb_init; WCDB returns -1006 if wcdb_init is called
// twice in the same process.
let lib: KoffiLibrary
if (wcdbBootstrapLib) {
console.log('[WCDB4] reusing bootstrap lib, skip koffi.load and wcdb_init')
lib = wcdbBootstrapLib
} else {
const preloadLibraries =
process.platform === 'win32'
? ['WCDB.dll', 'SDL2.dll']
: process.platform === 'darwin'
? ['libWCDB.dylib']
: []
for (const name of preloadLibraries) {
const preloadPath = path.join(libDir, name)
if (!fs.existsSync(preloadPath)) continue
try {
koffi.load(preloadPath)
console.log(`[WCDB4] preload ok ${preloadPath}`)
} catch {
console.warn(`[WCDB4] preload failed ${preloadPath}`)
// Some builds resolve dependencies through the platform loader path.
}
}
console.log(`[WCDB4] koffi.load begin ${libPath}`)
lib = koffi.load(libPath)
console.log(`[WCDB4] koffi.load ok ${libPath}`)
this.initProtection(lib, libDir)
const wcdbInit = lib.func('int32 wcdb_init()') as () => number
console.log('[WCDB4] wcdb_init begin')
const initResult = wcdbInit()
console.log(`[WCDB4] wcdb_init rc=${initResult}`)
if (initResult !== 0) {
throw new Error(`wcdb_init 失败,错误码: ${initResult}`)
} }
} }
const lib = koffi.load(libPath)
this.initProtection(lib, libDir)
this.wcdbInit = lib.func('int32 wcdb_init()') as () => number
this.wcdbShutdown = lib.func('int32 wcdb_shutdown()') as () => number this.wcdbShutdown = lib.func('int32 wcdb_shutdown()') as () => number
this.wcdbOpenAccount = lib.func( this.wcdbOpenAccount = lib.func(
'int32 wcdb_open_account(const char* path, const char* key, _Out_ int64* handle)' 'int32 wcdb_open_account(const char* path, const char* key, _Out_ int64* handle)'
@@ -845,6 +1152,13 @@ export class Wcdb4Client {
offset: number, offset: number,
outJson: WcdbVoidOut outJson: WcdbVoidOut
) => number ) => number
try {
this.wcdbGetMessageTableStats = lib.func(
'int32 wcdb_get_message_table_stats(int64 handle, const char* sessionId, _Out_ void** outJson)'
) as (handle: number, username: string, outJson: WcdbVoidOut) => number
} catch {
this.wcdbGetMessageTableStats = null
}
this.wcdbGetDisplayNames = lib.func( this.wcdbGetDisplayNames = lib.func(
'int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)' 'int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)'
) as (handle: number, usernamesJson: string, outJson: WcdbVoidOut) => number ) as (handle: number, usernamesJson: string, outJson: WcdbVoidOut) => number
@@ -986,29 +1300,43 @@ export class Wcdb4Client {
let lastCode = -1 let lastCode = -1
for (const resourceRoot of resourceRoots) { for (const resourceRoot of resourceRoots) {
try { try {
console.log(`[WCDB4] InitProtection call ${resourceRoot}`)
lastCode = initProtection(resourceRoot) lastCode = initProtection(resourceRoot)
if (lastCode === 0) return console.log(`[WCDB4] InitProtection rc=${lastCode} path=${resourceRoot}`)
} catch { if (lastCode === 0) {
console.log(`[WCDB4] InitProtection ok path=${resourceRoot}`)
return
}
} catch (error) {
console.warn(`[WCDB4] InitProtection exception path=${resourceRoot}`, error)
// Try next candidate. // Try next candidate.
} }
} }
console.warn( throw new Error(`InitProtection 失败,错误码: ${lastCode}; tried=${resourceRoots.join(' | ')}`)
`InitProtection 返回 ${lastCode},继续尝试 wcdb_init/open; tried=${resourceRoots.join(' | ')}`
)
} }
private findNativeLibrary(): string { private findNativeLibrary(): string {
return Wcdb4Client.resolveNativeLibrary()
}
static resolveNativeLibrary(): string {
const libName = const libName =
process.platform === 'darwin' process.platform === 'darwin'
? 'libwcdb_api.dylib' ? 'libwcdb_api.dylib'
: process.platform === 'linux' : process.platform === 'linux'
? 'libwcdb_api.so' ? 'libwcdb_api.so'
: 'wcdb_api.dll' : 'wcdb_api.dll'
const platformDir = process.platform === 'darwin' ? 'macos' : process.platform const platformDir =
process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'win32' : process.platform
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
const resourcesPath = process.resourcesPath || process.cwd() const resourcesPath = process.resourcesPath || process.cwd()
const candidates = [ const candidates = [
process.env.WCDB_DLL_PATH, process.env.WCDB_DLL_PATH,
path.join(resourcesPath, 'resources', 'wcdb', platformDir, archDir, libName),
path.join(resourcesPath, 'resources', 'wcdb', platformDir, 'x64', libName),
path.join(process.cwd(), 'resources', 'wcdb', platformDir, archDir, libName),
path.join(process.cwd(), 'resources', 'wcdb', platformDir, 'x64', libName),
path.join(resourcesPath, 'resources', platformDir, libName), path.join(resourcesPath, 'resources', platformDir, libName),
path.join(resourcesPath, 'resources', libName), path.join(resourcesPath, 'resources', libName),
path.join(process.cwd(), 'resources', platformDir, libName), path.join(process.cwd(), 'resources', platformDir, libName),
@@ -1019,6 +1347,11 @@ export class Wcdb4Client {
if (!found) { if (!found) {
throw new Error(`找不到 WCDB native 库: ${candidates.join(', ')}`) throw new Error(`找不到 WCDB native 库: ${candidates.join(', ')}`)
} }
if (process.platform === 'win32') {
const runtimeDir = path.join(resourcesPath, 'resources', 'runtime', 'win32')
const dllDir = path.dirname(found)
process.env.PATH = [dllDir, runtimeDir, process.env.PATH || ''].filter(Boolean).join(path.delimiter)
}
return found return found
} }
@@ -1437,4 +1770,8 @@ export class Wcdb4Client {
const normalized = input > 1e12 ? Math.floor(input / 1000) : Math.floor(input) const normalized = input > 1e12 ? Math.floor(input / 1000) : Math.floor(input)
return Math.min(Math.max(normalized, 0), 2147483647) return Math.min(Math.max(normalized, 0), 2147483647)
} }
private quoteSqlIdentifier(identifier: string): string {
return `"${String(identifier || '').replace(/"/g, '""')}"`
}
} }
+25 -4
View File
@@ -34,12 +34,33 @@ export class WechatDb {
private wcdb4Client: Wcdb4Client private wcdb4Client: Wcdb4Client
private chatMd5ToUsername = new Map<string, string>() private chatMd5ToUsername = new Map<string, string>()
constructor(rawKey: string, accountRoot?: string) { static async create(rawKey: string, accountRoot?: string): Promise<WechatDb> {
// WCDB native init must run on the Electron main process; worker threads
// get -1006 from wcdb_init. Initialize the client synchronously here
// (and keep create() async for callers that already await it).
return new Promise((resolve, reject) => {
try {
const client = new Wcdb4Client(rawKey, accountRoot)
client.open()
resolve(new WechatDb(rawKey, accountRoot, client))
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)))
}
})
}
constructor(
rawKey: string,
accountRoot?: string,
clientOverride?: Wcdb4Client,
initialChatTables?: { name: string; db_number: string }[]
) {
console.log(`Initializing WechatDb with key length: ${rawKey.trim().length}`) console.log(`Initializing WechatDb with key length: ${rawKey.trim().length}`)
const client = new Wcdb4Client(rawKey, accountRoot) const client =
client.open() clientOverride || new Wcdb4Client(rawKey, accountRoot)
if (!clientOverride) client.open()
this.wcdb4Client = client this.wcdb4Client = client
for (const table of client.getChatTables()) { for (const table of initialChatTables || client.getChatTables()) {
if (table.name.startsWith('Chat_')) { if (table.name.startsWith('Chat_')) {
this.chatMd5ToUsername.set(table.name.substring(5), table.db_number) this.chatMd5ToUsername.set(table.name.substring(5), table.db_number)
} }
@@ -40,6 +40,10 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
onDbKeyChange, onDbKeyChange,
onDbRootChanged onDbRootChanged
}) => { }) => {
const isWindows = window.electron.process.platform === 'win32'
const dbRootPlaceholder = isWindows
? 'C:\\Users\\你\\Documents\\WeChat Files'
: '~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files'
const [settings, setSettings] = useState<AppSettings | null>(null) const [settings, setSettings] = useState<AppSettings | null>(null)
const [settingsPath, setSettingsPath] = useState('') const [settingsPath, setSettingsPath] = useState('')
const [apiState, setApiState] = useState<ApiState | null>(null) const [apiState, setApiState] = useState<ApiState | null>(null)
@@ -208,7 +212,7 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
/> />
</div> </div>
<div className="settings-hint"> <div className="settings-hint">
macOS Keychain(safeStorage ), safeStorage
</div> </div>
</section> </section>
@@ -222,7 +226,7 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
value={settings?.dbRoot ?? ''} value={settings?.dbRoot ?? ''}
onChange={(e) => setSettings(settings ? { ...settings, dbRoot: e.target.value } : null)} onChange={(e) => setSettings(settings ? { ...settings, dbRoot: e.target.value } : null)}
onBlur={(e) => handleSave({ dbRoot: e.target.value })} onBlur={(e) => handleSave({ dbRoot: e.target.value })}
placeholder="~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files" placeholder={dbRootPlaceholder}
spellCheck={false} spellCheck={false}
/> />
</div> </div>
@@ -237,7 +241,7 @@ export const SettingsPanel: React.FC<SettingsPanelProps> = ({
{reopenStatus && <span className="settings-status">{reopenStatus}</span>} {reopenStatus && <span className="settings-status">{reopenStatus}</span>}
</div> </div>
<div className="settings-hint"> <div className="settings-hint">
xwechat_files , db_storage/ Windows Documents\WeChat FilesmacOS xwechat_files db_storage/session.db
</div> </div>
</section> </section>