diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..692aa94 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "启动程序", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/index.js", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 76de84d..7acd7e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "decoders": "^1.25.3", "handlebars": "^4.7.7", "koa": "^2.13.4", + "koa-body": "^6.0.1", "koa-router": "^10.1.1", "lua-runner": "^2.0.3", "micromatch": "^4.0.4", @@ -96,7 +97,6 @@ "version": "1.3.5", "resolved": "https://registry.npmmirror.com/@types/accepts/-/accepts-1.3.5.tgz", "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -120,7 +120,6 @@ "version": "1.19.2", "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -137,11 +136,19 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, + "node_modules/@types/co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/@types/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-3e0q2jyDAnx/DSZi0z2H0yoZ2wt5yRDZ+P7ymcMObvq0ufWRT4tsajyO+Q1VwVWiv9PRR4W3YEjEzBjeZlhF+w==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -149,14 +156,12 @@ "node_modules/@types/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmmirror.com/@types/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==", - "dev": true + "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==" }, "node_modules/@types/cookies": { "version": "0.7.7", "resolved": "https://registry.npmmirror.com/@types/cookies/-/cookies-0.7.7.tgz", "integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/express": "*", @@ -168,7 +173,6 @@ "version": "4.17.13", "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.13.tgz", "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -180,36 +184,39 @@ "version": "4.17.28", "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, + "node_modules/@types/formidable": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@types/formidable/-/formidable-2.0.5.tgz", + "integrity": "sha512-uvMcdn/KK3maPOaVUAc3HEYbCEhjaGFwww4EsX6IJfWIJ1tzHtDHczuImH3GKdusPnAAmzB07St90uabZeCKPA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-assert": { "version": "1.5.3", "resolved": "https://registry.npmmirror.com/@types/http-assert/-/http-assert-1.5.3.tgz", - "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==", - "dev": true + "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==" }, "node_modules/@types/http-errors": { "version": "1.8.2", "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-1.8.2.tgz", - "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==", - "dev": true + "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==" }, "node_modules/@types/keygrip": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/@types/keygrip/-/keygrip-1.0.2.tgz", - "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", - "dev": true + "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, "node_modules/@types/koa": { - "version": "2.13.4", - "resolved": "https://registry.npmmirror.com/@types/koa/-/koa-2.13.4.tgz", - "integrity": "sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==", - "dev": true, + "version": "2.13.5", + "resolved": "https://registry.npmmirror.com/@types/koa/-/koa-2.13.5.tgz", + "integrity": "sha512-HSUOdzKz3by4fnqagwthW/1w/yJspTgppyyalPVbgZf8jQWvdIXcVW5h2DGtw4zYntOaeRGx49r1hxoPWrD4aA==", "dependencies": { "@types/accepts": "*", "@types/content-disposition": "*", @@ -225,7 +232,6 @@ "version": "3.2.5", "resolved": "https://registry.npmmirror.com/@types/koa-compose/-/koa-compose-3.2.5.tgz", "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", - "dev": true, "dependencies": { "@types/koa": "*" } @@ -251,8 +257,7 @@ "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { "version": "17.0.8", @@ -271,14 +276,12 @@ "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "node_modules/@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/request": { "version": "2.48.4", @@ -318,7 +321,6 @@ "version": "1.13.10", "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.13.10.tgz", "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -437,6 +439,11 @@ "es-shim-unscopables": "^1.0.0" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "node_modules/asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -512,6 +519,14 @@ "node": ">=8" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cache-content-type": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/cache-content-type/-/cache-content-type-1.0.1.tgz", @@ -567,6 +582,17 @@ "node": ">= 0.12.0" } }, + "node_modules/co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -725,6 +751,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -891,6 +926,28 @@ "node": ">= 0.12" } }, + "node_modules/formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, + "node_modules/formidable/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", @@ -1060,6 +1117,14 @@ "node": ">= 0.4" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "engines": { + "node": ">=8" + } + }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/http-assert/-/http-assert-1.5.0.tgz", @@ -1109,6 +1174,25 @@ "npm": ">=1.3.7" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", @@ -1398,6 +1482,19 @@ "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" } }, + "node_modules/koa-body": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/koa-body/-/koa-body-6.0.1.tgz", + "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==", + "dependencies": { + "@types/co-body": "^6.1.0", + "@types/formidable": "^2.0.5", + "@types/koa": "^2.13.5", + "co-body": "^6.1.0", + "formidable": "^2.0.1", + "zod": "^3.19.1" + } + }, "node_modules/koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmmirror.com/koa-compose/-/koa-compose-4.1.0.tgz", @@ -1804,6 +1901,43 @@ "node": ">=0.6" } }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", @@ -2183,6 +2317,14 @@ "which-boxed-primitive": "^1.0.2" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -2275,6 +2417,11 @@ "engines": { "node": ">=6" } + }, + "node_modules/zod": { + "version": "3.20.5", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.20.5.tgz", + "integrity": "sha512-BTAAliwfoB9dWf2hC+TXlyWKk/YTqRGZjHQR0WLC2A2pzierWo7KuQ1ebjS4SNaFaxg/lDItzl9/QTgLjcHbgw==" } }, "dependencies": { @@ -2329,7 +2476,6 @@ "version": "1.3.5", "resolved": "https://registry.npmmirror.com/@types/accepts/-/accepts-1.3.5.tgz", "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -2353,7 +2499,6 @@ "version": "1.19.2", "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -2370,11 +2515,19 @@ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" }, + "@types/co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/@types/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-3e0q2jyDAnx/DSZi0z2H0yoZ2wt5yRDZ+P7ymcMObvq0ufWRT4tsajyO+Q1VwVWiv9PRR4W3YEjEzBjeZlhF+w==", + "requires": { + "@types/node": "*", + "@types/qs": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -2382,14 +2535,12 @@ "@types/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmmirror.com/@types/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==", - "dev": true + "integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==" }, "@types/cookies": { "version": "0.7.7", "resolved": "https://registry.npmmirror.com/@types/cookies/-/cookies-0.7.7.tgz", "integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==", - "dev": true, "requires": { "@types/connect": "*", "@types/express": "*", @@ -2401,7 +2552,6 @@ "version": "4.17.13", "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.13.tgz", "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -2413,36 +2563,39 @@ "version": "4.17.28", "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, + "@types/formidable": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@types/formidable/-/formidable-2.0.5.tgz", + "integrity": "sha512-uvMcdn/KK3maPOaVUAc3HEYbCEhjaGFwww4EsX6IJfWIJ1tzHtDHczuImH3GKdusPnAAmzB07St90uabZeCKPA==", + "requires": { + "@types/node": "*" + } + }, "@types/http-assert": { "version": "1.5.3", "resolved": "https://registry.npmmirror.com/@types/http-assert/-/http-assert-1.5.3.tgz", - "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==", - "dev": true + "integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==" }, "@types/http-errors": { "version": "1.8.2", "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-1.8.2.tgz", - "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==", - "dev": true + "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==" }, "@types/keygrip": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/@types/keygrip/-/keygrip-1.0.2.tgz", - "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", - "dev": true + "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, "@types/koa": { - "version": "2.13.4", - "resolved": "https://registry.npmmirror.com/@types/koa/-/koa-2.13.4.tgz", - "integrity": "sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==", - "dev": true, + "version": "2.13.5", + "resolved": "https://registry.npmmirror.com/@types/koa/-/koa-2.13.5.tgz", + "integrity": "sha512-HSUOdzKz3by4fnqagwthW/1w/yJspTgppyyalPVbgZf8jQWvdIXcVW5h2DGtw4zYntOaeRGx49r1hxoPWrD4aA==", "requires": { "@types/accepts": "*", "@types/content-disposition": "*", @@ -2458,7 +2611,6 @@ "version": "3.2.5", "resolved": "https://registry.npmmirror.com/@types/koa-compose/-/koa-compose-3.2.5.tgz", "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", - "dev": true, "requires": { "@types/koa": "*" } @@ -2484,8 +2636,7 @@ "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { "version": "17.0.8", @@ -2504,14 +2655,12 @@ "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/request": { "version": "2.48.4", @@ -2550,7 +2699,6 @@ "version": "1.13.10", "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.13.10.tgz", "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, "requires": { "@types/mime": "^1", "@types/node": "*" @@ -2649,6 +2797,11 @@ "es-shim-unscopables": "^1.0.0" } }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2712,6 +2865,11 @@ "fill-range": "^7.0.1" } }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, "cache-content-type": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/cache-content-type/-/cache-content-type-1.0.1.tgz", @@ -2755,6 +2913,17 @@ "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" }, + "co-body": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/co-body/-/co-body-6.1.0.tgz", + "integrity": "sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==", + "requires": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2876,6 +3045,15 @@ "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3015,6 +3193,27 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "dependencies": { + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", @@ -3142,6 +3341,11 @@ "has-symbols": "^1.0.2" } }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" + }, "http-assert": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/http-assert/-/http-assert-1.5.0.tgz", @@ -3180,6 +3384,19 @@ "sshpk": "^1.7.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==" + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", @@ -3411,6 +3628,19 @@ "vary": "^1.1.2" } }, + "koa-body": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/koa-body/-/koa-body-6.0.1.tgz", + "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==", + "requires": { + "@types/co-body": "^6.1.0", + "@types/formidable": "^2.0.5", + "@types/koa": "^2.13.5", + "co-body": "^6.1.0", + "formidable": "^2.0.1", + "zod": "^3.19.1" + } + }, "koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmmirror.com/koa-compose/-/koa-compose-4.1.0.tgz", @@ -3741,6 +3971,36 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", @@ -4029,6 +4289,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -4102,6 +4367,11 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "zod": { + "version": "3.20.5", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.20.5.tgz", + "integrity": "sha512-BTAAliwfoB9dWf2hC+TXlyWKk/YTqRGZjHQR0WLC2A2pzierWo7KuQ1ebjS4SNaFaxg/lDItzl9/QTgLjcHbgw==" } } } diff --git a/package.json b/package.json index 1d8a24e..ad37d14 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "node index.js", - "start-dev": "tsc && node index.js", + "dev": "tsc && node index.js", "build": "tsc" }, "author": { @@ -20,6 +20,7 @@ "decoders": "^1.25.3", "handlebars": "^4.7.7", "koa": "^2.13.4", + "koa-body": "^6.0.1", "koa-router": "^10.1.1", "lua-runner": "^2.0.3", "micromatch": "^4.0.4", diff --git a/src/App.ts b/src/App.ts index 12f6729..6b9970d 100644 --- a/src/App.ts +++ b/src/App.ts @@ -5,6 +5,8 @@ import { BaseProvider, MultipleMessage } from './base/provider/BaseProvider'; import { ChannelManager } from './ChannelManager'; import { ChannelConfig, Config } from './Config'; +import { EventManager } from './EventManager'; +import { CommonSendMessage } from './message/Message'; import { ProviderManager } from './ProviderManager'; import { RestfulApiManager } from './RestfulApiManager'; import { RobotManager } from './RobotManager'; @@ -16,6 +18,7 @@ export default class App { public config: Config; public srcPath: string = __dirname; + public event!: EventManager; public robot!: RobotManager; public provider!: ProviderManager; public service!: ServiceManager; @@ -30,6 +33,8 @@ export default class App { async initialize() { await this.initModules(); + await this.initRestfulApiManager(); + await this.initEventManager(); await this.initRobot(); await this.initProviderManager(); await this.initServiceManager(); @@ -42,6 +47,16 @@ export default class App { await Setup.initHandlebars(); } + async initRestfulApiManager() { + this.restfulApi = new RestfulApiManager(this, this.config.http_api); + await this.restfulApi.initialize(); + } + + async initEventManager() { + this.event = new EventManager(this); + await this.event.initialize(); + } + async initRobot() { this.robot = new RobotManager(this, this.config.robot); await this.robot.initialize(); @@ -79,11 +94,6 @@ export default class App { await this.channel.initialize(); } - async initRestfulApiManager() { - this.restfulApi = new RestfulApiManager(this, this.config.http_api); - await this.restfulApi.initialize(); - } - /** * 获取服务 * @param serviceName @@ -102,14 +112,22 @@ export default class App { } /** - * 发送消息 + * 发送推送消息 * @param channelId Channel ID * @param messages 消息内容 * @returns */ - async sendMessage(channelId: string, messages: MultipleMessage): Promise { + async sendPushMessage(channelId: string, messages: MultipleMessage): Promise { console.log(`[${channelId}] 消息: `, messages); - this.robot.sendMessage(channelId, messages); + this.robot.sendPushMessage(channelId, messages); + } + + /** + * 发送消息 + * @param message + */ + async sendMessage(message: CommonSendMessage) { + } require(file: string): any { diff --git a/src/EventManager.ts b/src/EventManager.ts new file mode 100644 index 0000000..f8f7e55 --- /dev/null +++ b/src/EventManager.ts @@ -0,0 +1,253 @@ +import EventEmitter from "events"; +import { debounce } from "throttle-debounce"; +import App from "./App"; +import { CommonReceivedMessage } from "./message/Message"; +import { GroupSender, UserSender } from "./message/Sender"; +import { Robot } from "./RobotManager"; + +export class EventManager { + private app: App; + private eventEmitter: EventEmitter; + private eventGroup: Record = {}; + private eventHandlerList: Record = {}; + + constructor(app: App) { + this.app = app; + this.eventEmitter = new EventEmitter; + } + + public async initialize() { + + } + + public getEventGroup() { + + } +} + +export const MessagePriority = { + LOWEST: 0, + LOW: 20, + DEFAULT: 40, + /** + * 在控制器中添加的临时会话处理器。 + * 用于处理深层会话,会比一般指令优先级高。 + */ + TEMP_HANDLER: 60, + HIGH: 80, + /** + * 一些系统自带的事件处理,如:记录消息 + */ + SYSTEM: 90, + /** + * 最高优先级,可以覆盖系统原本的处理 + */ + HIGHEST: 100 +}; + +export type MessageEventOptions = { + priority?: number, + /** Base sources: private, group, channel */ + source?: string[], + robotApi?: string[], +}; + +export type CommandInfo = { + command: string, + name: string, + alias?: string[], + help?: string, +}; + +export type EventListenerInfo = { + priority: number; + callback: CallableFunction; +} + +export type MessageCallback = (message: CommonReceivedMessage, resolved: VoidFunction) => any; +export type CommandCallback = (argv: string[], message: CommonReceivedMessage, resolved: VoidFunction) => any; +export type RawEventCallback = (robot: Robot, event: any, resolved: VoidFunction) => any; + +export type AllowedList = string[] | '*'; + +export class EventGroup { + readonly id: string; + + public allowPrivate = true; + public allowGroup = true; + + public allowedGroupList: AllowedList = '*'; + + private commandList: CommandInfo[] = []; + private eventList: Record = {}; + + constructor(id: string) { + this.id = id; + } + + public shouldAllowSource: (sender: any) => boolean = (sender) => { + if (sender instanceof UserSender) { + if (this.allowPrivate) { + return true; + } + } else if (sender instanceof GroupSender) { + if (this.allowedGroupList === '*') { + return true; + } else if (this.allowedGroupList.includes(sender.groupId)) { + return true; + } + } + return false; + } + + public emit(eventName: string, ...args: any[]) { + const eventList = this.eventList[eventName]; + + let isResolved = false; + let isBreakAll = false; + + const resolved = (breakAll: boolean = false) => { + isResolved = true; + if (breakAll) { + isBreakAll = true; + } + }; + + for (let eventInfo of eventList) { + eventInfo.callback(...args, resolved); + if (isResolved) { + break; + } + } + + return !isBreakAll; + } + + on(event: string, callback: CallableFunction, options?: MessageEventOptions) { + if (!(event in this.eventList)) { + this.eventList[event] = []; + } + + let defaultOptions: MessageEventOptions = { + priority: MessagePriority.DEFAULT + }; + if (!options) { + options = defaultOptions; + } else { + options = { + ...defaultOptions, + ...options + }; + } + + const eventInfo = { + callback: callback, + priority: options.priority! + }; + const singleEventList = this.eventList[event]; + const priority = options.priority!; + + // Add event to specified position + if (singleEventList.length === 0) { + singleEventList.push(eventInfo); + } else { + for (let i = 0; i < singleEventList.length; i++) { + if (singleEventList[i].priority < priority) { + const target = i - 1; + if (target === 0) { + singleEventList.unshift(eventInfo); + } else { + this.eventList[event] = [ + ...singleEventList.slice(0, target), + eventInfo, + ...singleEventList.slice(target) + ]; + } + } + } + } + } + + addCommand(command: string, name: string, callback: MessageCallback, options?: MessageEventOptions): void + addCommand(commandInfo: CommandInfo, callback: MessageCallback, options?: MessageEventOptions): void + addCommand(...args: any[]): void { + // 处理传入参数 + let commandInfo: Partial = {}; + let callback: MessageCallback; + let options: MessageEventOptions; + if (typeof args[0] === 'string' && typeof args[1] === 'string') { + commandInfo = { + command: args[0], + name: args[1] + }; + callback = args[2]; + options = args[3] ?? {}; + } else { + commandInfo = args[0]; + callback = args[1]; + options = args[2] ?? {}; + } + + // 注册消息 + this.commandList.push(commandInfo as any); + this.on(`command/${commandInfo.command}`, callback, options); + if (Array.isArray(commandInfo.alias)) { // Add event for alias + commandInfo.alias.forEach((cmd) => { + this.on(`command/${cmd}`, callback, options); + }); + } + } + + /** + * Add message handle. + * will be trigger on private message or group message with mentions to robot + * @param callback + * @param options + */ + onMentionedMessage(callback: MessageCallback, options?: MessageEventOptions) { + this.on('mentionedMessage', callback, options); + this.on('privateMessage', callback, options); + } + + /** + * Add private message handle. + * @param callback + * @param options + */ + onPrivateMessage(callback: MessageCallback, options?: MessageEventOptions) { + this.on('privateMessage', callback, options); + } + + /** + * Add group message handle. + * @param callback + * @param options + */ + onGroupMessage(callback: MessageCallback, options?: MessageEventOptions) { + this.on('groupMessage', callback, options); + } + + /** + * Add message handle. + * Will handle all messages in group + * @param callback + * @param options + */ + onMessage(callback: MessageCallback, options?: MessageEventOptions) { + this.on('message', callback, options); + } + + /** + * Add raw message handle. + * Will be triggered even when the message is a command. + * @param callback + * @param options + */ + onRawMessage(callback: MessageCallback, options?: MessageEventOptions) { + this.on('rawMessage', callback, options); + } + + onRawEvent(callback: RawEventCallback, options?: MessageEventOptions) { + this.on('rawEvent', callback, options); + } +} \ No newline at end of file diff --git a/src/RestfulApiManager.ts b/src/RestfulApiManager.ts index 423c899..7754e22 100644 --- a/src/RestfulApiManager.ts +++ b/src/RestfulApiManager.ts @@ -3,19 +3,22 @@ import Koa from 'koa'; import { RestfulApiConfig } from "./Config"; import Router from "koa-router"; import { makeRoutes } from "./restful/routes"; +import koaBody from "koa-body"; export interface RestfulContext { - app: App + feedbot: App, + request: Koa.Request, } export type FullRestfulContext = RestfulContext & Koa.BaseContext; +export type RestfulRouter = Router; export class RestfulApiManager { private app: App; private config: RestfulApiConfig; - private koa: Koa; - private router: Router; + public koa: Koa; + public router: RestfulRouter; constructor(app: App, config: Pick) { this.app = app; @@ -32,13 +35,18 @@ export class RestfulApiManager { public initialize(): Promise { makeRoutes(this.router, this, this.app); - + + this.koa.use(koaBody()); this.koa.use(this.globalMiddleware); + this.koa.on("error", (err, ctx) => { + console.error(err); + }); + return new Promise((resolve) => { this.koa.use(this.router.routes()); this.koa.listen(this.config.port, () => { - console.log(`Restful API 启动于:${this.config.port}`); + console.log(`Restful API 启动于:http://${this.config.host}:${this.config.port}`); resolve(); }); }); @@ -50,7 +58,7 @@ export class RestfulApiManager { * @param next */ public globalMiddleware = async (ctx: FullRestfulContext, next: () => Promise) => { - ctx.app = this.app; // 注入全局app + ctx.feedbot = this.app; // 注入全局app await next(); } @@ -135,4 +143,8 @@ export class RestfulApiManager { } return false; } + + public getRobotRouter(robotId: string) { + return this.router.prefix('/' + robotId); + } } \ No newline at end of file diff --git a/src/RobotManager.ts b/src/RobotManager.ts index 7b13a52..37b05f3 100644 --- a/src/RobotManager.ts +++ b/src/RobotManager.ts @@ -4,24 +4,30 @@ import path from "path"; import App from "./App"; import { MultipleMessage } from "./base/provider/BaseProvider"; import { RobotConfig } from "./Config"; +import { CommonSendMessage } from "./message/Message"; +import { RestfulApiManager, RestfulContext, RestfulRouter } from "./RestfulApiManager"; import { Target } from "./SubscribeManager"; const ROBOT_PATH = __dirname + "/robot"; export interface Robot { - initialize(): Promise; - sendMessage(targets: Target[], message: string): Promise; - baseId?: string; + type: string; + robotId?: string; + uid?: string; + initialize?: () => Promise; + initRestfulApi?: (router: RestfulRouter, api: RestfulApiManager) => Promise; + sendMessage(message: CommonSendMessage): Promise; + sendPushMessage(targets: Target[], message: string): Promise; } export class RobotManager { private app: App; - private config: { [key: string]: RobotConfig }; + private config: Record; - private robotClasses: { [key: string]: any }; - private robots: { [key: string]: Robot }; + private robotClasses: Record; + private robots: Record; - constructor(app: App, config: { [key: string]: RobotConfig }) { + constructor(app: App, config: Record) { this.app = app; this.config = config; @@ -59,7 +65,10 @@ export class RobotManager { let robotClass = this.robotClasses[robotType]; try { let robotObject: Robot = new robotClass(this.app, robotId, robotConfig); - await robotObject.initialize(); + + await robotObject.initialize?.(); + await robotObject.initRestfulApi?.(this.app.restfulApi.getRobotRouter(robotId), this.app.restfulApi); + this.robots[robotId] = robotObject; console.log(`已加载Robot: ${robotId}`); } catch(err) { @@ -71,15 +80,15 @@ export class RobotManager { } } - public async sendMessage(channelId: string, messages: MultipleMessage) { + public async sendPushMessage(channelId: string, messages: MultipleMessage) { for (let robotId in this.robots) { let robot = this.robots[robotId]; - let baseId = robot.baseId; + let robotType = robot.type; let currentMsg: string | null = null; if (robotId in messages) { currentMsg = messages[robotId]; - } else if (baseId && baseId in messages) { - currentMsg = messages[baseId]; + } else if (robotType && robotType in messages) { + currentMsg = messages[robotType]; } else if ("base" in messages) { currentMsg = messages["base"]; } @@ -93,7 +102,7 @@ export class RobotManager { } try { - await robot.sendMessage(targets, currentMsg); + await robot.sendPushMessage(targets, currentMsg); } catch(err) { console.error(`[${channelId}] 无法发送消息到 ${robotId} : `, err); } diff --git a/src/Setup.ts b/src/Setup.ts index 6c54812..f4f34a2 100644 --- a/src/Setup.ts +++ b/src/Setup.ts @@ -1,5 +1,5 @@ import Handlebars from "handlebars"; -import { Utils } from "./Utils"; +import { Utils } from "./utils/Utils"; export class Setup { public static initHandlebars() { diff --git a/src/base/provider/BaseProvider.ts b/src/base/provider/BaseProvider.ts index fe605e5..bdb2852 100644 --- a/src/base/provider/BaseProvider.ts +++ b/src/base/provider/BaseProvider.ts @@ -42,7 +42,7 @@ export class BaseProvider { * 发送消息 */ sendMessage(messages: MultipleMessage) { - this.app.sendMessage(this.channelId, messages) + this.app.sendPushMessage(this.channelId, messages) .then(() => {}) .catch((err) => { this.error('无法发送消息', err); @@ -53,7 +53,7 @@ export class BaseProvider { * 发送消息(异步) */ sendMessageAsync(messages: MultipleMessage): Promise { - return this.app.sendMessage(this.channelId, messages); + return this.app.sendPushMessage(this.channelId, messages); } /** diff --git a/src/controller/EchoController.ts b/src/controller/EchoController.ts new file mode 100644 index 0000000..5216644 --- /dev/null +++ b/src/controller/EchoController.ts @@ -0,0 +1,21 @@ +import { CommonGroupMessage, CommonReceivedMessage, CommonSendMessage } from "../message/Message"; +import { GroupSender } from "../message/Sender"; + +export class EchoController { + fliterBotGroupMessage(message: CommonGroupMessage) { + let newMsg = message.content.map((chunk) => { + if (chunk.type === 'text') { + return { + type: 'text', + data: { + text: chunk.data.text.replace(/^[ ]*说[::, ]*/, '').replace(/我/g, '你'), + } + }; + } else { + return chunk; + } + }); + + message.sendReply(newMsg); + } +} \ No newline at end of file diff --git a/src/message/Message.ts b/src/message/Message.ts new file mode 100644 index 0000000..59920ba --- /dev/null +++ b/src/message/Message.ts @@ -0,0 +1,184 @@ +import { Robot } from "../RobotManager"; +import { BaseSender, GroupSender, UserSender } from "./Sender"; + +export interface MessageChunk { + type: string; + baseType?: string; + data: any; +} + +export interface TextMessage extends MessageChunk { + type: 'text'; + data: { + text: string; + }; +} + +export interface ImageMessage extends MessageChunk { + type: 'image'; + data: { + url: string; + alt?: string; + }; +} + +export interface VoiceMessage extends MessageChunk { + type: 'voice'; + data: { + url: string; + text?: string; + }; +} + +export interface AttachmentMessage extends MessageChunk { + type: 'attachment'; + data: { + url: string; + fileName: string; + }; +} + +export interface MentionMessage extends MessageChunk { + type: 'mention'; + data: { + uid: string; + text?: string; + }; +} + +export type CommonMessageType = "text" | "combine" | "image" | "media" | "toast"; +export type CommonMessageOrigin = "private" | "group" | "channel"; + +/** 基本消息 */ +export class CommonMessage { + /** 消息ID */ + id?: string; + /** 消息内容 */ + content: MessageChunk[] = []; + /** 主类型 */ + type: string | CommonMessageType = "text"; + origin: string | CommonMessageOrigin = "private"; + /** 回复的消息ID */ + replyId?: string; + /** 提到的人 */ + mentions?: { uid: string, text?: string }[]; + + /** + * 提到某人 + * @param uid 用户ID + * @param text 显示的文本(部分接口支持) + * @returns + */ + public mention(uid: string, text?: string) { + // 私聊消息不支持 + if (this.origin === 'private') { + return false; + } + + if (typeof this.mentions === 'undefined') { + this.mentions = []; + } else if (this.mentions!.find((u) => u.uid === uid)) { + return true; + } + + this.mentions.push({ uid, text }); + this.content.unshift({ + type: 'mention', + data: { uid, text } + }); + return true; + } + + /** + * 取消提到某人 + * @param uid 用户ID + * @returns + */ + public removeMention(uid: string) { + // 私聊消息不支持 + if (this.origin === 'private') { + return false; + } + + if (typeof this.mentions === 'undefined') { + return true; + } else { + this.mentions = this.mentions.filter((u) => u.uid !== uid); + if (this.mentions.length === 0) { + delete this.mentions; + } + + this.content = this.content.filter((msg) => msg.type !== 'mention' || msg.data?.uid !== uid); + + return true; + } + } +} + +/** 基本发送的消息 */ +export class CommonSendMessage extends CommonMessage { + sender: Robot; + targetId: string; + + constructor(sender: Robot, targetType: string, targetId: string, content?: MessageChunk[]) { + super(); + this.sender = sender; + this.type = targetType; + this.targetId = targetId; + if (Array.isArray(content)) this.content = content; + } +} + +export class CommonReceivedMessage extends CommonMessage { + // 接收时间 + time: Date = new Date(); + // 接收者 + receiver: Robot; + // 发送者 + sender: any; + + constructor(receiver: Robot, messageId?: string) { + super(); + + this.receiver = receiver; + this.id = messageId; + } + + public async sendReply(message: string | MessageChunk[], addReply: boolean = false): Promise { + const sender = this.sender as BaseSender; + let newMessage = new CommonSendMessage(this.receiver!, sender.type, sender.targetId); + if (typeof message === 'string') { + let msgContent: MessageChunk[] = [{ + type: 'text', + data: { text: message } + }]; + newMessage.content = msgContent; + } else if (Array.isArray(message)) { + newMessage.content = message; + } else { + return null; + } + + newMessage = await this.receiver.sendMessage(newMessage); + + return newMessage; + } +} + +export class CommonPrivateMessage extends CommonReceivedMessage { + sender: US; + + constructor(sender: US, receiver: Robot, messageId?: string) { + super(receiver, messageId); + this.sender = sender; + } +} + +export class CommonGroupMessage extends CommonReceivedMessage { + sender: GS; + + constructor(sender: GS, receiver: Robot, messageId?: string) { + super(receiver, messageId); + this.sender = sender; + } +} \ No newline at end of file diff --git a/src/message/Sender.ts b/src/message/Sender.ts new file mode 100644 index 0000000..6b6a526 --- /dev/null +++ b/src/message/Sender.ts @@ -0,0 +1,66 @@ +export type BaseSenderType = "user" | "group" | "channel"; + +export interface BaseSender { + readonly type: string | BaseSenderType; + readonly targetId: string; +} + +export class UserSender implements BaseSender { + public readonly type = "user"; + public uid: string; + public userName?: string; + public nickName?: string; + + constructor(uid: string) { + this.uid = uid; + } + + static newAnonymous() { + return new UserSender(''); + } + + get targetId() { + return this.uid; + } + + get displayName() { + return this.nickName ?? this.userName ?? this.uid; + } +} + +export class GroupSender { + public readonly type = "group"; + + public groupId: string; + public groupName?: string; + + public uid: string; + public userName?: string; + public globalNickName?: string; + public nickName?: string; + + constructor(groupId: string, uid: string) { + this.groupId = groupId; + this.uid = uid; + } + + get targetId() { + return this.groupId; + } + + get groupDisplayName() { + return this.groupName ?? this.groupId; + } + + get displayName() { + return this.nickName ?? this.globalNickName ?? this.userName ?? this.uid; + } + + get userSender() { + let sender = new UserSender(this.uid); + sender.userName = this.userName; + sender.nickName = this.globalNickName; + + return sender; + } +} \ No newline at end of file diff --git a/src/robot/QQRobot.ts b/src/robot/QQRobot.ts index 28a930c..8fc943b 100644 --- a/src/robot/QQRobot.ts +++ b/src/robot/QQRobot.ts @@ -2,7 +2,11 @@ import App from "../App"; import { Robot } from "../RobotManager"; import { Target } from "../SubscribeManager"; import request from "request-promise"; -import { Utils } from "../Utils"; +import { Utils } from "../utils/Utils"; +import { FullRestfulContext, RestfulApiManager, RestfulRouter } from "../RestfulApiManager"; +import koa from "koa"; +import { convertMessageToQQChunk, parseQQMessageChunk, QQFaceMessage, QQGroupMessage, QQGroupSender, QQImageMessage, QQPrivateMessage, QQUserSender, QQVoiceMessage } from "./qq/Message"; +import { CommonReceivedMessage, CommonSendMessage, MentionMessage, TextMessage } from "../message/Message"; export type QQRobotConfig = { user: string; @@ -10,23 +14,109 @@ export type QQRobotConfig = { baseId?: string; } +export type QQGroupInfo = { + groupId: string, + groupName?: string, + memberCount?: number, + memberLimit?: number +}; + export default class QQRobot implements Robot { - private robotId: string; + public type = 'qq'; + + public uid: string; + public robotId: string; + + private app: App; private endpoint: string; - private botQQ: number; - public baseId?: string; + private taskId?: NodeJS.Timer; + + private groupList: QQGroupInfo[] = []; constructor(app: App, robotId: string, config: QQRobotConfig) { + this.app = app; this.robotId = robotId; this.endpoint = 'http://' + config.host; - this.botQQ = parseInt(config.user); - - this.baseId = config.baseId; + this.uid = config.user; } async initialize() { - + this.refreshRobotInfo(); + + // 每30分钟刷新一次信息 + this.taskId = setInterval(() => { + this.refreshRobotInfo(); + }, 30 * 60 * 1000); + } + + async refreshRobotInfo() { + // 刷新群信息 + let remoteGroupList = await this.getGroupList(); + remoteGroupList.forEach((groupInfo) => { + if (groupInfo.group_id) { + this.groupList.push({ + groupId: groupInfo.group_id, + groupName: groupInfo.group_name, + memberCount: groupInfo.member_count, + memberLimit: groupInfo.max_member_count + }); + } + }); + } + + async initRestfulApi(router: RestfulRouter, api: RestfulApiManager) { + api.router.post(`/event`, this.handlePostEvent.bind(this)); + } + + async handlePostEvent(ctx: FullRestfulContext, next: koa.Next) { + if (ctx.request.body?.post_type) { + const postData = ctx.request.body; + switch (postData.post_type) { + case 'message': + this.handleMessage(postData); + break; + } + } + + ctx.body = 'OK'; + await next(); + } + + /** + * 处理消息事件 + * @param postData + */ + async handleMessage(postData: any) { + if (postData.message_id) { + if (postData.message_type === 'group') { + // 处理群消息 + let groupInfo = this.groupList.find((info) => info.groupId === postData.group_id); + + let groupSender = new QQGroupSender(postData.group_id, postData.user_id); + groupSender.groupInfo = groupInfo; + groupSender.groupName = groupInfo?.groupName; + groupSender.globalNickName = postData.sender?.nickname; + groupSender.nickName = postData.sender?.card; + groupSender.role = postData.sender?.role ?? 'member'; + groupSender.level = postData.sender?.level; + groupSender.title = postData.sender?.title; + + let message = new QQGroupMessage(groupSender, this, postData.message_id.toString()); + message.time = new Date(postData.time * 1000); + + message = await parseQQMessageChunk(postData.message ?? [], message); + } else if (postData.message_type === 'private') { + // 处理私聊消息 + let userSender = new QQUserSender(postData.user_id); + userSender.nickName = postData.sender?.nickname; + + let message = new QQPrivateMessage(userSender, this, postData.message_id.toString()); + message.time = new Date(postData.time * 1000); + + message = await parseQQMessageChunk(postData.message ?? [], message); + } + } } /** @@ -35,8 +125,8 @@ export default class QQRobot implements Robot { * @param message - 消息 * @returns 回调 */ - async sendToUser(user: number|number[], message: string) { - if(Array.isArray(user)){ //发送给多个用户的处理 + async sendToUser(user: string | string[], message: string | any[]) { + if (Array.isArray(user)) { //发送给多个用户的处理 for (let one of user) { await this.sendToUser(one, message); await Utils.sleep(100); @@ -53,8 +143,8 @@ export default class QQRobot implements Robot { /** * 发送群消息 */ - async sendToGroup(group: number|number[], message: string) { - if(Array.isArray(group)){ //发送给多个群组的处理 + async sendToGroup(group: string | string[], message: string | any[]) { + if (Array.isArray(group)) { //发送给多个群组的处理 for (let one of group) { await this.sendToGroup(one, message); await Utils.sleep(100); @@ -70,15 +160,31 @@ export default class QQRobot implements Robot { /** * 发送消息 + * @param message */ - async sendMessage(targets: Target[], message: string) { - let groupList: number[] = []; - let userList: number[] = []; + async sendMessage(message: CommonSendMessage): Promise { + let msgData = await convertMessageToQQChunk(message); + + if (message.origin === 'private') { + await this.sendToUser(message.targetId, msgData); + } else if (message.origin === 'group') { + await this.sendToGroup(message.targetId, msgData); + } + + return message; + } + + /** + * 发送消息 + */ + async sendPushMessage(targets: Target[], message: string) { + let groupList: string[] = []; + let userList: string[] = []; for (let target of targets) { if (target.type === "group") { - groupList.push(parseInt(target.identity)); + groupList.push(target.identity); } else if (target.type === "user") { - userList.push(parseInt(target.identity)); + userList.push(target.identity); } } @@ -90,6 +196,15 @@ export default class QQRobot implements Robot { } } + async getGroupList(): Promise { + const res = await this.doApiRequest('get_group_list', {}); + if (res && res.status === 'ok') { + return res.data; + } else { + return []; + } + } + /** * 执行API调用 */ diff --git a/src/robot/TelegramRobot.ts b/src/robot/TelegramRobot.ts index 08f93a8..f0d5bff 100644 --- a/src/robot/TelegramRobot.ts +++ b/src/robot/TelegramRobot.ts @@ -1,8 +1,9 @@ import TelegramBot from "node-telegram-bot-api"; import App from "../App"; +import { CommonSendMessage } from "../message/Message"; import { Robot } from "../RobotManager"; import { Target } from "../SubscribeManager"; -import { Utils } from "../Utils"; +import { Utils } from "../utils/Utils"; export type TelegramRobotConfig = { token: string; @@ -11,13 +12,14 @@ export type TelegramRobotConfig = { } export default class TelegramRobot implements Robot { - private robotId: string; - bot: TelegramBot; - baseId?: string | undefined; + public type = 'telegram'; + public robotId: string; + public uid?: string; + + private bot: TelegramBot; constructor(app: App, robotId: string, config: TelegramRobotConfig) { this.robotId = robotId; - this.baseId = config.baseId; let botOptions: any = { polling: true @@ -69,12 +71,20 @@ export default class TelegramRobot implements Robot { return await this.bot.sendMessage(chatId, message); } + /** + * 发送消息 + * @param message + */ + async sendMessage(message: CommonSendMessage): Promise { + return message; + } + /** * 发送机器人消息 * @param targets 发送目标 * @param message 消息内容 */ - async sendMessage(targets: Target[], message: string): Promise { + async sendPushMessage(targets: Target[], message: string): Promise { let chatIdList: number[] = []; for (let target of targets) { chatIdList.push(parseInt(target.identity)); diff --git a/src/robot/qq/Message.ts b/src/robot/qq/Message.ts new file mode 100644 index 0000000..27a0823 --- /dev/null +++ b/src/robot/qq/Message.ts @@ -0,0 +1,223 @@ +import { CommonGroupMessage, CommonPrivateMessage, CommonReceivedMessage, CommonSendMessage, MentionMessage, MessageChunk, TextMessage } from "../../message/Message"; +import { GroupSender, UserSender } from "../../message/Sender"; +import { QQGroupInfo } from "../QQRobot"; + +export interface QQFaceMessage extends MessageChunk { + type: 'qqface'; + data: { + id: string + }; +} + +export interface QQImageMessage extends MessageChunk { + type: 'qqimage'; + data: { + url: string; + alt?: string; + subType?: string; + }; +} + +export interface QQVoiceMessage extends MessageChunk { + type: 'qqvoice'; + data: { + url: string; + }; +} + +export interface QQUrlMessage extends MessageChunk { + type: 'qqurl'; + data: { + url: string; + title: string; + }; +} + +export class QQUserSender extends UserSender { + constructor(uid: string) { + super(uid); + this.userName = uid; + } +} + +export class QQGroupSender extends GroupSender { + public role: "owner" | "admin" | "member" = 'member'; + public level?: string; + public title?: string; + public groupInfo?: QQGroupInfo; + + constructor(groupId: string, uid: string) { + super(groupId, uid); + this.userName = uid; + } + + get userSender() { + let sender = new QQUserSender(this.uid); + sender.userName = this.userName; + sender.nickName = this.globalNickName; + + return sender; + } +} + +/** + * 解析消息数组到消息对象 + * @param messageData + * @param message + * @returns + */ +export async function parseQQMessageChunk(messageData: any[], message: CommonReceivedMessage): Promise { + let willIgnoreMention = false; + messageData.forEach((chunkData) => { + if (chunkData.type) { + switch (chunkData.type) { + case 'text': + message.content.push({ + type: 'text', + data: { + text: chunkData.data?.text ?? '' + } + } as TextMessage); + break; + case 'image': + message.content.push({ + type: 'qqimage', + baseType: 'image', + data: { + url: chunkData.data?.url ?? '', + alt: chunkData.data?.file, + subType: chunkData.data?.subType + } + } as QQImageMessage); + break; + case 'record': + message.content.push({ + type: 'qqvoice', + baseType: 'voice', + data: { + url: chunkData.data?.url ?? '', + } + } as QQVoiceMessage); + break; + case 'face': + message.content.push({ + type: 'qqface', + data: { + id: chunkData.data?.id ?? '', + } + } as QQFaceMessage); + break; + case 'at': + if (chunkData.data?.qq) { + if (!willIgnoreMention) { + message.mention(chunkData.data.qq); + message.content.push({ + type: 'mention', + data: { + uid: chunkData.data.qq + } + } as MentionMessage); + } else { + willIgnoreMention = false; + } + } + break; + case 'reply': + if (chunkData.data?.id) { + message.replyId = chunkData.data.id; + willIgnoreMention = true; // 忽略下一个“@” + } + break; + } + } + }); + + if (message.content.length === 1) { + // 检查单一消息的类型 + switch (message.content[0].type) { + case 'qqimage': + message.type = 'image'; + break; + case 'qqvoice': + message.type = 'voice'; + break; + } + } + + return message; +} + +export async function convertMessageToQQChunk(message: CommonSendMessage) { + let msgChunk: any[] = []; + + message.content.forEach((rawChunk) => { + let chunk = rawChunk; + if (rawChunk.baseType && !rawChunk.type.startsWith('qq')) { + chunk = { + ...rawChunk, + type: rawChunk.baseType, + }; + } + + switch (chunk.type) { + case 'text': + msgChunk.push({ + type: 'text', + data: { + url: chunk.data.url + } + }); + break; + case 'qqface': + msgChunk.push({ + type: 'face', + data: { id: chunk.data.id } + }); + break; + case 'image': + case 'qqimage': + msgChunk.push({ + type: 'image', + data: { + file: chunk.data.url, + subType: chunk.data.subType ?? 0 + } + }); + break; + case 'voice': + case 'qqvoice': + msgChunk.push({ + type: 'record', + data: { + file: chunk.data.url + } + }); + break; + case 'mention': + msgChunk.push({ + type: 'at', + data: { + qq: chunk.data.uid + } + }); + break; + } + }) + + if (message.replyId) { + msgChunk.unshift({ + type: 'reply', + data: { id: message.replyId } + }); + } + + return msgChunk; +} + +export class QQPrivateMessage extends CommonPrivateMessage { + +} + +export class QQGroupMessage extends CommonGroupMessage { + +} \ No newline at end of file diff --git a/src/service/PusherService.ts b/src/service/PusherService.ts index d205332..20706ab 100644 --- a/src/service/PusherService.ts +++ b/src/service/PusherService.ts @@ -1,7 +1,7 @@ import App from "../App"; import { Service } from "../ServiceManager"; import Pusher, { Channel } from 'pusher-js'; -import { Utils } from "../Utils"; +import { Utils } from "../utils/Utils"; export type PusherServiceConfig = { app_id: string; diff --git a/src/Utils.ts b/src/utils/Utils.ts similarity index 91% rename from src/Utils.ts rename to src/utils/Utils.ts index 632a29b..29e60b4 100644 --- a/src/Utils.ts +++ b/src/utils/Utils.ts @@ -10,7 +10,7 @@ export class Utils { static getCurrentDate(): string { let date = new Date(); - return date.getFullYear() + '年' + date.getMonth() + '月' + date.getDate() + '日'; + return date.getFullYear() + '年' + (date.getMonth() + 1) + '月' + date.getDate() + '日'; } static count(dict: { [key: string]: any }): number { diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..95dd0c2 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1 @@ +export type AnyFunction = (...args: any) => any; \ No newline at end of file