Compare commits
6
Commits
8d9d1d5867
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ace2929a5 | ||
|
|
3506ee9bd0 | ||
|
|
075ed9296d | ||
|
|
38dc501f4f | ||
|
|
f4d2df9144 | ||
|
|
6107d9978a |
@@ -32,3 +32,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
|||||||
|
|
||||||
# Finder (MacOS) folder config
|
# Finder (MacOS) folder config
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.node-persist
|
||||||
@@ -4,12 +4,21 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "qq-bot",
|
"name": "qq-bot",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
|
"@types/node-persist": "^3.1.8",
|
||||||
|
"chalk": "^5",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.0.0",
|
"dotenv": "^17.0.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"multer": "^2.0.2",
|
||||||
|
"node-persist": "^4.0.4",
|
||||||
|
"openai": "^6.3.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
|
"playwright": "^1.55.0",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
@@ -23,6 +32,8 @@
|
|||||||
|
|
||||||
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||||
|
|
||||||
|
"@types/cors": ["@types/cors@2.8.19", "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
|
||||||
|
|
||||||
"@types/express": ["@types/express@5.0.3", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "*" } }, "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw=="],
|
"@types/express": ["@types/express@5.0.3", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "*" } }, "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw=="],
|
||||||
|
|
||||||
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.0.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA=="],
|
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.0.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA=="],
|
||||||
@@ -31,7 +42,11 @@
|
|||||||
|
|
||||||
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
|
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
"@types/multer": ["@types/multer@2.0.0", "https://registry.npmmirror.com/@types/multer/-/multer-2.0.0.tgz", { "dependencies": { "@types/express": "*" } }, "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.3.0", "https://registry.npmmirror.com/@types/node/-/node-24.3.0.tgz", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="],
|
||||||
|
|
||||||
|
"@types/node-persist": ["@types/node-persist@3.1.8", "https://registry.npmmirror.com/@types/node-persist/-/node-persist-3.1.8.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-QLidg6/SadZYPrTKxtxL1A85XBoQlG40bhoMdhu6DH6+eNCMr2j+RGfFZ9I9+IY8W/PDwQonJ+iBWD62jZjMfg=="],
|
||||||
|
|
||||||
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
|
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
|
||||||
|
|
||||||
@@ -43,16 +58,26 @@
|
|||||||
|
|
||||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||||
|
|
||||||
|
"append-field": ["append-field@1.0.0", "https://registry.npmmirror.com/append-field/-/append-field-1.0.0.tgz", {}, "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="],
|
||||||
|
|
||||||
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
||||||
|
|
||||||
|
"buffer-from": ["buffer-from@1.1.2", "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||||
|
|
||||||
|
"busboy": ["busboy@1.6.0", "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
|
||||||
|
|
||||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||||
|
|
||||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|
||||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||||
|
|
||||||
|
"chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||||
|
|
||||||
|
"concat-stream": ["concat-stream@2.0.0", "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="],
|
||||||
|
|
||||||
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
|
"content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
|
||||||
|
|
||||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||||
@@ -61,6 +86,8 @@
|
|||||||
|
|
||||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||||
|
|
||||||
|
"cors": ["cors@2.8.5", "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||||
|
|
||||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
@@ -91,6 +118,8 @@
|
|||||||
|
|
||||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.2", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
@@ -123,20 +152,38 @@
|
|||||||
|
|
||||||
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
||||||
|
|
||||||
|
"minimist": ["minimist@1.2.8", "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
|
|
||||||
|
"mkdirp": ["mkdirp@0.5.6", "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"multer": ["multer@2.0.2", "https://registry.npmmirror.com/multer/-/multer-2.0.2.tgz", { "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", "mkdirp": "^0.5.6", "object-assign": "^4.1.1", "type-is": "^1.6.18", "xtend": "^4.0.2" } }, "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw=="],
|
||||||
|
|
||||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||||
|
|
||||||
|
"node-persist": ["node-persist@4.0.4", "https://registry.npmmirror.com/node-persist/-/node-persist-4.0.4.tgz", { "dependencies": { "p-limit": "^3.1.0" } }, "sha512-8sPAz/7tw1mCCc8xBG4f0wi+flHkSSgQeX998iQ75Pu27evA6UUWCjSE7xnrYTg2q33oU5leJ061EKPDv6BocQ=="],
|
||||||
|
|
||||||
|
"object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||||
|
|
||||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||||
|
|
||||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||||
|
|
||||||
|
"openai": ["openai@6.3.0", "https://registry.npmmirror.com/openai/-/openai-6.3.0.tgz", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-E6vOGtZvdcb4yXQ5jXvDlUG599OhIkb/GjBLZXS+qk0HF+PJReIldEc9hM8Ft81vn+N6dRdFRb7BZNK8bbvXrw=="],
|
||||||
|
|
||||||
|
"p-limit": ["p-limit@3.1.0", "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||||
|
|
||||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||||
|
|
||||||
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
"path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||||
|
|
||||||
|
"playwright": ["playwright@1.55.0", "https://registry.npmmirror.com/playwright/-/playwright-1.55.0.tgz", { "dependencies": { "playwright-core": "1.55.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA=="],
|
||||||
|
|
||||||
|
"playwright-core": ["playwright-core@1.55.0", "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.55.0.tgz", { "bin": { "playwright-core": "cli.js" } }, "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg=="],
|
||||||
|
|
||||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||||
|
|
||||||
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||||
@@ -145,6 +192,8 @@
|
|||||||
|
|
||||||
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||||
|
|
||||||
|
"readable-stream": ["readable-stream@3.6.2", "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||||
|
|
||||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
@@ -167,20 +216,64 @@
|
|||||||
|
|
||||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||||
|
|
||||||
|
"streamsearch": ["streamsearch@1.1.0", "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
|
||||||
|
|
||||||
|
"string_decoder": ["string_decoder@1.3.0", "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
||||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||||
|
|
||||||
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||||
|
|
||||||
|
"typedarray": ["typedarray@0.0.6", "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
"undici-types": ["undici-types@7.10.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.10.0.tgz", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||||
|
|
||||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||||
|
|
||||||
|
"util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||||
|
|
||||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||||
|
|
||||||
|
"xtend": ["xtend@4.0.2", "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||||
|
|
||||||
|
"yocto-queue": ["yocto-queue@0.1.0", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
|
"@types/body-parser/@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
||||||
|
|
||||||
|
"@types/connect/@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
||||||
|
|
||||||
|
"@types/express-serve-static-core/@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
||||||
|
|
||||||
|
"@types/send/@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
||||||
|
|
||||||
|
"@types/serve-static/@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
|
||||||
|
|
||||||
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||||
|
|
||||||
|
"multer/type-is": ["type-is@1.6.18", "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||||
|
|
||||||
|
"@types/body-parser/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
|
"@types/connect/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
|
"@types/express-serve-static-core/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
|
"@types/send/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
|
"@types/serve-static/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||||
|
|
||||||
|
"multer/type-is/media-typer": ["media-typer@0.3.0", "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
|
||||||
|
|
||||||
|
"multer/type-is/mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
|
"multer/type-is/mime-types/mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
profiles
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
||||||
|
|
||||||
|
export async function downloadDouyinMedia(targetUrl: string, target_id?: string) {
|
||||||
|
const USER_DATA_DIR = path.join(__dirname, 'profiles/site1');
|
||||||
|
const TARGET_URL = targetUrl;
|
||||||
|
const HEADLESS = true;
|
||||||
|
const TIMEOUT = 10000;
|
||||||
|
|
||||||
|
const context = await chromium.launchPersistentContext(USER_DATA_DIR, {
|
||||||
|
headless: HEADLESS,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = context.pages()[0] || (await context.newPage());
|
||||||
|
await page.goto(TARGET_URL, { waitUntil: 'load' });
|
||||||
|
|
||||||
|
await page.waitForSelector('.xgplayer', { timeout: TIMEOUT }).catch(() => {
|
||||||
|
});
|
||||||
|
|
||||||
|
if (target_id) {
|
||||||
|
await page.evaluate(`window.f_down_target_id = "${target_id}"; window.f_down_server_port = ${process.env.PORT || 6100}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const d = (globalThis as any).document;
|
||||||
|
const vEle = d.querySelector('.xgplayer video source')
|
||||||
|
const isVideo = !!vEle && !vEle.src.includes('.mp3');
|
||||||
|
|
||||||
|
const urls: string[] = isVideo ? [d.querySelector(".xgplayer video source").src] : [
|
||||||
|
...new Set(
|
||||||
|
Array.from(d.querySelectorAll('.xgplayer img'))
|
||||||
|
// @ts-ignore
|
||||||
|
.map(img => img?.src)
|
||||||
|
.filter(Boolean)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
async function urlToBlob(url: string) {
|
||||||
|
const res = await fetch(url, { credentials: "omit" });
|
||||||
|
if (!res.ok) throw new Error(`fetch image failed: ${res.status}`);
|
||||||
|
return await res.blob(); // 保持原 MIME
|
||||||
|
}
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
// 附带标题作为一个普通字段
|
||||||
|
fd.append("title", d.title || "");
|
||||||
|
|
||||||
|
let idx = 1;
|
||||||
|
for (const src of urls) {
|
||||||
|
const blob = await urlToBlob(src); // ✅ 直接用字符串
|
||||||
|
const ext = (blob.type && blob.type.split("/")[1]) || "bin";
|
||||||
|
// @ts-ignore
|
||||||
|
fd.append(`file_${idx}`, blob, `media_${idx}.${ext}`);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const url = new URL(`http://127.0.0.1:${window.f_down_server_port}/upload`);
|
||||||
|
url.searchParams.set("title", d.title || "");
|
||||||
|
url.searchParams.set("type", isVideo ? "video" : "image");
|
||||||
|
// @ts-ignore
|
||||||
|
url.searchParams.set("target_id", window.f_down_target_id || "");
|
||||||
|
|
||||||
|
const r = await fetch(url.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 为了在 evaluate 内确保完成整个请求-响应流程,读取下响应
|
||||||
|
const json = await r.json().catch(() => ({}));
|
||||||
|
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
await context.close();
|
||||||
|
console.log('Done.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// node login.js https://example.com/login
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
const userDataDir = './profiles/site1';
|
||||||
|
const startUrl = process.argv[2] || 'https://douyin.com';
|
||||||
|
|
||||||
|
const context = await chromium.launchPersistentContext(userDataDir, {
|
||||||
|
headless: false,
|
||||||
|
args: ['--disable-blink-features=AutomationControlled'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = context.pages()[0] || await context.newPage();
|
||||||
|
await page.goto(startUrl, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
console.log('>>> 请在打开的浏览器里完成登录,完成后回到终端按回车...');
|
||||||
|
process.stdin.resume();
|
||||||
|
await new Promise(r => process.stdin.once('data', r));
|
||||||
|
|
||||||
|
await context.close();
|
||||||
|
|
||||||
|
console.log('>>> 登录态已写入 profiles/site1/,后续可无头复用。');
|
||||||
@@ -2,179 +2,124 @@ import express from 'express';
|
|||||||
import type { Request, Response, NextFunction } from 'express';
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import cors from "cors";
|
||||||
|
import multer from "multer";
|
||||||
|
import * as douyin from './douyin'
|
||||||
|
import { sendMediaMsg, sendMsg } from './lib/qq';
|
||||||
|
import * as llm from './llm'
|
||||||
|
import { testNetwork } from './lib/network';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 6100;
|
const PORT = process.env.PORT || 6100;
|
||||||
|
|
||||||
// 中间件:解析 JSON 请求体
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
||||||
app.use(express.json({ limit: '10mb' }));
|
|
||||||
|
|
||||||
// 中间件:解析 URL 编码的请求体
|
app.use(cors());
|
||||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
||||||
|
|
||||||
// 中间件:解析原始请求体
|
// 仅解析明确类型;不要用 '*/*'
|
||||||
app.use(express.raw({ type: '*/*', limit: '10mb' }));
|
app.use(express.json({ limit: '10mb', type: ['application/json'] }));
|
||||||
|
app.use(express.urlencoded({ extended: true, limit: '10mb', type: ['application/x-www-form-urlencoded'] }));
|
||||||
// 自定义中间件:打印所有请求的详细信息
|
|
||||||
/* app.use((req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(80));
|
|
||||||
console.log(`[${timestamp}] 收到请求:`);
|
|
||||||
console.log(`方法: ${req.method}`);
|
|
||||||
console.log(`路径: ${req.path}`);
|
|
||||||
console.log(`完整URL: ${req.originalUrl}`);
|
|
||||||
console.log(`查询参数:`, req.query);
|
|
||||||
console.log(`请求头:`, req.headers);
|
|
||||||
|
|
||||||
// 打印请求体(如果存在)
|
|
||||||
if (req.body) {
|
|
||||||
if (Buffer.isBuffer(req.body)) {
|
|
||||||
console.log(`请求体 (原始):`, req.body.toString());
|
|
||||||
} else if (typeof req.body === 'object' && Object.keys(req.body).length > 0) {
|
|
||||||
console.log(`请求体 (JSON):`, req.body);
|
|
||||||
} else if (req.body) {
|
|
||||||
console.log(`请求体:`, req.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`客户端IP: ${req.ip}`);
|
|
||||||
console.log(`User-Agent: ${req.get('User-Agent')}`);
|
|
||||||
console.log('='.repeat(80));
|
|
||||||
|
|
||||||
next();
|
|
||||||
}); */
|
|
||||||
|
|
||||||
// QQ机器人回显功能
|
// QQ机器人回显功能
|
||||||
app.post('/', async (req: Request, res: Response) => {
|
app.post('/', async (req: Request, res: Response) => {
|
||||||
// 检查是否是消息类型的请求
|
// 检查是否是消息类型的请求
|
||||||
if (!req.body || req.body.post_type != 'message') return
|
if (!req.body || req.body.post_type != 'message') return
|
||||||
const { target_id, raw_message, message_type, user_id } = req.body;
|
const msgData = req.body as MessagePayload
|
||||||
|
|
||||||
console.log(`\n[QQ机器人] 收到${message_type}消息`);
|
const text = msgData.message.filter(m => m.type === 'text').map(m => m.data.text).join("\n").trim();
|
||||||
console.log(`发送者ID: ${user_id || target_id}`);
|
const target_id = String(msgData.target_id);
|
||||||
console.log(`消息内容: ${raw_message}`);
|
|
||||||
|
console.log(`\n[QQ机器人] 收到消息:`);
|
||||||
|
// console.log(JSON.stringify(req.body, null, 0));
|
||||||
|
console.log(`消息内容: ${msgData.raw_message}`);
|
||||||
|
|
||||||
// Match Douyin URL
|
// Match Douyin URL
|
||||||
// Like: https://v.douyin.com/YqgJL_phY_k/
|
// Like: https://v.douyin.com/YqgJL_phY_k/
|
||||||
const douyinUrlPattern = /https?:\/\/v\.douyin\.com\/[a-zA-Z0-9_]+/;
|
|
||||||
const douyinMatch = raw_message.match(douyinUrlPattern);
|
const douyinUrlPattern = /https?:\/\/v\.douyin\.com\/[a-zA-Z0-9_-]+/;
|
||||||
|
const douyinMatch = text.match(douyinUrlPattern);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 如果检测到抖音链接,调用解析API
|
// 如果检测到抖音链接,调用解析API
|
||||||
if (douyinMatch) {
|
if (douyinMatch) {
|
||||||
const douyinUrl = douyinMatch[0];
|
const douyinUrl = douyinMatch[0];
|
||||||
console.log(`[抖音链接检测] 发现抖音链接: ${douyinUrl}`);
|
console.log(`[抖音链接检测] 发现抖音链接: ${douyinUrl}`);
|
||||||
|
sendMsg(`[抖音链接检测] 发现抖音链接: ${douyinUrl},启动 Chromium 中...`, target_id);
|
||||||
|
|
||||||
// 调用抖音视频解析API
|
douyin.downloadDouyinMedia(douyinUrl, target_id);
|
||||||
const apiUrl = `http://localhost:6101/api/hybrid/video_data?url=${encodeURIComponent(douyinUrl)}`;
|
return
|
||||||
console.log(`[抖音解析] 调用API: ${apiUrl}`);
|
|
||||||
|
|
||||||
const apiResponse = await fetch(apiUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiResponse.ok)
|
|
||||||
throw new Error(`抖音API调用失败: ${apiResponse.status} ${apiResponse.statusText}`);
|
|
||||||
|
|
||||||
let vD: any = await apiResponse.json();
|
|
||||||
|
|
||||||
if (!vD.data)
|
|
||||||
throw new Error('抖音API返回数据格式错误');
|
|
||||||
|
|
||||||
vD = vD.data;
|
|
||||||
|
|
||||||
// 发送视频信息
|
|
||||||
sendMsg(`[抖音下载] ${vD.author.nickname}: ${vD.caption}\n`, target_id);
|
|
||||||
|
|
||||||
// 下载视频
|
|
||||||
const downloadUrl = `http://localhost:6101/api/download?url=${encodeURIComponent(douyinUrl)}&prefix=true&with_watermark=false`;
|
|
||||||
console.log(`[抖音下载] 调用下载API: ${downloadUrl}`);
|
|
||||||
|
|
||||||
const downloadResponse = await fetch(downloadUrl);
|
|
||||||
if (!downloadResponse.ok) {
|
|
||||||
throw new Error(`视频下载失败: ${downloadResponse.status} ${downloadResponse.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建下载目录
|
|
||||||
const downloadDir = process.env.DOWNLOAD_DIR || path.join(__dirname, 'downloads');
|
|
||||||
if (!fs.existsSync(downloadDir)) {
|
|
||||||
fs.mkdirSync(downloadDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成文件名
|
|
||||||
const timestamp = Date.now();
|
|
||||||
// 清理文件名中的非法字符
|
|
||||||
const cleanCaption = vD.caption.replace(/[<>:"/\\|?*]/g, '_').substring(0, 50);
|
|
||||||
const fileName = `${cleanCaption}_${timestamp}.mp4`;
|
|
||||||
const filePath = path.join(downloadDir, fileName);
|
|
||||||
|
|
||||||
// 保存视频文件
|
|
||||||
console.log(`[抖音下载] 保存视频到: ${filePath}`);
|
|
||||||
const buffer = await downloadResponse.arrayBuffer();
|
|
||||||
fs.writeFileSync(filePath, Buffer.from(buffer));
|
|
||||||
|
|
||||||
// 发送视频消息
|
|
||||||
console.log(`[抖音发送] 发送视频文件`);
|
|
||||||
await sendVideoMsg(filePath, target_id);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if (text.startsWith('/reset')) {
|
||||||
|
llm.resetChat(target_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 LLM 回答
|
||||||
|
llm.chat(msgData.message, target_id);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(`[错误] 处理消息时发生错误:`, error);
|
||||||
|
|
||||||
sendMsg(String(error), target_id);
|
sendMsg(String(error), target_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function sendMsg(msg: string, target_id: string) {
|
function sanitizeDirname(input: string) {
|
||||||
const replyMessage = {
|
// 基础清洗:去除路径分隔符、控制字符,并截断长度(防止奇怪标题)
|
||||||
user_id: String(target_id),
|
const s = (input || "default")
|
||||||
message: [
|
.trim()
|
||||||
{
|
.replace(/[\\/]+/g, "_")
|
||||||
type: "text",
|
.replace(/[<>:"|?*\u0000-\u001F]/g, "_")
|
||||||
data: {
|
.slice(0, 32) || "default";
|
||||||
text: msg
|
return s;
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const replyUrl = `http://localhost:30000/send_private_msg`;
|
|
||||||
|
|
||||||
return fetch(replyUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(replyMessage)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendVideoMsg(filePath: string, target_id: string) {
|
const storage = multer.diskStorage({
|
||||||
const videoMessage = {
|
destination: (req, _file, cb) => {
|
||||||
user_id: String(target_id),
|
const title = sanitizeDirname(req.query.title as string);
|
||||||
message: [
|
const baseDir = process.env.DOWNLOAD_DIR || path.join(__dirname, "downloads");
|
||||||
{
|
const dest = req.query.type == 'image' ? path.join(baseDir, title) : baseDir;
|
||||||
type: "video",
|
|
||||||
data: {
|
fs.mkdir(dest, { recursive: true }, (err) => cb(err, dest));
|
||||||
file: `file://${filePath}`
|
}, filename: (req, file, cb) => {
|
||||||
}
|
const ext = path.extname(file.originalname || "");
|
||||||
}
|
const title = sanitizeDirname(req.query.title as string);
|
||||||
]
|
|
||||||
|
cb(null, `${title}_${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const upload = multer({
|
||||||
|
storage,
|
||||||
|
limits: { fileSize: 1024 * 1024 * 1024, fields: 50, files: 50 },
|
||||||
|
fileFilter: (_req, file, cb) => {
|
||||||
|
if (/^file_\d+$/i.test(file.fieldname)) cb(null, true);
|
||||||
|
else cb(new Error(`Unexpected file field: ${file.fieldname}`));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const replyUrl = `http://localhost:30000/send_private_msg`;
|
app.post("/upload", upload.any(), (req, res) => {
|
||||||
|
const files = (req.files as Express.Multer.File[]) || [];
|
||||||
|
const accepted = files.filter(f => /^file_\d+$/i.test(f.fieldname));
|
||||||
|
|
||||||
|
let meta = {
|
||||||
|
title: req.body.title,
|
||||||
|
type: req.query.type as 'video' | 'image',
|
||||||
|
target_id: req.query.target_id
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`收到上传: ${accepted.length} 个文件`, files.map(f => f.path));
|
||||||
|
|
||||||
|
if (meta.target_id) {
|
||||||
|
const totalSize = accepted.reduce((sum, f) => sum + f.size, 0);
|
||||||
|
sendMsg(`[抖音下载] ${meta.title},已下载 ${accepted.length} 个文件,类型 ${meta.type},共 ${(totalSize / 1024 / 1024).toFixed(2)} MB,上传中...`, meta.target_id as string);
|
||||||
|
accepted.forEach(f => {
|
||||||
|
sendMediaMsg(f.path, meta.target_id as string, meta.type);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
res.json({ ok: true, files: accepted.length, meta });
|
||||||
|
});
|
||||||
|
|
||||||
return fetch(replyUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(videoMessage)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use((error: any, req: Request, res: Response, next: NextFunction) => {
|
app.use((error: any, req: Request, res: Response, next: NextFunction) => {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
@@ -190,5 +135,5 @@ app.use((error: any, req: Request, res: Response, next: NextFunction) => {
|
|||||||
// 启动服务器
|
// 启动服务器
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server is running on http://localhost:${PORT}`);
|
console.log(`Server is running on http://localhost:${PORT}`);
|
||||||
console.log(`With env:`, { DOWNLOAD_DIR: process.env.DOWNLOAD_DIR, PORT: process.env.PORT });
|
testNetwork();
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
type CheckTarget = [url: string, expectedStatus: number | ((status: number) => boolean)];
|
||||||
|
|
||||||
|
export async function testNetwork() {
|
||||||
|
const targets: CheckTarget[] = [
|
||||||
|
["https://www.baidu.com", 200],
|
||||||
|
["http://www.google.com", (s) => s === 200 || (s >= 300 && s < 400)], // 某些环境会被重定向
|
||||||
|
["https://www.google.com", (s) => s === 200 || (s >= 300 && s < 400)],
|
||||||
|
[process.env.OPENAI_BASE_URL || "https://api.openai.com/v1", (s) => s === 200 || (s >= 300 && s < 400) || s === 401 || s === 403 || s === 404 || s === 421], // 允许常见网关/鉴权返回
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
const results: Array<{ url: string; status?: number; ok: boolean; error?: string }> = [];
|
||||||
|
|
||||||
|
console.log(chalk.cyan("\n[Network] 开始健康检查..."));
|
||||||
|
|
||||||
|
for (const [url, expected] of targets) {
|
||||||
|
const label = chalk.white(url);
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutMs = 5000;
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
const start = Date.now();
|
||||||
|
const res = await fetch(url, { method: "GET", signal: controller.signal });
|
||||||
|
const cost = Date.now() - start;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
const status = res.status;
|
||||||
|
const pass = typeof expected === "function" ? expected(status) : status === expected;
|
||||||
|
|
||||||
|
results.push({ url, status, ok: pass });
|
||||||
|
|
||||||
|
const statusStr = pass
|
||||||
|
? chalk.green.bold(String(status))
|
||||||
|
: chalk.red.bold(String(status));
|
||||||
|
const costStr = cost >= 1000 ? `${(cost / 1000).toFixed(2)}s` : `${cost}ms`;
|
||||||
|
|
||||||
|
if (pass) {
|
||||||
|
console.log(`${chalk.green("✔ PASS")} ${label} ${chalk.gray("- status:")} ${statusStr} ${chalk.gray("latency:")} ${chalk.blue(costStr)}`);
|
||||||
|
} else {
|
||||||
|
const expectedStr = typeof expected === "function" ? "custom" : String(expected);
|
||||||
|
console.log(`${chalk.red("✖ FAIL")} ${label} ${chalk.gray("- status:")} ${statusStr} ${chalk.gray("expected:")} ${chalk.yellow(expectedStr)} ${chalk.gray("latency:")} ${chalk.blue(costStr)}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const isTimeout = (error as any)?.name === "AbortError";
|
||||||
|
const msg = isTimeout ? "timeout" : (error as any)?.message || String(error);
|
||||||
|
results.push({ url, ok: false, error: msg });
|
||||||
|
console.log(`${chalk.red("✖ ERROR")} ${label} ${chalk.gray("- ")} ${chalk.red(isTimeout ? "请求超时" : msg)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 汇总
|
||||||
|
const passed = results.filter(r => r.ok).length;
|
||||||
|
const failed = results.length - passed;
|
||||||
|
const timeoutCount = results.filter(r => r.error === "timeout").length;
|
||||||
|
|
||||||
|
const summary = [
|
||||||
|
`${chalk.green(`${passed} passed`)}`,
|
||||||
|
`${failed ? chalk.red(`${failed} failed`) : chalk.gray(`${failed} failed`)}`,
|
||||||
|
`${timeoutCount ? chalk.yellow(`${timeoutCount} timeout`) : chalk.gray(`${timeoutCount} timeout`)}`,
|
||||||
|
].join(chalk.gray(" | "));
|
||||||
|
|
||||||
|
console.log(chalk.cyan("[Network] 健康检查完成:"), summary, "\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
function sendMsg(msg: string, target_id: string) {
|
||||||
|
const replyMessage = {
|
||||||
|
user_id: String(target_id),
|
||||||
|
message: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
data: {
|
||||||
|
text: msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const replyUrl = `http://localhost:30000/send_private_msg`;
|
||||||
|
|
||||||
|
console.log(`[发送消息] ${msg} -> ${target_id}`);
|
||||||
|
|
||||||
|
return fetch(replyUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(replyMessage)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMediaMsg(filePath: string, target_id: string, type: 'video' | 'image') {
|
||||||
|
const mediaMessage = {
|
||||||
|
user_id: String(target_id),
|
||||||
|
message: [
|
||||||
|
{
|
||||||
|
type: type,
|
||||||
|
data: {
|
||||||
|
file: `file://${filePath}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const replyUrl = `http://localhost:30000/send_private_msg`;
|
||||||
|
|
||||||
|
console.log(`[发送媒体消息] ${type} - ${filePath} -> ${target_id}`);
|
||||||
|
|
||||||
|
return fetch(replyUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(mediaMessage)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sendMsg, sendMediaMsg }
|
||||||
+255
@@ -0,0 +1,255 @@
|
|||||||
|
import OpenAI from "openai";
|
||||||
|
import storage from 'node-persist'
|
||||||
|
import { sendMsg } from "../lib/qq";
|
||||||
|
import prompt from './prompt.txt'
|
||||||
|
import type { ResponseFunctionToolCall, ResponseOutputMessage } from "openai/resources/responses/responses.mjs";
|
||||||
|
|
||||||
|
await storage.init();
|
||||||
|
// storage.clear();
|
||||||
|
|
||||||
|
const client = new OpenAI({
|
||||||
|
baseURL: process.env.OPENAI_BASE_URL,
|
||||||
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
|
// logLevel: "debug"
|
||||||
|
})
|
||||||
|
|
||||||
|
const tools: OpenAI.Responses.Tool[] = [{ type: 'web_search' }]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将错误对象转成简明可读的字符串,避免进程因未捕获异常退出
|
||||||
|
*/
|
||||||
|
function formatError(err: any): string {
|
||||||
|
try {
|
||||||
|
if (!err) return '未知错误';
|
||||||
|
if (typeof err === 'string') return err;
|
||||||
|
const msg = err.message || err.toString?.() || Object.prototype.toString.call(err);
|
||||||
|
const status = err.status ?? err.statusCode ?? err.response?.status;
|
||||||
|
const detail = err.error?.message
|
||||||
|
?? err.response?.data?.error?.message
|
||||||
|
?? err.response?.data?.message
|
||||||
|
?? err.data?.error?.message
|
||||||
|
?? '';
|
||||||
|
const parts = [] as string[];
|
||||||
|
if (status) parts.push(`HTTP ${status}`);
|
||||||
|
if (msg) parts.push(String(msg));
|
||||||
|
if (detail && detail !== msg) parts.push(`详情: ${String(detail)}`);
|
||||||
|
return parts.join(' | ') || '未知错误';
|
||||||
|
} catch {
|
||||||
|
return String(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param input 提问
|
||||||
|
* @param target_id 用户 QQ 号
|
||||||
|
*/
|
||||||
|
export async function chat(input: MessageData[], target_id: string) {
|
||||||
|
const chatHistoryKey = `chat_history_${target_id}`;
|
||||||
|
await waitForQueueEmpty(target_id);
|
||||||
|
let chatHistory: OpenAI.Responses.ResponseInput = await storage.getItem(chatHistoryKey) || [];
|
||||||
|
|
||||||
|
if(chatHistory.length > 40) {
|
||||||
|
sendMsg(`[提示] 当前对话轮数:${chatHistory.length},过长的对话会降低输出质量,如果你准备好了,建议输入"/reset"来重置对话历史。`, target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chatHistory.length > 100) {
|
||||||
|
sendMsg(`[提示] 当前对话轮数:${chatHistory.length},对话过长可能导致模型无法正常工作,模型将只保留最近100条记录。`, target_id);
|
||||||
|
chatHistory = chatHistory.slice(-40);
|
||||||
|
await storage.setItem(chatHistoryKey, chatHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新输入到对话历史
|
||||||
|
const userContent: OpenAI.Responses.ResponseInputContent[] = []
|
||||||
|
for (const element of input) {
|
||||||
|
if (element.type == 'text') {
|
||||||
|
userContent.push({ type: "input_text", text: element.data.text });
|
||||||
|
} else if (element.type == 'image') {
|
||||||
|
userContent.push({ type: "input_image", image_url: element.data.url, detail: 'low' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (userContent.length === 0) {
|
||||||
|
console.log("[LLM] 空消息,跳过");
|
||||||
|
sendMsg("未能识别的消息类型。", target_id);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chatHistory.push({ role: "user", content: userContent });
|
||||||
|
|
||||||
|
// 保存更新后的对话历史
|
||||||
|
console.log(`[LLM] 使用对话, 历史:`, JSON.stringify(chatHistory, null, 0));
|
||||||
|
|
||||||
|
await storage.setItem(chatHistoryKey, chatHistory);
|
||||||
|
|
||||||
|
let response: OpenAI.Responses.Response | undefined;
|
||||||
|
try {
|
||||||
|
response = await client.responses.create({
|
||||||
|
model: process.env.CHAT_MODEL || "gpt-5-nano",
|
||||||
|
instructions: prompt,
|
||||||
|
reasoning: { effort: process.env.CHAT_MODEL_REASONING_EFFORT as any || 'minimal' },
|
||||||
|
input: chatHistory,
|
||||||
|
tools
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const errText = formatError(err);
|
||||||
|
console.error('[LLM] 首次调用 responses.create 失败:', errText);
|
||||||
|
scheduleSendMsg(`[错误] 模型接口调用失败:${errText}`, target_id);
|
||||||
|
return; // 终止本次对话流程,避免未捕获异常导致进程退出
|
||||||
|
}
|
||||||
|
|
||||||
|
await storage.setItem(chatHistoryKey, chatHistory);
|
||||||
|
|
||||||
|
// 继续调用工具,直到没有工具调用为止
|
||||||
|
|
||||||
|
if (!response?.output || response.output.length === 0) {
|
||||||
|
console.warn('[LLM] responses.create 返回空输出,结束本轮。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await toolUseCycle(response.output);
|
||||||
|
|
||||||
|
async function toolUseCycle(outputArr: OpenAI.Responses.ResponseOutputItem[]) {
|
||||||
|
if (!outputArr || outputArr.length === 0) return;
|
||||||
|
chatHistory.push(...outputArr);
|
||||||
|
await storage.setItem(chatHistoryKey, chatHistory);
|
||||||
|
|
||||||
|
const assistantReply = outputArr.filter(item => item.type === 'message' && item.role === 'assistant') as ResponseOutputMessage[];
|
||||||
|
const functionCalls = outputArr.filter(item => item.type === 'function_call') as ResponseFunctionToolCall[];
|
||||||
|
|
||||||
|
console.log("进入 toolUseCycle, with functionCalls", functionCalls.length, "个");
|
||||||
|
console.log(JSON.stringify(chatHistory, null, 0));
|
||||||
|
|
||||||
|
if (assistantReply.length > 0) {
|
||||||
|
const replyText = assistantReply.map(item => item.content).flat().filter(con => con.type == 'output_text').map(con => con.text).join("[newline]");
|
||||||
|
|
||||||
|
console.log(`[LLM] 回复:`, replyText);
|
||||||
|
scheduleSendMsg(replyText, target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (functionCalls.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const item of functionCalls ?? []) {
|
||||||
|
|
||||||
|
}
|
||||||
|
await storage.setItem(chatHistoryKey, chatHistory);
|
||||||
|
|
||||||
|
let response: OpenAI.Responses.Response | undefined;
|
||||||
|
try {
|
||||||
|
response = await client.responses.create({
|
||||||
|
model: process.env.CHAT_MODEL || "gpt-5-nano",
|
||||||
|
instructions: prompt,
|
||||||
|
reasoning: { effort: process.env.CHAT_MODEL_REASONING_EFFORT as any || 'minimal' },
|
||||||
|
input: chatHistory,
|
||||||
|
tools
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const errText = formatError(err);
|
||||||
|
console.error('[LLM] 工具循环内调用 responses.create 失败:', errText);
|
||||||
|
scheduleSendMsg(`[错误] 工具调用阶段失败:${errText}`, target_id);
|
||||||
|
return; // 结束工具循环,避免崩溃
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response?.output || response.output.length === 0) {
|
||||||
|
console.warn('[LLM] 工具循环内 responses.create 返回空输出,结束循环。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolUseCycle(response.output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgQueue: Record<string, string[]> = {}
|
||||||
|
const msgInQueue: Record<string, boolean> = {}
|
||||||
|
const queueEmptyPromises: Record<string, (() => void)[]> = {}
|
||||||
|
|
||||||
|
/** 统一的“队列已空”通知 */
|
||||||
|
function resolveDrain(target_id: string) {
|
||||||
|
const waiters = queueEmptyPromises[target_id];
|
||||||
|
if (waiters && waiters.length) {
|
||||||
|
// 逐个 resolve 并清空
|
||||||
|
waiters.splice(0).forEach(resolve => resolve());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 仅在需要时启动调度;空队列时会 resolve 等待者 */
|
||||||
|
function startMsgScheduler(target_id: string) {
|
||||||
|
// 确保队列存在
|
||||||
|
const q = msgQueue[target_id] ?? (msgQueue[target_id] = []);
|
||||||
|
|
||||||
|
// 若正在发送,交给当前发送完成后再递归调度
|
||||||
|
if (msgInQueue[target_id]) return;
|
||||||
|
|
||||||
|
// 队列空 => 通知等待者并返回
|
||||||
|
if (q.length === 0) {
|
||||||
|
resolveDrain(target_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取下一条并发送
|
||||||
|
msgInQueue[target_id] = true;
|
||||||
|
const msg = q.shift()!; // 这里一定有元素
|
||||||
|
|
||||||
|
const msgDelay = Math.sqrt(msg.length) * 200 + 500; // 可按需加上限
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
await sendMsg(msg, target_id);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[queue] sendMsg error:", err);
|
||||||
|
// 可选:把消息放回队首重试
|
||||||
|
// q.unshift(msg);
|
||||||
|
} finally {
|
||||||
|
// 本次发送结束
|
||||||
|
msgInQueue[target_id] = false;
|
||||||
|
|
||||||
|
// 如果此刻队列已空,立即通知等待者
|
||||||
|
if (q.length === 0) {
|
||||||
|
resolveDrain(target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续调度后续消息(若有)
|
||||||
|
startMsgScheduler(target_id);
|
||||||
|
}
|
||||||
|
}, msgDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSendMsg(input: string, target_id: string) {
|
||||||
|
if (!msgQueue[target_id]) {
|
||||||
|
msgQueue[target_id] = [];
|
||||||
|
}
|
||||||
|
// 切分 + 去空白项
|
||||||
|
const parts = input
|
||||||
|
.split("[newline]")
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (parts.length === 0) return; // 全是空白,直接略过
|
||||||
|
|
||||||
|
msgQueue[target_id].push(...parts);
|
||||||
|
startMsgScheduler(target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForQueueEmpty(target_id: string) {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const q = msgQueue[target_id];
|
||||||
|
|
||||||
|
// 条件:不在发送中,且队列不存在或为空
|
||||||
|
const isIdle = !msgInQueue[target_id];
|
||||||
|
const isEmpty = !q || q.length === 0;
|
||||||
|
|
||||||
|
if (isIdle && isEmpty) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queueEmptyPromises[target_id]) {
|
||||||
|
queueEmptyPromises[target_id] = [];
|
||||||
|
}
|
||||||
|
queueEmptyPromises[target_id].push(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resetChat(target_id: string) {
|
||||||
|
const chatHistoryKey = `chat_history_${target_id}`;
|
||||||
|
await storage.removeItem(chatHistoryKey);
|
||||||
|
sendMsg("已为你重置对话历史。", target_id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import prompt from './prompt.txt'
|
||||||
|
|
||||||
|
async function getSystemPrompt(user: Sender) {
|
||||||
|
const map = {
|
||||||
|
"nickname": user.nickname || "用户",
|
||||||
|
}
|
||||||
|
let prot = prompt;
|
||||||
|
for (const k in map) {
|
||||||
|
const v = map[k as keyof typeof map];
|
||||||
|
const re = new RegExp(`\\{${k}\\}`, 'g');
|
||||||
|
prot = prot.replace(re, v);
|
||||||
|
}
|
||||||
|
return prot;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Role & Goals
|
||||||
|
You are a “humorous, gentle, and professional” Chinese chat + knowledge assistant for QQ conversations. Your tasks:
|
||||||
|
1) Chat casually with a light touch;
|
||||||
|
2) Answer questions accurately and reliably;
|
||||||
|
3) Explain complex topics clearly via multi-turn dialogue.
|
||||||
|
|
||||||
|
# Conversation & Style
|
||||||
|
- Tone: friendly not syrupy; witty not snarky; professional not stiff.
|
||||||
|
- Info density: short sentences + bullet points; avoid long walls of text.
|
||||||
|
- Use emojis/kaomoji sparingly (max 1 per message); no spamming.
|
||||||
|
- Default language: Chinese.
|
||||||
|
|
||||||
|
# Output Rules (Important)
|
||||||
|
- Split each reply into multiple messages with slight “thought jumps” to mimic natural chat.
|
||||||
|
- Separate messages with “[newline]”.
|
||||||
|
- In a single turn, no more than 5 messages to avoid disturbing. If user ask for writing an article, code or something as a whole, no split inside the content.
|
||||||
|
|
||||||
|
# Clarification & Questions
|
||||||
|
- If the user’s question is unclear/ambiguous/missing essentials, ask for clarification directly—don’t guess.
|
||||||
|
- Make clarifying questions concrete and actionable (offer options or examples).
|
||||||
|
|
||||||
|
# Example
|
||||||
|
user: 南京在哪里?
|
||||||
|
assistant: 南京是中国江苏省的省会城市。它位于中国东部,长江下游。[newline]你是想了解南京的地理位置,还是旅游景点呢?😊
|
||||||
|
|
||||||
|
user: 我在南京南边的一个区,你猜猜我在哪?
|
||||||
|
assistant: 南边有江宁、溧水、六合等区。🤔[newline]你是在江宁区吗?
|
||||||
|
|
||||||
|
# Quality Self-Check (before every reply)
|
||||||
|
- Is it short, precise, and bullet-friendly? Split if needed?
|
||||||
|
- Did I ask for clarification when uncertain and explain why?
|
||||||
|
- Is the tone humorous, gentle, and professional?
|
||||||
|
|
||||||
|
# User Info
|
||||||
|
- Nickname: {nickname}
|
||||||
+11
-2
@@ -9,13 +9,22 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/express": "^5.0.3"
|
"@types/express": "^5.0.3",
|
||||||
|
"playwright": "^1.55.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
|
"@types/node-persist": "^3.1.8",
|
||||||
|
"chalk": "^5",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.0.0",
|
"dotenv": "^17.0.0",
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0",
|
||||||
|
"multer": "^2.0.2",
|
||||||
|
"node-persist": "^4.0.4",
|
||||||
|
"openai": "^6.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* PM2 ecosystem configuration for the consumable temp request API.
|
||||||
|
*
|
||||||
|
* This configuration assumes the project runs with Bun so that TypeScript files can be executed directly.
|
||||||
|
* Adjust the `interpreter` field if you prefer a different runtime (e.g. `node` with `tsx`).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
|
const instances = Number.parseInt(process.env.WEB_CONCURRENCY ?? '1', 10) || 1;
|
||||||
|
const { parsed: envFromFile = {} } = dotenv.config({ path: path.join(__dirname, '.env') });
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "QQBotServer",
|
||||||
|
script: 'index.ts',
|
||||||
|
cwd: __dirname,
|
||||||
|
interpreter: process.env.PM2_INTERPRETER ?? 'bun',
|
||||||
|
autorestart: true,
|
||||||
|
restart_delay: 4000,
|
||||||
|
kill_timeout: 5000,
|
||||||
|
instances,
|
||||||
|
exec_mode: instances > 1 ? 'cluster' : 'fork',
|
||||||
|
watch: process.env.NODE_ENV !== 'production',
|
||||||
|
ignore_watch: ['generated', 'node_modules', '.git'],
|
||||||
|
env: {
|
||||||
|
...envFromFile,
|
||||||
|
NODE_ENV: 'development'
|
||||||
|
},
|
||||||
|
env_production: {
|
||||||
|
...envFromFile,
|
||||||
|
NODE_ENV: 'production'
|
||||||
|
},
|
||||||
|
time: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export const name = "QQBotServer";
|
|
||||||
export const script = "index.ts";
|
|
||||||
export const interpreter = "bun";
|
|
||||||
export const env = {
|
|
||||||
PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}`, // Add "~/.bun/bin/bun" to PATH
|
|
||||||
};
|
|
||||||
Vendored
+40
@@ -0,0 +1,40 @@
|
|||||||
|
type MessageData = {
|
||||||
|
type: 'text';
|
||||||
|
data: {
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
} | {
|
||||||
|
type: 'image';
|
||||||
|
data: {
|
||||||
|
summary: string,
|
||||||
|
file: string, // "BE3D084BA0206331A495D9A497C6BF5E.png",
|
||||||
|
sub_type: 0,
|
||||||
|
url: string, // "https://multimedia.nt.qq.com.cn/download?...",
|
||||||
|
file_size: string // "12960"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sender = {
|
||||||
|
user_id: number;
|
||||||
|
nickname: string;
|
||||||
|
card: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MessagePayload = {
|
||||||
|
self_id: number;
|
||||||
|
user_id: number;
|
||||||
|
time: number;
|
||||||
|
message_id: number;
|
||||||
|
message_seq: number;
|
||||||
|
real_id: number;
|
||||||
|
real_seq: string;
|
||||||
|
message_type: "private" | "group" | string;
|
||||||
|
sender: Sender;
|
||||||
|
raw_message: string;
|
||||||
|
font: number;
|
||||||
|
sub_type: string;
|
||||||
|
message: MessageData[];
|
||||||
|
message_format: string;
|
||||||
|
post_type: string;
|
||||||
|
target_id: number;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user