From 2e55924a1934b7724af6658c34161007419b351b Mon Sep 17 00:00:00 2001 From: markyfsun Date: Sat, 27 Apr 2024 23:01:47 +0800 Subject: [PATCH 1/3] feat: market face --- src/ntqqapi/constructor.ts | 12 ++++++++ src/ntqqapi/types/msg.ts | 48 +++++++----------------------- src/onebot11/action/msg/SendMsg.ts | 4 ++- src/onebot11/constructor.ts | 4 +++ src/onebot11/types.ts | 4 ++- 5 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index 1c3e885..ae198d7 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -6,6 +6,7 @@ import { SendArkElement, SendFaceElement, SendFileElement, + SendMarketFaceElement, SendPicElement, SendPttElement, SendReplyElement, @@ -260,6 +261,17 @@ export class SendMsgElementConstructor { } } + static mface(emojiPackageId: number, emojiId: string, key: string):SendMarketFaceElement{ + return { + elementType: ElementType.MFACE, + marketFaceElement: { + emojiPackageId, + emojiId, + key + }, + } + } + static dice(resultId: number | null): SendFaceElement { // 实际测试并不能控制结果 diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index d36a93b..fd30bc2 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -10,6 +10,7 @@ export enum ElementType { FACE = 6, REPLY = 7, ARK = 10, + MFACE = 11, } export interface SendTextElement { @@ -91,6 +92,11 @@ export interface SendFaceElement { faceElement: FaceElement } +export interface SendMarketFaceElement { + elementType: ElementType.MFACE, + marketFaceElement: MarketFaceElement +} + export interface FileElement { "fileMd5"?: "", "fileName": string, @@ -128,7 +134,7 @@ export interface SendArkElement { } export type SendMessageElement = SendTextElement | SendPttElement | - SendPicElement | SendReplyElement | SendFaceElement | SendFileElement | SendVideoElement | SendArkElement + SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement | SendVideoElement | SendArkElement export enum AtType { notAt = 0, @@ -238,42 +244,9 @@ export interface FaceElement { } export interface MarketFaceElement { - "itemType": 6, - "faceInfo": 1, - "emojiPackageId": 203875, - "subType": 3, - "mediaType": 0, - "imageWidth": 200, - "imageHeight": 200, - "faceName": string, - "emojiId": "094d53bd1c9ac5d35d04b08e8a6c992c", - "key": "a8b1dd0aebc8d910", - "param": null, - "mobileParam": null, - "sourceType": null, - "startTime": null, - "endTime": null, - "emojiType": 1, - "hasIpProduct": null, - "voiceItemHeightArr": null, - "sourceName": null, - "sourceJumpUrl": null, - "sourceTypeName": null, - "backColor": null, - "volumeColor": null, - "staticFacePath": "E:\\SystemDocuments\\QQ\\721011692\\nt_qq\\nt_data\\Emoji\\marketface\\203875\\094d53bd1c9ac5d35d04b08e8a6c992c_aio.png", - "dynamicFacePath": "E:\\SystemDocuments\\QQ\\721011692\\nt_qq\\nt_data\\Emoji\\marketface\\203875\\094d53bd1c9ac5d35d04b08e8a6c992c", - "supportSize": [ - { - "width": 300, - "height": 300 - }, - { - "width": 200, - "height": 200 - } - ], - "apngSupportSize": null + "emojiPackageId": number, + "emojiId": string, + "key": string, } export interface VideoElement { @@ -420,6 +393,7 @@ export interface RawMessage { arkElement: ArkElement; grayTipElement: GrayTipElement; faceElement: FaceElement; + mfaceElement: MarketFaceElement; videoElement: VideoElement; fileElement: FileElement; marketFaceElement: MarketFaceElement; diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 5cda5e5..092e787 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -155,7 +155,9 @@ export async function createSendElements(messageData: OB11MessageData[], target: } } break; - + case OB11MessageDataType.mface: { + sendElements.push(SendMsgElementConstructor.mface(sendMsg.data.emojiPackageId, sendMsg.data.emojiId, sendMsg.data.key)) + } case OB11MessageDataType.image: case OB11MessageDataType.file: case OB11MessageDataType.video: diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index 7425a23..3cc88bc 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -224,6 +224,10 @@ export class OB11Constructor { // const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000` const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${md5}/raw300.gif`; message_data["data"]["url"] = url; + message_data["data"]["emoji_id"] = element.marketFaceElement.emojiId + message_data["data"]["emoji_package_id"] = String(element.marketFaceElement.emojiPackageId) + message_data["data"]["key"] = element.marketFaceElement.key + } else if (element.markdownElement) { message_data["type"] = OB11MessageDataType.markdown; message_data["data"]["data"] = element.markdownElement.content; diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index b4a9540..2d88e31 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -125,7 +125,9 @@ export enum OB11MessageDataType { export interface OB11MessageMFace{ type: OB11MessageDataType.mface, data: { - text: string + emojiPackageId: number, + emojiId: string, + key: string } } From dc1e1ea21b4ef2d9fb73525c1f3d9171eaf0935e Mon Sep 17 00:00:00 2001 From: student_2333 Date: Tue, 30 Apr 2024 11:28:24 +0800 Subject: [PATCH 2/3] style: reformat --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/workflows/publish.yml | 54 +- .prettierrc.yml | 4 + README.md | 21 +- electron.vite.config.ts | 140 ++- manifest.json | 8 +- package.json | 7 +- scripts/gen-version.ts | 18 +- scripts/test/test_db.ts | 22 +- src/common/config.ts | 165 +-- src/common/data.ts | 153 ++- src/common/db.ts | 516 ++++---- src/common/server/http.ts | 213 ++-- src/common/server/websocket.ts | 157 ++- src/common/types.ts | 4 +- src/common/utils/audio.ts | 251 ++-- src/common/utils/file.ts | 437 ++++--- src/common/utils/helper.ts | 83 +- src/common/utils/index.ts | 18 +- src/common/utils/log.ts | 58 +- src/common/utils/qqlevel.ts | 8 +- src/common/utils/qqpkg.ts | 14 +- src/common/utils/upgrade.ts | 163 ++- src/common/utils/video.ts | 152 +-- src/main/ipcsend.ts | 17 +- src/main/main.ts | 930 ++++++++------- src/main/setConfig.ts | 121 +- src/ntqqapi/api/file.ts | 350 +++--- src/ntqqapi/api/friend.ts | 117 +- src/ntqqapi/api/group.ts | 446 +++---- src/ntqqapi/api/index.ts | 14 +- src/ntqqapi/api/msg.ts | 457 +++---- src/ntqqapi/api/user.ts | 299 ++--- src/ntqqapi/api/webapi.ts | 144 ++- src/ntqqapi/api/window.ts | 89 +- src/ntqqapi/constructor.ts | 268 ++--- src/ntqqapi/external/crychic/index.ts | 85 +- src/ntqqapi/external/moehook/hook.ts | 19 +- src/ntqqapi/hook.ts | 427 ++++--- src/ntqqapi/ntcall.ts | 365 +++--- src/ntqqapi/types/cache.ts | 89 +- src/ntqqapi/types/group.ts | 96 +- src/ntqqapi/types/index.ts | 12 +- src/ntqqapi/types/msg.ts | 624 +++++----- src/ntqqapi/types/notify.ts | 89 +- src/ntqqapi/types/user.ts | 128 +- src/onebot11/action/BaseAction.ts | 74 +- src/onebot11/action/OB11Response.ts | 46 +- src/onebot11/action/file/GetFile.ts | 184 ++- src/onebot11/action/file/GetImage.ts | 9 +- src/onebot11/action/file/GetRecord.ts | 18 +- src/onebot11/action/go-cqhttp/DownloadFile.ts | 121 +- .../action/go-cqhttp/GetForwardMsg.ts | 70 +- .../action/go-cqhttp/GetGroupMsgHistory.ts | 67 +- .../action/go-cqhttp/GetStrangerInfo.ts | 31 +- .../action/go-cqhttp/SendForwardMsg.ts | 22 +- src/onebot11/action/go-cqhttp/UploadFile.ts | 72 +- src/onebot11/action/group/GetGroupInfo.ts | 28 +- src/onebot11/action/group/GetGroupList.ts | 27 +- .../action/group/GetGroupMemberInfo.ts | 51 +- .../action/group/GetGroupMemberList.ts | 39 +- src/onebot11/action/group/GetGuildList.ts | 14 +- src/onebot11/action/group/SendGroupMsg.ts | 23 +- .../action/group/SetGroupAddRequest.ts | 41 +- src/onebot11/action/group/SetGroupAdmin.ts | 40 +- src/onebot11/action/group/SetGroupBan.ts | 35 +- src/onebot11/action/group/SetGroupCard.ts | 32 +- src/onebot11/action/group/SetGroupKick.ts | 32 +- src/onebot11/action/group/SetGroupLeave.ts | 30 +- src/onebot11/action/group/SetGroupName.ts | 23 +- src/onebot11/action/group/SetGroupWholeBan.ts | 24 +- src/onebot11/action/index.ts | 89 +- src/onebot11/action/llonebot/Config.ts | 29 +- src/onebot11/action/llonebot/Debug.ts | 64 +- .../action/llonebot/GetGroupAddRequest.ts | 52 +- src/onebot11/action/llonebot/SetQQAvatar.ts | 70 +- src/onebot11/action/msg/DeleteMsg.ts | 31 +- src/onebot11/action/msg/ForwardSingleMsg.ts | 35 +- src/onebot11/action/msg/GetMsg.ts | 43 +- src/onebot11/action/msg/MarkMsgAsRead.ts | 20 +- src/onebot11/action/msg/SendMsg.ts | 1058 +++++++++-------- src/onebot11/action/msg/SendPrivateMsg.ts | 18 +- src/onebot11/action/msg/SetMsgEmojiLike.ts | 43 +- src/onebot11/action/system/CanSendImage.ts | 10 +- src/onebot11/action/system/CanSendRecord.ts | 18 +- src/onebot11/action/system/CleanCache.ts | 170 ++- src/onebot11/action/system/GetLoginInfo.ts | 21 +- src/onebot11/action/system/GetStatus.ts | 23 +- src/onebot11/action/system/GetVersionInfo.ts | 24 +- src/onebot11/action/types.ts | 116 +- src/onebot11/action/user/GetCookie.ts | 20 +- src/onebot11/action/user/GetFriendList.ts | 21 +- src/onebot11/action/user/SendLike.ts | 56 +- .../action/user/SetFriendAddRequest.ts | 26 +- src/onebot11/constructor.ts | 410 ++++--- src/onebot11/cqcode.ts | 95 +- src/onebot11/event/OB11BaseEvent.ts | 21 +- .../event/message/OB11BaseMessageEvent.ts | 6 +- src/onebot11/event/meta/OB11BaseMetaEvent.ts | 8 +- src/onebot11/event/meta/OB11HeartbeatEvent.ts | 28 +- src/onebot11/event/meta/OB11LifeCycleEvent.ts | 22 +- .../event/notice/OB11BaseNoticeEvent.ts | 6 +- .../notice/OB11FriendRecallNoticeEvent.ts | 20 +- .../event/notice/OB11GroupAdminNoticeEvent.ts | 8 +- .../event/notice/OB11GroupBanEvent.ts | 26 +- .../event/notice/OB11GroupCardEvent.ts | 23 +- .../event/notice/OB11GroupDecreaseEvent.ts | 24 +- .../event/notice/OB11GroupIncreaseEvent.ts | 24 +- .../event/notice/OB11GroupNoticeEvent.ts | 8 +- .../notice/OB11GroupRecallNoticeEvent.ts | 24 +- .../event/notice/OB11GroupTitleEvent.ts | 21 +- .../notice/OB11GroupUploadNoticeEvent.ts | 30 +- .../event/notice/OB11MsgEmojiLikeEvent.ts | 20 +- src/onebot11/event/notice/OB11PokeEvent.ts | 46 +- .../event/request/OB11FriendRequest.ts | 17 +- .../event/request/OB11GroupRequest.ts | 17 +- src/onebot11/server/http.ts | 75 +- src/onebot11/server/postOB11Event.ts | 301 ++--- src/onebot11/server/ws/ReverseWebsocket.ts | 240 ++-- src/onebot11/server/ws/WebsocketServer.ts | 121 +- src/onebot11/server/ws/reply.ts | 30 +- src/onebot11/types.ts | 372 +++--- src/preload.ts | 68 +- src/renderer/components/button.ts | 5 +- src/renderer/components/index.ts | 10 +- src/renderer/components/item.ts | 15 +- src/renderer/components/list.ts | 14 +- src/renderer/components/option.ts | 5 +- src/renderer/components/select.ts | 121 +- src/renderer/components/switch.ts | 9 +- src/renderer/icon.ts | 2 +- src/renderer/index.ts | 716 +++++------ src/renderer/style.css | 6 +- src/version.ts | 2 +- tsconfig.json | 14 +- 135 files changed, 7149 insertions(+), 6945 deletions(-) create mode 100644 .prettierrc.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index e8ca336..776c15b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -78,4 +78,4 @@ body: attributes: label: OneBot 客户端运行日志 description: 粘贴 OneBot 客户端的相关日志内容到此处 - render: shell \ No newline at end of file + render: shell diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 724f87b..35efece 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,39 +1,39 @@ -name: "publish" +name: 'publish' on: push: tags: - - "v*" + - 'v*' jobs: build-and-publish: runs-on: ubuntu-latest steps: - - name: checkout - uses: actions/checkout@v3 + - name: checkout + uses: actions/checkout@v3 - - name: setup node - uses: actions/setup-node@v2 - with: - node-version: 18 + - name: setup node + uses: actions/setup-node@v2 + with: + node-version: 18 - - name: install dependenies - run: | - export ELECTRON_SKIP_BINARY_DOWNLOAD=1 - npm install + - name: install dependenies + run: | + export ELECTRON_SKIP_BINARY_DOWNLOAD=1 + npm install - - name: build - run: npm run build + - name: build + run: npm run build - - name: zip - run: | - sudo apt install zip -y - cp manifest.json ./dist/manifest.json - cd ./dist/ - zip -r ../LLOneBot.zip ./* - - - name: publish - uses: ncipollo/release-action@v1 - with: - artifacts: "LLOneBot.zip" - draft: true - token: ${{ secrets.RELEASE_TOKEN }} \ No newline at end of file + - name: zip + run: | + sudo apt install zip -y + cp manifest.json ./dist/manifest.json + cd ./dist/ + zip -r ../LLOneBot.zip ./* + + - name: publish + uses: ncipollo/release-action@v1 + with: + artifacts: 'LLOneBot.zip' + draft: true + token: ${{ secrets.RELEASE_TOKEN }} diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..cb7c5cc --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,4 @@ +semi: false +singleQuote: true +trailingComma: all +printWidth: 120 diff --git a/README.md b/README.md index 6b70403..8388e4a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - # LLOneBot API + LiteLoaderQQNT插件,使你的NTQQ支持OneBot11协议进行QQ机器人开发 > [!CAUTION]\ @@ -23,11 +23,11 @@ TG群: - ## TODO + - [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用 - [x] 支持正、反向websocket(感谢@disymayufei的PR) -- [x] 转发消息记录 +- [x] 转发消息记录 - [x] 好友点赞api - [x] 群管理功能,禁言、踢人,改群名片等 - [x] 视频消息 @@ -39,17 +39,20 @@ TG群: - [ ] 框架对接文档 ## onebot11文档 + ## Stargazers over time + [![Stargazers over time](https://starchart.cc/LLOneBot/LLOneBot.svg?variant=adaptive)](https://starchart.cc/LLOneBot/LLOneBot) - ## 鸣谢 -* [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) -* [LLAPI](https://github.com/Night-stars-1/LiteLoaderQQNT-Plugin-LLAPI) -* [chronocat](https://github.com/chrononeko/chronocat/) -* [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot) + +- [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) +- [LLAPI](https://github.com/Night-stars-1/LiteLoaderQQNT-Plugin-LLAPI) +- [chronocat](https://github.com/chrononeko/chronocat/) +- [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot) ## 友链 -* [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) 一款用C#实现的NTQQ纯协议跨平台QQ机器人框架 + +- [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) 一款用C#实现的NTQQ纯协议跨平台QQ机器人框架 diff --git a/electron.vite.config.ts b/electron.vite.config.ts index deb8dc0..4e8cce1 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -1,74 +1,86 @@ -import cp from 'vite-plugin-cp'; -import "./scripts/gen-version" +import cp from 'vite-plugin-cp' +import './scripts/gen-version' -const external = ["silk-wasm", "ws", - "level", "classic-level", "abstract-level", "level-supports", "level-transcoder", - "module-error", "catering", "node-gyp-build"]; +const external = [ + 'silk-wasm', + 'ws', + 'level', + 'classic-level', + 'abstract-level', + 'level-supports', + 'level-transcoder', + 'module-error', + 'catering', + 'node-gyp-build', +] function genCpModule(module: string) { - return {src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false} + return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false } } let config = { - main: { - build: { - outDir: "dist/main", - emptyOutDir: true, - lib: { - formats: ["cjs"], - entry: {"main": "src/main/main.ts"}, - }, - rollupOptions: { - external, - input: "src/main/main.ts", - } - }, - resolve: { - alias: { - './lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg' - }, - }, - plugins: [cp({ - targets: [ - ...external.map(genCpModule), - {src: './manifest.json', dest: 'dist'}, {src: './icon.jpg', dest: 'dist'}, - {src: './src/ntqqapi/external/crychic/crychic-win32-x64.node', dest: 'dist/main/'}, - {src: './src/ntqqapi/external/moehook/MoeHook-win32-x64.node', dest: 'dist/main/', rename: 'MoeHook.node'}, - ] - })] + main: { + build: { + outDir: 'dist/main', + emptyOutDir: true, + lib: { + formats: ['cjs'], + entry: { main: 'src/main/main.ts' }, + }, + rollupOptions: { + external, + input: 'src/main/main.ts', + }, }, - preload: { - // vite config options - build: { - outDir: "dist/preload", - emptyOutDir: true, - lib: { - formats: ["cjs"], - entry: {"preload": "src/preload.ts"}, - }, - rollupOptions: { - // external: externalAll, - input: "src/preload.ts", - } - }, - resolve: {} + resolve: { + alias: { + './lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg', + }, }, - renderer: { - // vite config options - build: { - outDir: "dist/renderer", - emptyOutDir: true, - lib: { - formats: ["es"], - entry: {"renderer": "src/renderer/index.ts"}, - }, - rollupOptions: { - // external: externalAll, - input: "src/renderer/index.ts", - } - }, - resolve: {} - } + plugins: [ + cp({ + targets: [ + ...external.map(genCpModule), + { src: './manifest.json', dest: 'dist' }, + { src: './icon.jpg', dest: 'dist' }, + { src: './src/ntqqapi/external/crychic/crychic-win32-x64.node', dest: 'dist/main/' }, + { src: './src/ntqqapi/external/moehook/MoeHook-win32-x64.node', dest: 'dist/main/', rename: 'MoeHook.node' }, + ], + }), + ], + }, + preload: { + // vite config options + build: { + outDir: 'dist/preload', + emptyOutDir: true, + lib: { + formats: ['cjs'], + entry: { preload: 'src/preload.ts' }, + }, + rollupOptions: { + // external: externalAll, + input: 'src/preload.ts', + }, + }, + resolve: {}, + }, + renderer: { + // vite config options + build: { + outDir: 'dist/renderer', + emptyOutDir: true, + lib: { + formats: ['es'], + entry: { renderer: 'src/renderer/index.ts' }, + }, + rollupOptions: { + // external: externalAll, + input: 'src/renderer/index.ts', + }, + }, + resolve: {}, + }, } -export default config; \ No newline at end of file +export default config diff --git a/manifest.json b/manifest.json index b337eeb..d49a0d4 100644 --- a/manifest.json +++ b/manifest.json @@ -20,14 +20,10 @@ "name": "LLOneBot.zip" } }, - "platform": [ - "win32", - "linux", - "darwin" - ], + "platform": ["win32", "linux", "darwin"], "injects": { "renderer": "./renderer/index.js", "main": "./main/main.cjs", "preload": "./preload/preload.cjs" } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 8320897..ceb514f 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ "build-mac": "npm run build && npm run deploy-mac", "deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/", "build-win": "npm run build && npm run deploy-win", - "deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"" + "deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"", + "format": "prettier -cw ." }, "author": "", - "license": "ISC", + "license": "MIT", "dependencies": { "compressing": "^1.10.0", "cors": "^2.8.5", @@ -37,7 +38,7 @@ "electron-vite": "^2.0.0", "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-n": "^15.0.0 || ^16.0.0", "eslint-plugin-promise": "^6.0.0", "ts-node": "^10.9.2", "typescript": "*", diff --git a/scripts/gen-version.ts b/scripts/gen-version.ts index ca10da5..b702dee 100644 --- a/scripts/gen-version.ts +++ b/scripts/gen-version.ts @@ -4,19 +4,19 @@ import { version } from '../src/version' const manifestPath = path.join(__dirname, '../manifest.json') -function readManifest (): any { - if (fs.existsSync(manifestPath)) { - return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) - } +function readManifest(): any { + if (fs.existsSync(manifestPath)) { + return JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) + } } -function writeManifest (manifest: any) { - fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)) +function writeManifest(manifest: any) { + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)) } const manifest = readManifest() if (version !== manifest.version) { - manifest.version = version - manifest.name = `LLOneBot v${version}` - writeManifest(manifest) + manifest.version = version + manifest.name = `LLOneBot v${version}` + writeManifest(manifest) } diff --git a/scripts/test/test_db.ts b/scripts/test/test_db.ts index f63f109..66681fd 100644 --- a/scripts/test/test_db.ts +++ b/scripts/test/test_db.ts @@ -1,17 +1,17 @@ -import {Level} from "level" +import { Level } from 'level' -const db = new Level(process.env["level_db_path"], {valueEncoding: 'json'}); +const db = new Level(process.env['level_db_path'], { valueEncoding: 'json' }) async function getGroupNotify() { - let keys = await db.keys().all(); - let result = [] - for (const key of keys) { - // console.log(key) - if (key.startsWith("group_notify_")) { - result.push(key) - } + let keys = await db.keys().all() + let result = [] + for (const key of keys) { + // console.log(key) + if (key.startsWith('group_notify_')) { + result.push(key) } - return result + } + return result } -getGroupNotify().then(console.log) \ No newline at end of file +getGroupNotify().then(console.log) diff --git a/src/common/config.ts b/src/common/config.ts index 6ae2723..a4d73fd 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -1,99 +1,102 @@ -import fs from "fs"; -import fsPromise from "fs/promises"; -import {Config, OB11Config} from './types'; +import fs from 'fs' +import fsPromise from 'fs/promises' +import { Config, OB11Config } from './types' -import {mergeNewProperties} from "./utils/helper"; -import path from "node:path"; -import {selfInfo} from "./data"; -import {DATA_DIR} from "./utils"; +import { mergeNewProperties } from './utils/helper' +import path from 'node:path' +import { selfInfo } from './data' +import { DATA_DIR } from './utils' -export const HOOK_LOG = false; +export const HOOK_LOG = false -export const ALLOW_SEND_TEMP_MSG = false; +export const ALLOW_SEND_TEMP_MSG = false export class ConfigUtil { - private readonly configPath: string; - private config: Config | null = null; + private readonly configPath: string + private config: Config | null = null - constructor(configPath: string) { - this.configPath = configPath; + constructor(configPath: string) { + this.configPath = configPath + } + + getConfig(cache = true) { + if (this.config && cache) { + return this.config } - getConfig(cache = true) { - if (this.config && cache) { - return this.config; - } + return this.reloadConfig() + } - return this.reloadConfig(); + reloadConfig(): Config { + let ob11Default: OB11Config = { + httpPort: 3000, + httpHosts: [], + httpSecret: '', + wsPort: 3001, + wsHosts: [], + enableHttp: true, + enableHttpPost: true, + enableWs: true, + enableWsReverse: false, + messagePostFormat: 'array', + enableHttpHeart: false, + } + let defaultConfig: Config = { + ob11: ob11Default, + heartInterval: 60000, + token: '', + enableLocalFile2Url: false, + debug: false, + log: false, + reportSelfMessage: false, + autoDeleteFile: false, + autoDeleteFileSecond: 60, + enablePoke: false, } - reloadConfig(): Config { - let ob11Default: OB11Config = { - httpPort: 3000, - httpHosts: [], - httpSecret: "", - wsPort: 3001, - wsHosts: [], - enableHttp: true, - enableHttpPost: true, - enableWs: true, - enableWsReverse: false, - messagePostFormat: "array", - enableHttpHeart: false - } - let defaultConfig: Config = { - ob11: ob11Default, - heartInterval: 60000, - token: "", - enableLocalFile2Url: false, - debug: false, - log: false, - reportSelfMessage: false, - autoDeleteFile: false, - autoDeleteFileSecond: 60, - enablePoke: false - }; - - if (!fs.existsSync(this.configPath)) { - this.config = defaultConfig; - return this.config; - } else { - const data = fs.readFileSync(this.configPath, "utf-8"); - let jsonData: Config = defaultConfig; - try { - jsonData = JSON.parse(data) - } catch (e) { - this.config = defaultConfig; - return this.config; - } - mergeNewProperties(defaultConfig, jsonData); - this.checkOldConfig(jsonData.ob11, jsonData, "httpPort", "http"); - this.checkOldConfig(jsonData.ob11, jsonData, "httpHosts", "hosts"); - this.checkOldConfig(jsonData.ob11, jsonData, "wsPort", "wsPort"); - // console.log("get config", jsonData); - this.config = jsonData; - return this.config; - } + if (!fs.existsSync(this.configPath)) { + this.config = defaultConfig + return this.config + } else { + const data = fs.readFileSync(this.configPath, 'utf-8') + let jsonData: Config = defaultConfig + try { + jsonData = JSON.parse(data) + } catch (e) { + this.config = defaultConfig + return this.config + } + mergeNewProperties(defaultConfig, jsonData) + this.checkOldConfig(jsonData.ob11, jsonData, 'httpPort', 'http') + this.checkOldConfig(jsonData.ob11, jsonData, 'httpHosts', 'hosts') + this.checkOldConfig(jsonData.ob11, jsonData, 'wsPort', 'wsPort') + // console.log("get config", jsonData); + this.config = jsonData + return this.config } + } - setConfig(config: Config) { - this.config = config; - fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8") - } + setConfig(config: Config) { + this.config = config + fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8') + } - private checkOldConfig(currentConfig: Config | OB11Config, - oldConfig: Config | OB11Config, - currentKey: string, oldKey: string) { - // 迁移旧的配置到新配置,避免用户重新填写配置 - const oldValue = oldConfig[oldKey]; - if (oldValue) { - currentConfig[currentKey] = oldValue; - delete oldConfig[oldKey]; - } + private checkOldConfig( + currentConfig: Config | OB11Config, + oldConfig: Config | OB11Config, + currentKey: string, + oldKey: string, + ) { + // 迁移旧的配置到新配置,避免用户重新填写配置 + const oldValue = oldConfig[oldKey] + if (oldValue) { + currentConfig[currentKey] = oldValue + delete oldConfig[oldKey] } + } } export function getConfigUtil() { - const configFilePath = path.join(DATA_DIR, `config_${selfInfo.uin}.json`) - return new ConfigUtil(configFilePath) -} \ No newline at end of file + const configFilePath = path.join(DATA_DIR, `config_${selfInfo.uin}.json`) + return new ConfigUtil(configFilePath) +} diff --git a/src/common/data.ts b/src/common/data.ts index 982c980..8c03020 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -1,113 +1,104 @@ -import { - type Friend, - type FriendRequest, - type Group, - type GroupMember, - type SelfInfo -} from '../ntqqapi/types' -import {type FileCache, type LLOneBotError} from './types' -import {NTQQGroupApi} from "../ntqqapi/api/group"; -import {log} from "./utils/log"; -import {isNumeric} from "./utils/helper"; +import { type Friend, type FriendRequest, type Group, type GroupMember, type SelfInfo } from '../ntqqapi/types' +import { type FileCache, type LLOneBotError } from './types' +import { NTQQGroupApi } from '../ntqqapi/api/group' +import { log } from './utils/log' +import { isNumeric } from './utils/helper' export const selfInfo: SelfInfo = { - uid: '', - uin: '', - nick: '', - online: true + uid: '', + uin: '', + nick: '', + online: true, } export let groups: Group[] = [] export let friends: Friend[] = [] export let friendRequests: Map = new Map() export const llonebotError: LLOneBotError = { - ffmpegError: '', - httpServerError: '', - wsServerError: '', - otherError: 'LLOnebot未能正常启动,请检查日志查看错误' + ffmpegError: '', + httpServerError: '', + wsServerError: '', + otherError: 'LLOnebot未能正常启动,请检查日志查看错误', } - export async function getFriend(uinOrUid: string): Promise { - let filterKey = isNumeric(uinOrUid.toString()) ? "uin" : "uid" - let filterValue = uinOrUid - let friend = friends.find(friend => friend[filterKey] === filterValue.toString()) - // if (!friend) { - // try { - // friends = (await NTQQApi.getFriends(true)) - // friend = friends.find(friend => friend[filterKey] === filterValue.toString()) - // } catch (e) { - // // log("刷新好友列表失败", e.stack.toString()) - // } - // } - return friend + let filterKey = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid' + let filterValue = uinOrUid + let friend = friends.find((friend) => friend[filterKey] === filterValue.toString()) + // if (!friend) { + // try { + // friends = (await NTQQApi.getFriends(true)) + // friend = friends.find(friend => friend[filterKey] === filterValue.toString()) + // } catch (e) { + // // log("刷新好友列表失败", e.stack.toString()) + // } + // } + return friend } export async function getGroup(qq: string): Promise { - let group = groups.find(group => group.groupCode === qq.toString()) - if (!group) { - try { - const _groups = await NTQQGroupApi.getGroups(true); - group = _groups.find(group => group.groupCode === qq.toString()) - if (group) { - groups.push(group) - } - } catch (e) { - } - - } - return group + let group = groups.find((group) => group.groupCode === qq.toString()) + if (!group) { + try { + const _groups = await NTQQGroupApi.getGroups(true) + group = _groups.find((group) => group.groupCode === qq.toString()) + if (group) { + groups.push(group) + } + } catch (e) {} + } + return group } export function deleteGroup(groupCode: string) { - const groupIndex = groups.findIndex(group => group.groupCode === groupCode.toString()) - // log(groups, groupCode, groupIndex); - if (groupIndex !== -1) { - log("删除群", groupCode); - groups.splice(groupIndex, 1) - } + const groupIndex = groups.findIndex((group) => group.groupCode === groupCode.toString()) + // log(groups, groupCode, groupIndex); + if (groupIndex !== -1) { + log('删除群', groupCode) + groups.splice(groupIndex, 1) + } } export async function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number) { - groupQQ = groupQQ.toString() - memberUinOrUid = memberUinOrUid.toString() - const group = await getGroup(groupQQ) - if (group) { - const filterKey = isNumeric(memberUinOrUid) ? "uin" : "uid" - const filterValue = memberUinOrUid - let filterFunc: (member: GroupMember) => boolean = member => member[filterKey] === filterValue - let member = group.members?.find(filterFunc) - if (!member) { - try { - const _members = await NTQQGroupApi.getGroupMembers(groupQQ) - if (_members.length > 0) { - group.members = _members - } - } catch (e) { - // log("刷新群成员列表失败", e.stack.toString()) - } - - member = group.members?.find(filterFunc) + groupQQ = groupQQ.toString() + memberUinOrUid = memberUinOrUid.toString() + const group = await getGroup(groupQQ) + if (group) { + const filterKey = isNumeric(memberUinOrUid) ? 'uin' : 'uid' + const filterValue = memberUinOrUid + let filterFunc: (member: GroupMember) => boolean = (member) => member[filterKey] === filterValue + let member = group.members?.find(filterFunc) + if (!member) { + try { + const _members = await NTQQGroupApi.getGroupMembers(groupQQ) + if (_members.length > 0) { + group.members = _members } - return member + } catch (e) { + // log("刷新群成员列表失败", e.stack.toString()) + } + + member = group.members?.find(filterFunc) } - return null + return member + } + return null } export async function refreshGroupMembers(groupQQ: string) { - const group = groups.find(group => group.groupCode === groupQQ) - if (group) { - group.members = await NTQQGroupApi.getGroupMembers(groupQQ) - } + const group = groups.find((group) => group.groupCode === groupQQ) + if (group) { + group.members = await NTQQGroupApi.getGroupMembers(groupQQ) + } } export const uidMaps: Record = {} // 一串加密的字符串(uid) -> qq号 export function getUidByUin(uin: string) { - for (const uid in uidMaps) { - if (uidMaps[uid] === uin) { - return uid - } + for (const uid in uidMaps) { + if (uidMaps[uid] === uin) { + return uid } + } } -export let tempGroupCodeMap: Record = {} // peerUid => 群号 \ No newline at end of file +export let tempGroupCodeMap: Record = {} // peerUid => 群号 diff --git a/src/common/db.ts b/src/common/db.ts index 5038c46..e60f4cc 100644 --- a/src/common/db.ts +++ b/src/common/db.ts @@ -1,277 +1,275 @@ -import {Level} from "level"; -import {type GroupNotify, RawMessage} from "../ntqqapi/types"; -import {DATA_DIR} from "./utils"; -import {selfInfo} from "./data"; -import {FileCache} from "./types"; -import {log} from "./utils/log"; +import { Level } from 'level' +import { type GroupNotify, RawMessage } from '../ntqqapi/types' +import { DATA_DIR } from './utils' +import { selfInfo } from './data' +import { FileCache } from './types' +import { log } from './utils/log' -type ReceiveTempUinMap = Record; +type ReceiveTempUinMap = Record class DBUtil { - public readonly DB_KEY_PREFIX_MSG_ID = "msg_id_"; - public readonly DB_KEY_PREFIX_MSG_SHORT_ID = "msg_short_id_"; - public readonly DB_KEY_PREFIX_MSG_SEQ_ID = "msg_seq_id_"; - public readonly DB_KEY_PREFIX_FILE = "file_"; - public readonly DB_KEY_PREFIX_GROUP_NOTIFY = "group_notify_"; - private readonly DB_KEY_RECEIVED_TEMP_UIN_MAP = "received_temp_uin_map"; - public db: Level; - public cache: Record = {} // : RawMessage - private currentShortId: number; + public readonly DB_KEY_PREFIX_MSG_ID = 'msg_id_' + public readonly DB_KEY_PREFIX_MSG_SHORT_ID = 'msg_short_id_' + public readonly DB_KEY_PREFIX_MSG_SEQ_ID = 'msg_seq_id_' + public readonly DB_KEY_PREFIX_FILE = 'file_' + public readonly DB_KEY_PREFIX_GROUP_NOTIFY = 'group_notify_' + private readonly DB_KEY_RECEIVED_TEMP_UIN_MAP = 'received_temp_uin_map' + public db: Level + public cache: Record = {} // : RawMessage + private currentShortId: number - /* - * 数据库结构 - * msg_id_101231230999: {} // 长id: RawMessage - * msg_short_id_1: 101231230999 // 短id: 长id - * msg_seq_id_1: 101231230999 // 序列id: 长id - * file_7827DBAFJFW2323.png: {} // 文件名: FileCache - * */ + /* + * 数据库结构 + * msg_id_101231230999: {} // 长id: RawMessage + * msg_short_id_1: 101231230999 // 短id: 长id + * msg_seq_id_1: 101231230999 // 序列id: 长id + * file_7827DBAFJFW2323.png: {} // 文件名: FileCache + * */ - constructor() { - let initCount = 0; - new Promise((resolve, reject) => { - const initDB = () => { - initCount++; - // if (initCount > 50) { - // return reject("init db fail") - // } + constructor() { + let initCount = 0 + new Promise((resolve, reject) => { + const initDB = () => { + initCount++ + // if (initCount > 50) { + // return reject("init db fail") + // } - try { - if (!selfInfo.uin) { - setTimeout(initDB, 300); - return - } - const DB_PATH = DATA_DIR + `/msg_${selfInfo.uin}`; - this.db = new Level(DB_PATH, {valueEncoding: 'json'}); - console.log("llonebot init db success") - resolve(null) - } catch (e) { - console.log("init db fail", e.stack.toString()) - setTimeout(initDB, 300); - } - } - initDB(); - }).then() - - const expiredMilliSecond = 1000 * 60 * 60; - - setInterval(() => { - // this.cache = {} - // 清理时间较久的缓存 - const now = Date.now() - for (let key in this.cache) { - let message: RawMessage = this.cache[key] as RawMessage; - if (message?.msgTime){ - if ((now - (parseInt(message.msgTime) * 1000)) > expiredMilliSecond) { - delete this.cache[key] - // log("clear cache", key, message.msgTime); - } - } - } - }, expiredMilliSecond) - } - - public async getReceivedTempUinMap(): Promise { - try{ - this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)); - }catch (e) { - } - return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap; - } - public setReceivedTempUinMap(data: ReceiveTempUinMap) { - this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = data; - this.db.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then(); - } - private addCache(msg: RawMessage) { - const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId - const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + msg.msgShortId - const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq - this.cache[longIdKey] = this.cache[shortIdKey] = msg - } - - public clearCache() { - this.cache = {} - } - - async getMsgByShortId(shortMsgId: number): Promise { - const shortMsgIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId; - if (this.cache[shortMsgIdKey]) { - // log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey]) - return this.cache[shortMsgIdKey] as RawMessage - } try { - const longId = await this.db.get(shortMsgIdKey); - const msg = await this.getMsgByLongId(longId) - this.addCache(msg) - return msg - } catch (e) { - log("getMsgByShortId db error", e.stack.toString()) - } - } - - async getMsgByLongId(longId: string): Promise { - const longIdKey = this.DB_KEY_PREFIX_MSG_ID + longId; - if (this.cache[longIdKey]) { - return this.cache[longIdKey] as RawMessage - } - try { - const data = await this.db.get(longIdKey) - const msg = JSON.parse(data) - this.addCache(msg) - return msg - } catch (e) { - // log("getMsgByLongId db error", e.stack.toString()) - } - } - - async getMsgBySeqId(seqId: string): Promise { - const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + seqId; - if (this.cache[seqIdKey]) { - return this.cache[seqIdKey] as RawMessage - } - try { - const longId = await this.db.get(seqIdKey); - const msg = await this.getMsgByLongId(longId) - this.addCache(msg) - return msg - } catch (e) { - log("getMsgBySeqId db error", e.stack.toString()) - } - } - - async addMsg(msg: RawMessage) { - // 有则更新,无则添加 - // log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId); - const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId - let existMsg = this.cache[longIdKey] as RawMessage - if (!existMsg) { - try { - existMsg = await this.getMsgByLongId(msg.msgId) - } catch (e) { - // log("addMsg getMsgByLongId error", e.stack.toString()) - } - } - if (existMsg) { - // log("消息已存在", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId) - this.updateMsg(msg).then() - return existMsg.msgShortId - } - this.addCache(msg); - - const shortMsgId = await this.genMsgShortId(); - const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId; - const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq; - msg.msgShortId = shortMsgId; - // log("新增消息记录", msg.msgId) - this.db.put(shortIdKey, msg.msgId).then().catch(); - this.db.put(longIdKey, JSON.stringify(msg)).then().catch(); - try { - await this.db.get(seqIdKey) - } catch (e) { - // log("新的seqId", seqIdKey) - this.db.put(seqIdKey, msg.msgId).then().catch(); - } - if (!this.cache[seqIdKey]) { - this.cache[seqIdKey] = msg; - } - return shortMsgId - // log(`消息入库 ${seqIdKey}: ${msg.msgId}, ${shortMsgId}: ${msg.msgId}`); - } - - async updateMsg(msg: RawMessage) { - const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId - let existMsg = this.cache[longIdKey] as RawMessage - if (!existMsg) { - try { - existMsg = await this.getMsgByLongId(msg.msgId) - } catch (e) { - existMsg = msg - } - } - - Object.assign(existMsg, msg) - this.db.put(longIdKey, JSON.stringify(existMsg)).then().catch(); - const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg.msgShortId; - const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq; - if (!this.cache[seqIdKey]) { - this.cache[seqIdKey] = existMsg; - } - this.db.put(shortIdKey, msg.msgId).then().catch(); - try { - await this.db.get(seqIdKey) - } catch (e) { - this.db.put(seqIdKey, msg.msgId).then().catch(); - // log("更新seqId error", e.stack, seqIdKey); - } - // log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId); - } - - - private async genMsgShortId(): Promise { - const key = "msg_current_short_id"; - if (this.currentShortId === undefined) { - try { - let id: string = await this.db.get(key); - this.currentShortId = parseInt(id); - } catch (e) { - this.currentShortId = -2147483640 - } - } - - this.currentShortId++; - this.db.put(key, this.currentShortId.toString()).then().catch(); - return this.currentShortId; - } - - async addFileCache(fileNameOrUuid: string, data: FileCache) { - const key = this.DB_KEY_PREFIX_FILE + fileNameOrUuid; - if (this.cache[key]) { + if (!selfInfo.uin) { + setTimeout(initDB, 300) return - } - let cacheDBData = {...data} - delete cacheDBData['downloadFunc'] - this.cache[fileNameOrUuid] = data; - try { - await this.db.put(key, JSON.stringify(cacheDBData)); + } + const DB_PATH = DATA_DIR + `/msg_${selfInfo.uin}` + this.db = new Level(DB_PATH, { valueEncoding: 'json' }) + console.log('llonebot init db success') + resolve(null) } catch (e) { - log("addFileCache db error", e.stack.toString()) + console.log('init db fail', e.stack.toString()) + setTimeout(initDB, 300) } + } + initDB() + }).then() + + const expiredMilliSecond = 1000 * 60 * 60 + + setInterval(() => { + // this.cache = {} + // 清理时间较久的缓存 + const now = Date.now() + for (let key in this.cache) { + let message: RawMessage = this.cache[key] as RawMessage + if (message?.msgTime) { + if (now - parseInt(message.msgTime) * 1000 > expiredMilliSecond) { + delete this.cache[key] + // log("clear cache", key, message.msgTime); + } + } + } + }, expiredMilliSecond) + } + + public async getReceivedTempUinMap(): Promise { + try { + this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)) + } catch (e) {} + return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap + } + public setReceivedTempUinMap(data: ReceiveTempUinMap) { + this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = data + this.db.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then() + } + private addCache(msg: RawMessage) { + const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId + const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + msg.msgShortId + const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq + this.cache[longIdKey] = this.cache[shortIdKey] = msg + } + + public clearCache() { + this.cache = {} + } + + async getMsgByShortId(shortMsgId: number): Promise { + const shortMsgIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId + if (this.cache[shortMsgIdKey]) { + // log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey]) + return this.cache[shortMsgIdKey] as RawMessage + } + try { + const longId = await this.db.get(shortMsgIdKey) + const msg = await this.getMsgByLongId(longId) + this.addCache(msg) + return msg + } catch (e) { + log('getMsgByShortId db error', e.stack.toString()) + } + } + + async getMsgByLongId(longId: string): Promise { + const longIdKey = this.DB_KEY_PREFIX_MSG_ID + longId + if (this.cache[longIdKey]) { + return this.cache[longIdKey] as RawMessage + } + try { + const data = await this.db.get(longIdKey) + const msg = JSON.parse(data) + this.addCache(msg) + return msg + } catch (e) { + // log("getMsgByLongId db error", e.stack.toString()) + } + } + + async getMsgBySeqId(seqId: string): Promise { + const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + seqId + if (this.cache[seqIdKey]) { + return this.cache[seqIdKey] as RawMessage + } + try { + const longId = await this.db.get(seqIdKey) + const msg = await this.getMsgByLongId(longId) + this.addCache(msg) + return msg + } catch (e) { + log('getMsgBySeqId db error', e.stack.toString()) + } + } + + async addMsg(msg: RawMessage) { + // 有则更新,无则添加 + // log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId); + const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId + let existMsg = this.cache[longIdKey] as RawMessage + if (!existMsg) { + try { + existMsg = await this.getMsgByLongId(msg.msgId) + } catch (e) { + // log("addMsg getMsgByLongId error", e.stack.toString()) + } + } + if (existMsg) { + // log("消息已存在", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId) + this.updateMsg(msg).then() + return existMsg.msgShortId + } + this.addCache(msg) + + const shortMsgId = await this.genMsgShortId() + const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId + const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq + msg.msgShortId = shortMsgId + // log("新增消息记录", msg.msgId) + this.db.put(shortIdKey, msg.msgId).then().catch() + this.db.put(longIdKey, JSON.stringify(msg)).then().catch() + try { + await this.db.get(seqIdKey) + } catch (e) { + // log("新的seqId", seqIdKey) + this.db.put(seqIdKey, msg.msgId).then().catch() + } + if (!this.cache[seqIdKey]) { + this.cache[seqIdKey] = msg + } + return shortMsgId + // log(`消息入库 ${seqIdKey}: ${msg.msgId}, ${shortMsgId}: ${msg.msgId}`); + } + + async updateMsg(msg: RawMessage) { + const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId + let existMsg = this.cache[longIdKey] as RawMessage + if (!existMsg) { + try { + existMsg = await this.getMsgByLongId(msg.msgId) + } catch (e) { + existMsg = msg + } } - async getFileCache(fileNameOrUuid: string): Promise { - const key = this.DB_KEY_PREFIX_FILE + (fileNameOrUuid); - if (this.cache[key]) { - return this.cache[key] as FileCache - } - try { - let data = await this.db.get(key); - return JSON.parse(data); - } catch (e) { - // log("getFileCache db error", e.stack.toString()) - } + Object.assign(existMsg, msg) + this.db.put(longIdKey, JSON.stringify(existMsg)).then().catch() + const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg.msgShortId + const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq + if (!this.cache[seqIdKey]) { + this.cache[seqIdKey] = existMsg + } + this.db.put(shortIdKey, msg.msgId).then().catch() + try { + await this.db.get(seqIdKey) + } catch (e) { + this.db.put(seqIdKey, msg.msgId).then().catch() + // log("更新seqId error", e.stack, seqIdKey); + } + // log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId); + } + + private async genMsgShortId(): Promise { + const key = 'msg_current_short_id' + if (this.currentShortId === undefined) { + try { + let id: string = await this.db.get(key) + this.currentShortId = parseInt(id) + } catch (e) { + this.currentShortId = -2147483640 + } } - async addGroupNotify(notify: GroupNotify) { - const key = this.DB_KEY_PREFIX_GROUP_NOTIFY + notify.seq; - let existNotify = this.cache[key] as GroupNotify - if (existNotify) { - return - } - this.cache[key] = notify; - this.db.put(key, JSON.stringify(notify)).then().catch(); - } + this.currentShortId++ + this.db.put(key, this.currentShortId.toString()).then().catch() + return this.currentShortId + } - async getGroupNotify(seq: string): Promise { - const key = this.DB_KEY_PREFIX_GROUP_NOTIFY + seq; - if (this.cache[key]) { - return this.cache[key] as GroupNotify - } - try { - let data = await this.db.get(key); - return JSON.parse(data); - } catch (e) { - // log("getGroupNotify db error", e.stack.toString()) - } + async addFileCache(fileNameOrUuid: string, data: FileCache) { + const key = this.DB_KEY_PREFIX_FILE + fileNameOrUuid + if (this.cache[key]) { + return } + let cacheDBData = { ...data } + delete cacheDBData['downloadFunc'] + this.cache[fileNameOrUuid] = data + try { + await this.db.put(key, JSON.stringify(cacheDBData)) + } catch (e) { + log('addFileCache db error', e.stack.toString()) + } + } + + async getFileCache(fileNameOrUuid: string): Promise { + const key = this.DB_KEY_PREFIX_FILE + fileNameOrUuid + if (this.cache[key]) { + return this.cache[key] as FileCache + } + try { + let data = await this.db.get(key) + return JSON.parse(data) + } catch (e) { + // log("getFileCache db error", e.stack.toString()) + } + } + + async addGroupNotify(notify: GroupNotify) { + const key = this.DB_KEY_PREFIX_GROUP_NOTIFY + notify.seq + let existNotify = this.cache[key] as GroupNotify + if (existNotify) { + return + } + this.cache[key] = notify + this.db.put(key, JSON.stringify(notify)).then().catch() + } + + async getGroupNotify(seq: string): Promise { + const key = this.DB_KEY_PREFIX_GROUP_NOTIFY + seq + if (this.cache[key]) { + return this.cache[key] as GroupNotify + } + try { + let data = await this.db.get(key) + return JSON.parse(data) + } catch (e) { + // log("getGroupNotify db error", e.stack.toString()) + } + } } -export const dbUtil = new DBUtil(); \ No newline at end of file +export const dbUtil = new DBUtil() diff --git a/src/common/server/http.ts b/src/common/server/http.ts index 31e4067..1b1898b 100644 --- a/src/common/server/http.ts +++ b/src/common/server/http.ts @@ -1,120 +1,119 @@ -import express, { Express, Request, Response } from "express"; -import http from "http"; -import cors from 'cors'; -import { log } from "../utils/log"; -import { getConfigUtil } from "../config"; -import { llonebotError } from "../data"; +import express, { Express, Request, Response } from 'express' +import http from 'http' +import cors from 'cors' +import { log } from '../utils/log' +import { getConfigUtil } from '../config' +import { llonebotError } from '../data' type RegisterHandler = (res: Response, payload: any) => Promise export abstract class HttpServerBase { - name: string = "LLOneBot"; - private readonly expressAPP: Express; - private server: http.Server = null; + name: string = 'LLOneBot' + private readonly expressAPP: Express + private server: http.Server = null - constructor() { - this.expressAPP = express(); - // 添加 CORS 中间件 - this.expressAPP.use(cors()); - this.expressAPP.use(express.urlencoded({ extended: true, limit: "5000mb" })); - this.expressAPP.use((req, res, next) => { - // 兼容处理没有带content-type的请求 - // log("req.headers['content-type']", req.headers['content-type']) - req.headers['content-type'] = 'application/json'; - const originalJson = express.json({ limit: "5000mb" }); - // 调用原始的express.json()处理器 - originalJson(req, res, (err) => { - if (err) { - log("Error parsing JSON:", err); - return res.status(400).send("Invalid JSON"); - } - next(); - }); - }); + constructor() { + this.expressAPP = express() + // 添加 CORS 中间件 + this.expressAPP.use(cors()) + this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' })) + this.expressAPP.use((req, res, next) => { + // 兼容处理没有带content-type的请求 + // log("req.headers['content-type']", req.headers['content-type']) + req.headers['content-type'] = 'application/json' + const originalJson = express.json({ limit: '5000mb' }) + // 调用原始的express.json()处理器 + originalJson(req, res, (err) => { + if (err) { + log('Error parsing JSON:', err) + return res.status(400).send('Invalid JSON') + } + next() + }) + }) + } + + authorize(req: Request, res: Response, next: () => void) { + let serverToken = getConfigUtil().getConfig().token + let clientToken = '' + const authHeader = req.get('authorization') + if (authHeader) { + clientToken = authHeader.split('Bearer ').pop() + log('receive http header token', clientToken) + } else if (req.query.access_token) { + if (Array.isArray(req.query.access_token)) { + clientToken = req.query.access_token[0].toString() + } else { + clientToken = req.query.access_token.toString() + } + log('receive http url token', clientToken) } - authorize(req: Request, res: Response, next: () => void) { - let serverToken = getConfigUtil().getConfig().token; - let clientToken = "" - const authHeader = req.get("authorization") - if (authHeader) { - clientToken = authHeader.split("Bearer ").pop() - log("receive http header token", clientToken) - } else if (req.query.access_token) { - if (Array.isArray(req.query.access_token)) { - clientToken = req.query.access_token[0].toString(); - } else { - clientToken = req.query.access_token.toString(); - } - log("receive http url token", clientToken) - } + if (serverToken && clientToken != serverToken) { + return res.status(403).send(JSON.stringify({ message: 'token verify failed!' })) + } + next() + } - if (serverToken && clientToken != serverToken) { - return res.status(403).send(JSON.stringify({ message: 'token verify failed!' })); - } - next(); - }; + start(port: number) { + try { + this.expressAPP.get('/', (req: Request, res: Response) => { + res.send(`${this.name}已启动`) + }) + this.listen(port) + llonebotError.httpServerError = '' + } catch (e) { + log('HTTP服务启动失败', e.toString()) + llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString() + } + } - start(port: number) { - try { - this.expressAPP.get('/', (req: Request, res: Response) => { - res.send(`${this.name}已启动`); - }) - this.listen(port); - llonebotError.httpServerError = "" - } catch (e) { - log("HTTP服务启动失败", e.toString()) - llonebotError.httpServerError = "HTTP服务启动失败, " + e.toString() - } + stop() { + llonebotError.httpServerError = '' + if (this.server) { + this.server.close() + this.server = null + } + } + + restart(port: number) { + this.stop() + this.start(port) + } + + abstract handleFailed(res: Response, payload: any, err: any): void + + registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) { + if (!url.startsWith('/')) { + url = '/' + url } - stop() { - llonebotError.httpServerError = "" - if (this.server) { - this.server.close() - this.server = null; - } + if (!this.expressAPP[method]) { + const err = `${this.name} register router failed,${method} not exist` + log(err) + throw err } + this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => { + let payload = req.body + if (method == 'get') { + payload = req.query + } else if (req.query) { + payload = { ...req.query, ...req.body } + } + log('收到http请求', url, payload) + try { + res.send(await handler(res, payload)) + } catch (e) { + this.handleFailed(res, payload, e.stack.toString()) + } + }) + } - restart(port: number) { - this.stop() - this.start(port) - } - - abstract handleFailed(res: Response, payload: any, err: any): void - - registerRouter(method: "post" | "get" | string, url: string, handler: RegisterHandler) { - if (!url.startsWith("/")) { - url = "/" + url - } - - if (!this.expressAPP[method]) { - const err = `${this.name} register router failed,${method} not exist`; - log(err); - throw err; - } - this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => { - let payload = req.body; - if (method == "get") { - payload = req.query - } - else if (req.query) { - payload = { ...req.query, ...req.body } - } - log("收到http请求", url, payload); - try { - res.send(await handler(res, payload)) - } catch (e) { - this.handleFailed(res, payload, e.stack.toString()) - } - }); - } - - protected listen(port: number) { - this.server = this.expressAPP.listen(port, "0.0.0.0", () => { - const info = `${this.name} started 0.0.0.0:${port}` - console.log(info); - log(info); - }); - } -} \ No newline at end of file + protected listen(port: number) { + this.server = this.expressAPP.listen(port, '0.0.0.0', () => { + const info = `${this.name} started 0.0.0.0:${port}` + console.log(info) + log(info) + }) + } +} diff --git a/src/common/server/websocket.ts b/src/common/server/websocket.ts index 2b9def1..0ac56cd 100644 --- a/src/common/server/websocket.ts +++ b/src/common/server/websocket.ts @@ -1,106 +1,93 @@ -import {WebSocket, WebSocketServer} from "ws"; -import urlParse from "url"; -import {IncomingMessage} from "node:http"; -import {log} from "../utils/log"; -import {getConfigUtil} from "../config"; -import {llonebotError} from "../data"; +import { WebSocket, WebSocketServer } from 'ws' +import urlParse from 'url' +import { IncomingMessage } from 'node:http' +import { log } from '../utils/log' +import { getConfigUtil } from '../config' +import { llonebotError } from '../data' class WebsocketClientBase { - private wsClient: WebSocket + private wsClient: WebSocket - constructor() { + constructor() {} + + send(msg: string) { + if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) { + this.wsClient.send(msg) } + } - send(msg: string) { - if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) { - this.wsClient.send(msg); - } - } - - onMessage(msg: string) { - - } + onMessage(msg: string) {} } export class WebsocketServerBase { - private ws: WebSocketServer = null; + private ws: WebSocketServer = null - constructor() { - console.log(`llonebot websocket service started`) + constructor() { + console.log(`llonebot websocket service started`) + } + + start(port: number) { + try { + this.ws = new WebSocketServer({ port, maxPayload: 1024 * 1024 * 1024 }) + llonebotError.wsServerError = '' + } catch (e) { + llonebotError.wsServerError = '正向ws服务启动失败, ' + e.toString() } + this.ws.on('connection', (wsClient, req) => { + const url = req.url.split('?').shift() + this.authorize(wsClient, req) + this.onConnect(wsClient, url, req) + wsClient.on('message', async (msg) => { + this.onMessage(wsClient, url, msg.toString()) + }) + }) + } - start(port: number) { - try { - this.ws = new WebSocketServer({port, - maxPayload: 1024 * 1024 * 1024 - }); - llonebotError.wsServerError = '' - }catch (e) { - llonebotError.wsServerError = "正向ws服务启动失败, " + e.toString() - } - this.ws.on("connection", (wsClient, req) => { - const url = req.url.split("?").shift() - this.authorize(wsClient, req); - this.onConnect(wsClient, url, req); - wsClient.on("message", async (msg) => { - this.onMessage(wsClient, url, msg.toString()) - }) - }) - } + stop() { + llonebotError.wsServerError = '' + this.ws.close((err) => { + log('ws server close failed!', err) + }) + this.ws = null + } - stop() { - llonebotError.wsServerError = '' - this.ws.close((err) => { - log("ws server close failed!", err) - }); - this.ws = null; - } + restart(port: number) { + this.stop() + this.start(port) + } - restart(port: number) { - this.stop(); - this.start(port); - } - - authorize(wsClient: WebSocket, req) { - let token = getConfigUtil().getConfig().token; - const url = req.url.split("?").shift(); - log("ws connect", url) - let clientToken: string = "" - const authHeader = req.headers['authorization']; - if (authHeader) { - clientToken = authHeader.split("Bearer ").pop() - log("receive ws header token", clientToken); + authorize(wsClient: WebSocket, req) { + let token = getConfigUtil().getConfig().token + const url = req.url.split('?').shift() + log('ws connect', url) + let clientToken: string = '' + const authHeader = req.headers['authorization'] + if (authHeader) { + clientToken = authHeader.split('Bearer ').pop() + log('receive ws header token', clientToken) + } else { + const parsedUrl = urlParse.parse(req.url, true) + const urlToken = parsedUrl.query.access_token + if (urlToken) { + if (Array.isArray(urlToken)) { + clientToken = urlToken[0] } else { - const parsedUrl = urlParse.parse(req.url, true); - const urlToken = parsedUrl.query.access_token; - if (urlToken) { - if (Array.isArray(urlToken)) { - clientToken = urlToken[0] - } else { - clientToken = urlToken - } - log("receive ws url token", clientToken); - } - } - if (token && clientToken != token) { - this.authorizeFailed(wsClient) - return wsClient.close() + clientToken = urlToken } + log('receive ws url token', clientToken) + } } - - authorizeFailed(wsClient: WebSocket) { - + if (token && clientToken != token) { + this.authorizeFailed(wsClient) + return wsClient.close() } + } - onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { + authorizeFailed(wsClient: WebSocket) {} - } + onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) {} - onMessage(wsClient: WebSocket, url: string, msg: string) { + onMessage(wsClient: WebSocket, url: string, msg: string) {} - } - - sendHeart() { - - } -} \ No newline at end of file + sendHeart() {} +} diff --git a/src/common/types.ts b/src/common/types.ts index 7cad1df..58fd136 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -12,11 +12,11 @@ export interface OB11Config { enableHttpHeart?: boolean } export interface CheckVersion { - result: boolean, + result: boolean version: string } export interface Config { - imageRKey?: string; + imageRKey?: string ob11: OB11Config token?: string heartInterval?: number // ms diff --git a/src/common/utils/audio.ts b/src/common/utils/audio.ts index bd87ecc..3e14343 100644 --- a/src/common/utils/audio.ts +++ b/src/common/utils/audio.ts @@ -1,131 +1,130 @@ -import fs from "fs"; -import {encode, getDuration, getWavFileInfo, isWav} from "silk-wasm"; -import fsPromise from "fs/promises"; -import {log} from "./log"; -import path from "node:path"; -import {DATA_DIR, TEMP_DIR} from "./index"; -import {v4 as uuidv4} from "uuid"; -import {getConfigUtil} from "../config"; -import {spawn} from "node:child_process" +import fs from 'fs' +import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm' +import fsPromise from 'fs/promises' +import { log } from './log' +import path from 'node:path' +import { DATA_DIR, TEMP_DIR } from './index' +import { v4 as uuidv4 } from 'uuid' +import { getConfigUtil } from '../config' +import { spawn } from 'node:child_process' export async function encodeSilk(filePath: string) { - function getFileHeader(filePath: string) { - // 定义要读取的字节数 - const bytesToRead = 7; - try { - const buffer = fs.readFileSync(filePath, { - encoding: null, - flag: "r", - }); - - const fileHeader = buffer.toString("hex", 0, bytesToRead); - return fileHeader; - } catch (err) { - console.error("读取文件错误:", err); - return; - } - } - - async function isWavFile(filePath: string) { - return isWav(fs.readFileSync(filePath)); - } - - async function guessDuration(pttPath: string) { - const pttFileInfo = await fsPromise.stat(pttPath) - let duration = pttFileInfo.size / 1024 / 3 // 3kb/s - duration = Math.floor(duration) - duration = Math.max(1, duration) - log(`通过文件大小估算语音的时长:`, duration) - return duration - } - - // function verifyDuration(oriDuration: number, guessDuration: number) { - // // 单位都是秒 - // if (oriDuration - guessDuration > 10) { - // return guessDuration - // } - // oriDuration = Math.max(1, oriDuration) - // return oriDuration - // } - // async function getAudioSampleRate(filePath: string) { - // try { - // const mm = await import('music-metadata'); - // const metadata = await mm.parseFile(filePath); - // log(`${filePath}采样率`, metadata.format.sampleRate); - // return metadata.format.sampleRate; - // } catch (error) { - // log(`${filePath}采样率获取失败`, error.stack); - // // console.error(error); - // } - // } - + function getFileHeader(filePath: string) { + // 定义要读取的字节数 + const bytesToRead = 7 try { - const pttPath = path.join(TEMP_DIR, uuidv4()); - if (getFileHeader(filePath) !== "02232153494c4b") { - log(`语音文件${filePath}需要转换成silk`) - const _isWav = await isWavFile(filePath); - const pcmPath = pttPath + ".pcm" - let sampleRate = 0 - const convert = () => { - return new Promise((resolve, reject) => { - const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || "ffmpeg" - const cp = spawn(ffmpegPath, ["-y", "-i", filePath, "-ar", "24000", "-ac", "1", "-f", "s16le", pcmPath]) - cp.on("error", err => { - log(`FFmpeg处理转换出错: `, err.message) - return reject(err) - }) - cp.on("exit", (code, signal) => { - const EXIT_CODES = [0, 255] - if (code == null || EXIT_CODES.includes(code)) { - sampleRate = 24000 - const data = fs.readFileSync(pcmPath) - fs.unlink(pcmPath, (err) => { - }) - return resolve(data) - } - log(`FFmpeg exit: code=${code ?? "unknown"} sig=${signal ?? "unknown"}`) - reject(Error(`FFmpeg处理转换失败`)) - }) - }) - } - let input: Buffer - if (!_isWav) { - input = await convert() - } else { - input = fs.readFileSync(filePath) - const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] - const {fmt} = getWavFileInfo(input) - // log(`wav文件信息`, fmt) - if (!allowSampleRate.includes(fmt.sampleRate)) { - input = await convert() - } - } - const silk = await encode(input, sampleRate); - fs.writeFileSync(pttPath, silk.data); - log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, silk.duration) - return { - converted: true, - path: pttPath, - duration: silk.duration / 1000 - }; - } else { - const silk = fs.readFileSync(filePath); - let duration = 0; - try { - duration = getDuration(silk) / 1000 - } catch (e) { - log("获取语音文件时长失败, 使用文件大小推测时长", filePath, e.stack) - duration = await guessDuration(filePath); - } + const buffer = fs.readFileSync(filePath, { + encoding: null, + flag: 'r', + }) - return { - converted: false, - path: filePath, - duration, - }; - } - } catch (error) { - log("convert silk failed", error.stack); - return {}; + const fileHeader = buffer.toString('hex', 0, bytesToRead) + return fileHeader + } catch (err) { + console.error('读取文件错误:', err) + return } -} \ No newline at end of file + } + + async function isWavFile(filePath: string) { + return isWav(fs.readFileSync(filePath)) + } + + async function guessDuration(pttPath: string) { + const pttFileInfo = await fsPromise.stat(pttPath) + let duration = pttFileInfo.size / 1024 / 3 // 3kb/s + duration = Math.floor(duration) + duration = Math.max(1, duration) + log(`通过文件大小估算语音的时长:`, duration) + return duration + } + + // function verifyDuration(oriDuration: number, guessDuration: number) { + // // 单位都是秒 + // if (oriDuration - guessDuration > 10) { + // return guessDuration + // } + // oriDuration = Math.max(1, oriDuration) + // return oriDuration + // } + // async function getAudioSampleRate(filePath: string) { + // try { + // const mm = await import('music-metadata'); + // const metadata = await mm.parseFile(filePath); + // log(`${filePath}采样率`, metadata.format.sampleRate); + // return metadata.format.sampleRate; + // } catch (error) { + // log(`${filePath}采样率获取失败`, error.stack); + // // console.error(error); + // } + // } + + try { + const pttPath = path.join(TEMP_DIR, uuidv4()) + if (getFileHeader(filePath) !== '02232153494c4b') { + log(`语音文件${filePath}需要转换成silk`) + const _isWav = await isWavFile(filePath) + const pcmPath = pttPath + '.pcm' + let sampleRate = 0 + const convert = () => { + return new Promise((resolve, reject) => { + const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg' + const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]) + cp.on('error', (err) => { + log(`FFmpeg处理转换出错: `, err.message) + return reject(err) + }) + cp.on('exit', (code, signal) => { + const EXIT_CODES = [0, 255] + if (code == null || EXIT_CODES.includes(code)) { + sampleRate = 24000 + const data = fs.readFileSync(pcmPath) + fs.unlink(pcmPath, (err) => {}) + return resolve(data) + } + log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`) + reject(Error(`FFmpeg处理转换失败`)) + }) + }) + } + let input: Buffer + if (!_isWav) { + input = await convert() + } else { + input = fs.readFileSync(filePath) + const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] + const { fmt } = getWavFileInfo(input) + // log(`wav文件信息`, fmt) + if (!allowSampleRate.includes(fmt.sampleRate)) { + input = await convert() + } + } + const silk = await encode(input, sampleRate) + fs.writeFileSync(pttPath, silk.data) + log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, silk.duration) + return { + converted: true, + path: pttPath, + duration: silk.duration / 1000, + } + } else { + const silk = fs.readFileSync(filePath) + let duration = 0 + try { + duration = getDuration(silk) / 1000 + } catch (e) { + log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack) + duration = await guessDuration(filePath) + } + + return { + converted: false, + path: filePath, + duration, + } + } + } catch (error) { + log('convert silk failed', error.stack) + return {} + } +} diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts index 174d81f..5cf88de 100644 --- a/src/common/utils/file.ts +++ b/src/common/utils/file.ts @@ -1,258 +1,255 @@ -import fs from "fs"; -import fsPromise from "fs/promises"; -import crypto from "crypto"; -import util from "util"; -import path from "node:path"; -import {v4 as uuidv4} from "uuid"; -import {log, TEMP_DIR} from "./index"; -import {dbUtil} from "../db"; -import * as fileType from "file-type"; -import {net} from "electron"; - +import fs from 'fs' +import fsPromise from 'fs/promises' +import crypto from 'crypto' +import util from 'util' +import path from 'node:path' +import { v4 as uuidv4 } from 'uuid' +import { log, TEMP_DIR } from './index' +import { dbUtil } from '../db' +import * as fileType from 'file-type' +import { net } from 'electron' export function isGIF(path: string) { - const buffer = Buffer.alloc(4); - const fd = fs.openSync(path, 'r'); - fs.readSync(fd, buffer, 0, 4, 0); - fs.closeSync(fd); - return buffer.toString() === 'GIF8' + const buffer = Buffer.alloc(4) + const fd = fs.openSync(path, 'r') + fs.readSync(fd, buffer, 0, 4, 0) + fs.closeSync(fd) + return buffer.toString() === 'GIF8' } // 定义一个异步函数来检查文件是否存在 export function checkFileReceived(path: string, timeout: number = 3000): Promise { - return new Promise((resolve, reject) => { - const startTime = Date.now(); + return new Promise((resolve, reject) => { + const startTime = Date.now() - function check() { - if (fs.existsSync(path)) { - resolve(); - } else if (Date.now() - startTime > timeout) { - reject(new Error(`文件不存在: ${path}`)); - } else { - setTimeout(check, 100); - } - } + function check() { + if (fs.existsSync(path)) { + resolve() + } else if (Date.now() - startTime > timeout) { + reject(new Error(`文件不存在: ${path}`)) + } else { + setTimeout(check, 100) + } + } - check(); - }); + check() + }) } export async function file2base64(path: string) { - const readFile = util.promisify(fs.readFile); - let result = { - err: "", - data: "" - } + const readFile = util.promisify(fs.readFile) + let result = { + err: '', + data: '', + } + try { + // 读取文件内容 + // if (!fs.existsSync(path)){ + // path = path.replace("\\Ori\\", "\\Thumb\\"); + // } try { - // 读取文件内容 - // if (!fs.existsSync(path)){ - // path = path.replace("\\Ori\\", "\\Thumb\\"); - // } - try { - await checkFileReceived(path, 5000); - } catch (e: any) { - result.err = e.toString(); - return result; - } - const data = await readFile(path); - // 转换为Base64编码 - result.data = data.toString('base64'); - } catch (err) { - result.err = err.toString(); + await checkFileReceived(path, 5000) + } catch (e: any) { + result.err = e.toString() + return result } - return result; + const data = await readFile(path) + // 转换为Base64编码 + result.data = data.toString('base64') + } catch (err) { + result.err = err.toString() + } + return result } - - export function calculateFileMD5(filePath: string): Promise { - return new Promise((resolve, reject) => { - // 创建一个流式读取器 - const stream = fs.createReadStream(filePath); - const hash = crypto.createHash('md5'); + return new Promise((resolve, reject) => { + // 创建一个流式读取器 + const stream = fs.createReadStream(filePath) + const hash = crypto.createHash('md5') - stream.on('data', (data: Buffer) => { - // 当读取到数据时,更新哈希对象的状态 - hash.update(data); - }); + stream.on('data', (data: Buffer) => { + // 当读取到数据时,更新哈希对象的状态 + hash.update(data) + }) - stream.on('end', () => { - // 文件读取完成,计算哈希 - const md5 = hash.digest('hex'); - resolve(md5); - }); + stream.on('end', () => { + // 文件读取完成,计算哈希 + const md5 = hash.digest('hex') + resolve(md5) + }) - stream.on('error', (err: Error) => { - // 处理可能的读取错误 - reject(err); - }); - }); + stream.on('error', (err: Error) => { + // 处理可能的读取错误 + reject(err) + }) + }) } export interface HttpDownloadOptions { - url: string; - headers?: Record | string; + url: string + headers?: Record | string } export async function httpDownload(options: string | HttpDownloadOptions): Promise { - let chunks: Buffer[] = []; - let url: string; - let headers: Record = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36" - }; - if (typeof options === "string") { - url = options; - } else { - url = options.url; - if (options.headers) { - if (typeof options.headers === "string") { - headers = JSON.parse(options.headers); - } else { - headers = options.headers; - } - } + let chunks: Buffer[] = [] + let url: string + let headers: Record = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', + } + if (typeof options === 'string') { + url = options + } else { + url = options.url + if (options.headers) { + if (typeof options.headers === 'string') { + headers = JSON.parse(options.headers) + } else { + headers = options.headers + } } - const fetchRes = await net.fetch(url, headers); - if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`) + } + const fetchRes = await net.fetch(url, headers) + if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`) - const blob = await fetchRes.blob(); - let buffer = await blob.arrayBuffer(); - return Buffer.from(buffer); + const blob = await fetchRes.blob() + let buffer = await blob.arrayBuffer() + return Buffer.from(buffer) } type Uri2LocalRes = { - success: boolean, - errMsg: string, - fileName: string, - ext: string, - path: string, - isLocal: boolean + success: boolean + errMsg: string + fileName: string + ext: string + path: string + isLocal: boolean } export async function uri2local(uri: string, fileName: string = null): Promise { - let res = { - success: false, - errMsg: "", - fileName: "", - ext: "", - path: "", - isLocal: false - } - if (!fileName) { - fileName = uuidv4(); - } - let filePath = path.join(TEMP_DIR, fileName) - let url = null; - try { - url = new URL(uri); - } catch (e) { - res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在` - return res - } - - // log("uri protocol", url.protocol, uri); - if (url.protocol == "base64:") { - // base64转成文件 - let base64Data = uri.split("base64://")[1] - try { - const buffer = Buffer.from(base64Data, 'base64'); - fs.writeFileSync(filePath, buffer); - - } catch (e: any) { - res.errMsg = `base64文件下载失败,` + e.toString() - return res - } - } else if (url.protocol == "http:" || url.protocol == "https:") { - // 下载文件 - let buffer: Buffer = null; - try { - buffer = await httpDownload(uri); - } catch (e) { - res.errMsg = `${url}下载失败,` + e.toString() - return res - } - try { - const pathInfo = path.parse(decodeURIComponent(url.pathname)) - if (pathInfo.name) { - fileName = pathInfo.name - if (pathInfo.ext) { - fileName += pathInfo.ext - // res.ext = pathInfo.ext - } - } - res.fileName = fileName - filePath = path.join(TEMP_DIR, uuidv4() + fileName) - fs.writeFileSync(filePath, buffer); - } catch (e: any) { - res.errMsg = `${url}下载失败,` + e.toString() - return res - } - } else { - let pathname: string; - if (url.protocol === "file:") { - // await fs.copyFile(url.pathname, filePath); - pathname = decodeURIComponent(url.pathname) - if (process.platform === "win32") { - filePath = pathname.slice(1) - } else { - filePath = pathname - } - } else { - const cache = await dbUtil.getFileCache(uri); - if (cache) { - filePath = cache.filePath - } else { - filePath = uri; - } - } - - res.isLocal = true - } - // else{ - // res.errMsg = `不支持的file协议,` + url.protocol - // return res - // } - // if (isGIF(filePath) && !res.isLocal) { - // await fs.rename(filePath, filePath + ".gif"); - // filePath += ".gif"; - // } - if (!res.isLocal && !res.ext) { - try { - let ext: string = (await fileType.fileTypeFromFile(filePath)).ext - if (ext) { - log("获取文件类型", ext, filePath) - fs.renameSync(filePath, filePath + `.${ext}`) - filePath += `.${ext}` - res.fileName += `.${ext}` - res.ext = ext - } - } catch (e) { - // log("获取文件类型失败", filePath,e.stack) - } - } - res.success = true - res.path = filePath + let res = { + success: false, + errMsg: '', + fileName: '', + ext: '', + path: '', + isLocal: false, + } + if (!fileName) { + fileName = uuidv4() + } + let filePath = path.join(TEMP_DIR, fileName) + let url = null + try { + url = new URL(uri) + } catch (e) { + res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在` return res + } + + // log("uri protocol", url.protocol, uri); + if (url.protocol == 'base64:') { + // base64转成文件 + let base64Data = uri.split('base64://')[1] + try { + const buffer = Buffer.from(base64Data, 'base64') + fs.writeFileSync(filePath, buffer) + } catch (e: any) { + res.errMsg = `base64文件下载失败,` + e.toString() + return res + } + } else if (url.protocol == 'http:' || url.protocol == 'https:') { + // 下载文件 + let buffer: Buffer = null + try { + buffer = await httpDownload(uri) + } catch (e) { + res.errMsg = `${url}下载失败,` + e.toString() + return res + } + try { + const pathInfo = path.parse(decodeURIComponent(url.pathname)) + if (pathInfo.name) { + fileName = pathInfo.name + if (pathInfo.ext) { + fileName += pathInfo.ext + // res.ext = pathInfo.ext + } + } + res.fileName = fileName + filePath = path.join(TEMP_DIR, uuidv4() + fileName) + fs.writeFileSync(filePath, buffer) + } catch (e: any) { + res.errMsg = `${url}下载失败,` + e.toString() + return res + } + } else { + let pathname: string + if (url.protocol === 'file:') { + // await fs.copyFile(url.pathname, filePath); + pathname = decodeURIComponent(url.pathname) + if (process.platform === 'win32') { + filePath = pathname.slice(1) + } else { + filePath = pathname + } + } else { + const cache = await dbUtil.getFileCache(uri) + if (cache) { + filePath = cache.filePath + } else { + filePath = uri + } + } + + res.isLocal = true + } + // else{ + // res.errMsg = `不支持的file协议,` + url.protocol + // return res + // } + // if (isGIF(filePath) && !res.isLocal) { + // await fs.rename(filePath, filePath + ".gif"); + // filePath += ".gif"; + // } + if (!res.isLocal && !res.ext) { + try { + let ext: string = (await fileType.fileTypeFromFile(filePath)).ext + if (ext) { + log('获取文件类型', ext, filePath) + fs.renameSync(filePath, filePath + `.${ext}`) + filePath += `.${ext}` + res.fileName += `.${ext}` + res.ext = ext + } + } catch (e) { + // log("获取文件类型失败", filePath,e.stack) + } + } + res.success = true + res.path = filePath + return res } export async function copyFolder(sourcePath: string, destPath: string) { - try { - const entries = await fsPromise.readdir(sourcePath, {withFileTypes: true}); - await fsPromise.mkdir(destPath, {recursive: true}); - for (let entry of entries) { - const srcPath = path.join(sourcePath, entry.name); - const dstPath = path.join(destPath, entry.name); - if (entry.isDirectory()) { - await copyFolder(srcPath, dstPath); - } else { - try { - await fsPromise.copyFile(srcPath, dstPath); - } catch (error) { - console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`); - // 这里可以决定是否要继续复制其他文件 - } - } + try { + const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true }) + await fsPromise.mkdir(destPath, { recursive: true }) + for (let entry of entries) { + const srcPath = path.join(sourcePath, entry.name) + const dstPath = path.join(destPath, entry.name) + if (entry.isDirectory()) { + await copyFolder(srcPath, dstPath) + } else { + try { + await fsPromise.copyFile(srcPath, dstPath) + } catch (error) { + console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`) + // 这里可以决定是否要继续复制其他文件 } - } catch (error) { - console.error('复制文件夹时出错:', error); + } } -} \ No newline at end of file + } catch (error) { + console.error('复制文件夹时出错:', error) + } +} diff --git a/src/common/utils/helper.ts b/src/common/utils/helper.ts index 48862b8..e85c38e 100644 --- a/src/common/utils/helper.ts +++ b/src/common/utils/helper.ts @@ -1,48 +1,48 @@ export function truncateString(obj: any, maxLength = 500) { - if (obj !== null && typeof obj === 'object') { - Object.keys(obj).forEach(key => { - if (typeof obj[key] === 'string') { - // 如果是字符串且超过指定长度,则截断 - if (obj[key].length > maxLength) { - obj[key] = obj[key].substring(0, maxLength) + '...'; - } - } else if (typeof obj[key] === 'object') { - // 如果是对象或数组,则递归调用 - truncateString(obj[key], maxLength); - } - }); - } - return obj; + if (obj !== null && typeof obj === 'object') { + Object.keys(obj).forEach((key) => { + if (typeof obj[key] === 'string') { + // 如果是字符串且超过指定长度,则截断 + if (obj[key].length > maxLength) { + obj[key] = obj[key].substring(0, maxLength) + '...' + } + } else if (typeof obj[key] === 'object') { + // 如果是对象或数组,则递归调用 + truncateString(obj[key], maxLength) + } + }) + } + return obj } export function isNumeric(str: string) { - return /^\d+$/.test(str); + return /^\d+$/.test(str) } export function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)) } // 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 export function mergeNewProperties(newObj: any, oldObj: any) { - Object.keys(newObj).forEach(key => { - // 如果老对象不存在当前属性,则直接复制 - if (!oldObj.hasOwnProperty(key)) { - oldObj[key] = newObj[key]; - } else { - // 如果老对象和新对象的当前属性都是对象,则递归合并 - if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') { - mergeNewProperties(newObj[key], oldObj[key]); - } else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') { - // 属性冲突,有一方不是对象,直接覆盖 - oldObj[key] = newObj[key]; - } - } - }); + Object.keys(newObj).forEach((key) => { + // 如果老对象不存在当前属性,则直接复制 + if (!oldObj.hasOwnProperty(key)) { + oldObj[key] = newObj[key] + } else { + // 如果老对象和新对象的当前属性都是对象,则递归合并 + if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') { + mergeNewProperties(newObj[key], oldObj[key]) + } else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') { + // 属性冲突,有一方不是对象,直接覆盖 + oldObj[key] = newObj[key] + } + } + }) } export function isNull(value: any) { - return value === undefined || value === null; + return value === undefined || value === null } /** @@ -52,17 +52,16 @@ export function isNull(value: any) { * @returns 处理后的字符串,超过长度的地方将会换行 */ export function wrapText(str: string, maxLength: number): string { - // 初始化一个空字符串用于存放结果 - let result: string = ''; + // 初始化一个空字符串用于存放结果 + let result: string = '' - // 循环遍历字符串,每次步进maxLength个字符 - for (let i = 0; i < str.length; i += maxLength) { - // 从i开始,截取长度为maxLength的字符串段,并添加到结果字符串 - // 如果不是第一段,先添加一个换行符 - if (i > 0) result += '\n'; - result += str.substring(i, i + maxLength); - } + // 循环遍历字符串,每次步进maxLength个字符 + for (let i = 0; i < str.length; i += maxLength) { + // 从i开始,截取长度为maxLength的字符串段,并添加到结果字符串 + // 如果不是第一段,先添加一个换行符 + if (i > 0) result += '\n' + result += str.substring(i, i + maxLength) + } - return result; + return result } - diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 5cc4594..156763e 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -1,5 +1,5 @@ -import path from "node:path"; -import fs from "fs"; +import path from 'node:path' +import fs from 'fs' export * from './file' export * from './helper' @@ -7,12 +7,12 @@ export * from './log' export * from './qqlevel' export * from './qqpkg' export * from './upgrade' -export const DATA_DIR = global.LiteLoader.plugins["LLOneBot"].path.data; -export const TEMP_DIR = path.join(DATA_DIR, "temp"); -export const PLUGIN_DIR = global.LiteLoader.plugins["LLOneBot"].path.plugin; +export const DATA_DIR = global.LiteLoader.plugins['LLOneBot'].path.data +export const TEMP_DIR = path.join(DATA_DIR, 'temp') +export const PLUGIN_DIR = global.LiteLoader.plugins['LLOneBot'].path.plugin if (!fs.existsSync(TEMP_DIR)) { - fs.mkdirSync(TEMP_DIR, {recursive: true}); + fs.mkdirSync(TEMP_DIR, { recursive: true }) } -export {getVideoInfo} from "./video"; -export {checkFfmpeg} from "./video"; -export {encodeSilk} from "./audio"; \ No newline at end of file +export { getVideoInfo } from './video' +export { checkFfmpeg } from './video' +export { encodeSilk } from './audio' diff --git a/src/common/utils/log.ts b/src/common/utils/log.ts index e957851..e06593f 100644 --- a/src/common/utils/log.ts +++ b/src/common/utils/log.ts @@ -1,37 +1,35 @@ -import {selfInfo} from "../data"; -import fs from "fs"; -import path from "node:path"; -import {DATA_DIR, truncateString} from "./index"; -import {getConfigUtil} from "../config"; +import { selfInfo } from '../data' +import fs from 'fs' +import path from 'node:path' +import { DATA_DIR, truncateString } from './index' +import { getConfigUtil } from '../config' -const date = new Date(); -const logFileName = `llonebot-${date.toLocaleString("zh-CN")}.log`.replace(/\//g, "-").replace(/:/g, "-"); -const logDir = path.join(DATA_DIR, "logs"); +const date = new Date() +const logFileName = `llonebot-${date.toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-') +const logDir = path.join(DATA_DIR, 'logs') if (!fs.existsSync(logDir)) { - fs.mkdirSync(logDir, {recursive: true}); + fs.mkdirSync(logDir, { recursive: true }) } export function log(...msg: any[]) { - if (!getConfigUtil().getConfig().log) { - return //console.log(...msg); - } + if (!getConfigUtil().getConfig().log) { + return //console.log(...msg); + } - const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : "" - let logMsg = ""; - for (let msgItem of msg) { - // 判断是否是对象 - if (typeof msgItem === "object") { - let obj = JSON.parse(JSON.stringify(msgItem)); - logMsg += JSON.stringify(truncateString(obj)) + " "; - continue; - } - logMsg += msgItem + " "; + const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : '' + let logMsg = '' + for (let msgItem of msg) { + // 判断是否是对象 + if (typeof msgItem === 'object') { + let obj = JSON.parse(JSON.stringify(msgItem)) + logMsg += JSON.stringify(truncateString(obj)) + ' ' + continue } - let currentDateTime = new Date().toLocaleString(); - logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n` - // sendLog(...msg); - // console.log(msg) - fs.appendFile(path.join(logDir, logFileName), logMsg, (err: any) => { - - }) -} \ No newline at end of file + logMsg += msgItem + ' ' + } + let currentDateTime = new Date().toLocaleString() + logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n` + // sendLog(...msg); + // console.log(msg) + fs.appendFile(path.join(logDir, logFileName), logMsg, (err: any) => {}) +} diff --git a/src/common/utils/qqlevel.ts b/src/common/utils/qqlevel.ts index 49cc13a..ac89932 100644 --- a/src/common/utils/qqlevel.ts +++ b/src/common/utils/qqlevel.ts @@ -1,7 +1,7 @@ // QQ等级换算 -import {QQLevel} from "../../ntqqapi/types"; +import { QQLevel } from '../../ntqqapi/types' export function calcQQLevel(level: QQLevel) { - const {crownNum, sunNum, moonNum, starNum} = level - return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum -} \ No newline at end of file + const { crownNum, sunNum, moonNum, starNum } = level + return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum +} diff --git a/src/common/utils/qqpkg.ts b/src/common/utils/qqpkg.ts index 428fef7..0043d53 100644 --- a/src/common/utils/qqpkg.ts +++ b/src/common/utils/qqpkg.ts @@ -1,12 +1,12 @@ -import path from "path"; +import path from 'path' type QQPkgInfo = { - version: string; - buildVersion: string; - platform: string; - eleArch: string; + version: string + buildVersion: string + platform: string + eleArch: string } -export const qqPkgInfo: QQPkgInfo = require(path.join(process.resourcesPath, "app/package.json")) +export const qqPkgInfo: QQPkgInfo = require(path.join(process.resourcesPath, 'app/package.json')) -export const isQQ998: boolean = qqPkgInfo.buildVersion >= "22106" \ No newline at end of file +export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106' diff --git a/src/common/utils/upgrade.ts b/src/common/utils/upgrade.ts index 0ded290..6c84a16 100644 --- a/src/common/utils/upgrade.ts +++ b/src/common/utils/upgrade.ts @@ -1,98 +1,97 @@ -import { version } from "../../version"; -import * as path from "node:path"; -import * as fs from "node:fs"; -import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from "."; -import compressing from "compressing"; +import { version } from '../../version' +import * as path from 'node:path' +import * as fs from 'node:fs' +import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from '.' +import compressing from 'compressing' - -const downloadMirrorHosts = ["https://mirror.ghproxy.com/"]; -const checkVersionMirrorHosts = ["https://521github.com"]; +const downloadMirrorHosts = ['https://mirror.ghproxy.com/'] +const checkVersionMirrorHosts = ['https://521github.com'] export async function checkNewVersion() { - const latestVersionText = await getRemoteVersion(); - const latestVersion = latestVersionText.split("."); - log("llonebot last version", latestVersion); - const currentVersion: string[] = version.split("."); - log("llonebot current version", currentVersion); - for (let k of [0, 1, 2]) { - if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) { - log("") - return { result: true, version: latestVersionText }; - } - else if (parseInt(latestVersion[k]) < parseInt(currentVersion[k])) { - break; - } + const latestVersionText = await getRemoteVersion() + const latestVersion = latestVersionText.split('.') + log('llonebot last version', latestVersion) + const currentVersion: string[] = version.split('.') + log('llonebot current version', currentVersion) + for (let k of [0, 1, 2]) { + if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) { + log('') + return { result: true, version: latestVersionText } + } else if (parseInt(latestVersion[k]) < parseInt(currentVersion[k])) { + break } - return { result: false, version: version }; + } + return { result: false, version: version } } export async function upgradeLLOneBot() { - const latestVersion = await getRemoteVersion(); - if (latestVersion && latestVersion != "") { - const downloadUrl = "https://github.com/LLOneBot/LLOneBot/releases/download/v" + latestVersion + "/LLOneBot.zip"; - const filePath = path.join(TEMP_DIR, "./update-" + latestVersion + ".zip"); - let downloadSuccess = false; - // 多镜像下载 - for (const mirrorGithub of downloadMirrorHosts) { - try { - const buffer = await httpDownload(mirrorGithub + downloadUrl); - fs.writeFileSync(filePath, buffer) - downloadSuccess = true; - break; - } catch (e) { - log("llonebot upgrade error", e); - } - } - if (!downloadSuccess) { - log("llonebot upgrade error", "download failed"); - return false; - } - const temp_ver_dir = path.join(TEMP_DIR, "LLOneBot" + latestVersion); - let uncompressedPromise = async function () { - return new Promise((resolve, reject) => { - compressing.zip.uncompress(filePath, temp_ver_dir).then(() => { - resolve(true); - }).catch((reason: any) => { - log("llonebot upgrade failed, ", reason); - if (reason?.errno == -4082) { - resolve(true); - } - resolve(false); - }); - }); - } - const uncompressedResult = await uncompressedPromise(); - // 复制文件 - await copyFolder(temp_ver_dir, PLUGIN_DIR); - - return uncompressedResult; + const latestVersion = await getRemoteVersion() + if (latestVersion && latestVersion != '') { + const downloadUrl = 'https://github.com/LLOneBot/LLOneBot/releases/download/v' + latestVersion + '/LLOneBot.zip' + const filePath = path.join(TEMP_DIR, './update-' + latestVersion + '.zip') + let downloadSuccess = false + // 多镜像下载 + for (const mirrorGithub of downloadMirrorHosts) { + try { + const buffer = await httpDownload(mirrorGithub + downloadUrl) + fs.writeFileSync(filePath, buffer) + downloadSuccess = true + break + } catch (e) { + log('llonebot upgrade error', e) + } } - return false; + if (!downloadSuccess) { + log('llonebot upgrade error', 'download failed') + return false + } + const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion) + let uncompressedPromise = async function () { + return new Promise((resolve, reject) => { + compressing.zip + .uncompress(filePath, temp_ver_dir) + .then(() => { + resolve(true) + }) + .catch((reason: any) => { + log('llonebot upgrade failed, ', reason) + if (reason?.errno == -4082) { + resolve(true) + } + resolve(false) + }) + }) + } + const uncompressedResult = await uncompressedPromise() + // 复制文件 + await copyFolder(temp_ver_dir, PLUGIN_DIR) + + return uncompressedResult + } + return false } export async function getRemoteVersion() { - let Version = ""; - for (let i = 0; i < checkVersionMirrorHosts.length; i++) { - let mirrorGithub = checkVersionMirrorHosts[i]; - let tVersion = await getRemoteVersionByMirror(mirrorGithub); - if (tVersion && tVersion != "") { - Version = tVersion; - break; - } + let Version = '' + for (let i = 0; i < checkVersionMirrorHosts.length; i++) { + let mirrorGithub = checkVersionMirrorHosts[i] + let tVersion = await getRemoteVersionByMirror(mirrorGithub) + if (tVersion && tVersion != '') { + Version = tVersion + break } - return Version; + } + return Version } export async function getRemoteVersionByMirror(mirrorGithub: string) { - let releasePage = "error"; + let releasePage = 'error' - try { - releasePage = (await httpDownload(mirrorGithub + "/LLOneBot/LLOneBot/releases")).toString(); - // log("releasePage", releasePage); - if (releasePage === "error") return ""; - return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))[0]; - } catch { - } - return ""; - -} \ No newline at end of file + try { + releasePage = (await httpDownload(mirrorGithub + '/LLOneBot/LLOneBot/releases')).toString() + // log("releasePage", releasePage); + if (releasePage === 'error') return '' + return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))[0] + } catch {} + return '' +} diff --git a/src/common/utils/video.ts b/src/common/utils/video.ts index d2632d0..19a3a22 100644 --- a/src/common/utils/video.ts +++ b/src/common/utils/video.ts @@ -1,88 +1,90 @@ -import {log} from "./log"; -import ffmpeg from "fluent-ffmpeg"; -import fs from "fs"; -import {getConfigUtil} from "../config"; +import { log } from './log' +import ffmpeg from 'fluent-ffmpeg' +import fs from 'fs' +import { getConfigUtil } from '../config' -const defaultVideoThumbB64 = "/9j/4AAQSkZJRgABAQAAAQABAAD//gAXR2VuZXJhdGVkIGJ5IFNuaXBhc3Rl/9sAhAAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47AQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAF/APADAREAAhEBAxEB/8QBogAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLEAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+foBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxEAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDiAayNxwagBwNAC5oAM0xBmgBM0ANJoAjY0AQsaBkTGgCM0DEpAFAC0AFMBaACgAoEJTASgQlACUwCgQ4UAOFADhQA4UAOFADxQIkBqDQUGgBwagBQaBC5pgGaAELUAMLUARs1AETGgBhNAxhoASkAUALQIKYxaBBQAUwEoAQ0CEoASmAUAOoEKKAHCgBwoAeKAHigQ7NZmoZpgLmgBd1Ahd1ABupgNLUAMLUAMY0AMJoAYaAENACUCCgAoAWgAoAWgBKYCUAJQISgApgLQAooEOFACigB4oAeKBDxQAVmaiZpgGaAFzQAbqAE3UAIWpgNJoAYTQIaaAEoAQ0CEoASgBaACgBaACmAUAJQAlAgoAKYC0AKKBCigB4FADgKBDwKAHigBuazNRM0DEzTAM0AJmgAzQAhNAhpNACGmA2gQlACUCEoAKACgBaAFpgFACUAJQAUCCmAUALQIcBQA4CgB4FADgKBDhQA4UAMzWZqNzTGJQAZoATNABmgBKAEoEIaYCUCEoASgQlABQAtABQAtMBKACgAoEFABimAYoEKBQA4CgB4FADwKBDgKAFFADhQBCazNhKAEpgFACUAFACUAFAhDTAbQISgAoEJQAUALQAtMAoAKADFABigQYoAMUALimIUCgBwFAh4FADgKAHUALQAtAENZmwlACUwEoAKAEoAKACgQlMBpoEJQAUCCgBcUAFABTAXFAC4oAMUAGKBBigAxQIKYCigQ8UAOFADhQAtAC0ALQBDWZqJQMSgBKYBQAlABQISgBKYCGgQlAC0CCgBcUAFABTAUCkA7FMAxQAYoEJQAUCCmAooEOFADxQA4UAFAC0ALQBDWZqJQAlACUxhQAlABQIKAEoASmISgBcUCCgBaACgBcUAKBQAuKYC0CEoAQ0AJQISmAooEPFADhQA4UALQAtAC0AQ1maiUAFACUAJTAKAEoAKAEoAMUxBigAxQIWgAoAKAFAoAWgBaYBQIQ0ANNACUCCmIUUAOFADxQA4UALQAtABQBFWZqFACUAFACYpgFACUAFACUAFAgxTEFABQAUALQAooAWgAoAKYDTQIaaAEpiCgQ4UAOFAh4oGOFAC0ALSAKYEdZmglABQAUDDFACUwEoASgAoAKBBQIKYBQAUALQAtAC0AJQAhpgNJoENJoATNMQCgQ8UCHigB4oAWgYtABQAUAMrM0CgAoAKADFACUxiUAJQAlAgoAKYgoAKACgYtAC0AFAhDTAQmgBhNAhpNACZpiFBoEPFAEi0CHigB1ABQAUDEoAbWZoFABQAtABTAQ0ANNAxDQAlAhaAEpiCgAoGFAC0AFABmgBCaYhpNADCaBDSaBBmgABpiJFNAEimgB4NADqAFzQAlACE0AJWZoFAC0AFAC0wEIoAaaAG0AJQAUCCgApjCgAoAKADNABmgBpNMQ0mgBpNAhhNAgzQAoNADwaAHqaAJAaBDgaYC5oATNACZoAWszQKACgBaBDqYCGgBpoAYaBiUCCgBKYBQMKACgAoAM0AITQIaTQA0mmA0mgQ3NAhKAHCgBwNADwaAHg0AOBpiFzQAZoATNAD6zNAoAKAFoEOpgBoAaaAGGmAw0AJmgAzQMM0AGaADNABmgBM0AITQIaTQAhNMQw0AJQIKAFFADhQA4GgBwNADs0xC5oAM0CDNAEtZmoUCCgBaAHUwCgBppgRtQAw0ANzQAZoAM0AGaADNABmgBKAEoAQ0ANNMQhoEJQAlMBaQDgaAFBoAcDTAdmgQuaADNAgzQBPWZqFAgoAWgBaYC0CGmmBG1AyM0ANJoATNACZoAXNABmgAzQAUAJQAhoAQ0xDTQISmAUALQAUgHA0AKDTAdmgQuaBBQAtAFiszQKACgBaAFFMAoEIaYEbUDI2oAYaAEoASgAzQAuaACgAoAKAENMQ00AJTEFAhKACgAoAXNACg0AOBoAWgQtAC0AWazNAoAKACgBaYBQIQ0AMNMYw0AMIoAbQAlMAoAKACgAzSAKYhKAENACUxBQIKACgBKACgBaAHCgQ4UALQAUAWqzNAoAKACgApgFACGgQ00xjTQAwigBCKAG4pgJQAlABQAUCCgBKACgBKYgoEFABQISgAoAWgBRQA4UALQAUCLdZmoUAFABQAlMAoASgBDQA00wENACYoATFMBpFADSKAEoEJQAUAFABQAlMQtAgoASgQUAJQAUAKKAHCgBaBBQBbrM1CgAoAKACmAUAJQAlADaYBQAlACYpgIRQA0igBpFAhtABQAUAFMAoEFABQIKAEoASgQUALQAooAWgQUAW81mbC0CCgApgFACUAIaAEpgJQAUAFABQAhFMBpFADSKAGkUCExQAYoAMUAGKADFMQYoAMUCExSATFABQIKYBQAtABQIt5qDYM0ALmgQtIApgIaAENADaACmAlAC0ALQAUwGkUANIoAaRQAmKBBigAxQAYoAMUAGKBBigBMUAJigQmKAExTAKBC0AFAFnNQaig0AKDQAtAgoASgBDQAlMBKACgAFADhQAtMBCKAGkUAIRQAmKADFABigQmKADFACYoAXFABigQmKAExQAmKBCYpgJigAoAnzUGgZoAcDQAuaBC0AJQAhoASmAlABQAtADhQAtMAoATFACEUAJigAxQAYoATFAhMUAFABQAuKADFABigBpWgBCKBCYpgJigB+ag0DNADgaBDgaAFzQITNACUAJTAKACgBRQAopgOoAWgBKAEoAKACgAoASgBpoEJQAooAWgBaBhigBMUCEIoAQigBMUAJSLCgBQaBDgaQC5oEFACUwCgBKACmAtADhQA4UALQAUAJQAUAJQAUAJQAhoENoAWgBRQAooGLQAUAGKAGkUAIRQIZSKEoGKKBDhQAUCCgAoAKBBQAUwFoGKKAHCgBaACgAoASgAoASgBCaAEoEJmgAoAUGgBQaAHZoGFABQAUANoAjpDEoAWgBaAFoEFACUALQAUCCmAUAOFAxRQAtAC0AJQAUAJQAmaBDSaAEzQAmaYBmgBQaAHA0gFzQAuaBhmgAzQAlAEdIYUALQAtAgoAKAEoEFAC0AFMAoAUUDFFAC0ALQAUAJQAhoENNACE0wEoATNABmgBc0ALmgBc0gDNAC5oATNABmgBKRQlACigB1AgoASgQlABTAWgBKACgBaBi0ALQAZoAM0AFACGgQ00wENACUAJQAUCFzQMM0ALmgAzQAZoAM0AGaQC0igoAUUALQIWgBDQISmAUAFACUAFABQAuaBi5oAM0AGaBBmgBKAEpgIaAG0AJQAUCFoAM0DDNAC5oATNABmgAzQBJUlBQAooAWgQtACGmIaaACgAoASgBKACgBc0DCgQUAGaADNABTASgBDQAlACUAFAgoAKBhQAUAFABQAlAE1SUFAxRQIWgQtMBDQIQ0AJQAlAhKBiUAFABmgBc0AGaADNABTAKACgBKAEoASgQlABQAUAFAC0AFACUAFAE1SaBQAUCHCgQtMBKBCUAJQISgBDQA00DEzQAuaADNMBc0AGaADNABQAUAJQAlABQISgAoAKACgBaACgBKAEoAnqTQSgBRQIcKBC0xCUAJQISgBKAENADDQAmaYwzQAuaADNAC0AFABQAUAFAhKACgBKACgAoAWgAoELQAlAxKAJqk0EoAWgQooELTEFADaBCUABoENNMY00ANNAwzQAZoAXNAC0AFAC0CFoASgAoASgBKACgAoAWgQtABQAUANNAyWpNAoAKBCimIWgQUCEoASmIQ0ANNADTQMaaAEoGLmgAzQAtADhQIWgBaACgQhoASgYlACUALQIWgBaACgBKAENAyWpNBKYBQIcKBC0CEoEJTAKBCUANNADDQMQ0ANoGFAC5oAUGgBwNAhRQIWgBaAENACGgBtAwoAKAFzQIXNABmgAoAQ0DJKRoJQAtAhRQSLQIKYCUCCgBDQA00AMNAxpoGNoAM0AGaAFBoAcDQIcKBDqACgBDQAhoAQ0DEoAKADNAC5oEGaBhmgAoAkpGgUCCgQooELQIKYhKACgBKAGmgBpoGMNAxDQAlAwzQIUUAOFAhwoAcKBC0AJQAhoGNNACUAFABQAZoAXNABQAUAS0ixKACgQoNAhaYgoEFACUABoAaaAGmgYw0DENAxtABQAooEOFADhQIcKAFoASgBDQAhoGJQAUAFACUALQIKBi0CJDSLEoATNAhc0CHZpiCgQUAJQIKBjTQAhoGNNAxpoATFABigBQKAHCgBwoAWgAoAKACgBKAEoASgAoASgBaAAUAOoEONIoaTQAZoAUGmIUGgQtAgzQISgAoAQ0DGmgYlAxKACgAxQAtACigBRQAtAxaACgAoATFABigBCKAG0CEoAWgBRTAUUAf//Z" +const defaultVideoThumbB64 = + '/9j/4AAQSkZJRgABAQAAAQABAAD//gAXR2VuZXJhdGVkIGJ5IFNuaXBhc3Rl/9sAhAAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47AQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAF/APADAREAAhEBAxEB/8QBogAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLEAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+foBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxEAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDiAayNxwagBwNAC5oAM0xBmgBM0ANJoAjY0AQsaBkTGgCM0DEpAFAC0AFMBaACgAoEJTASgQlACUwCgQ4UAOFADhQA4UAOFADxQIkBqDQUGgBwagBQaBC5pgGaAELUAMLUARs1AETGgBhNAxhoASkAUALQIKYxaBBQAUwEoAQ0CEoASmAUAOoEKKAHCgBwoAeKAHigQ7NZmoZpgLmgBd1Ahd1ABupgNLUAMLUAMY0AMJoAYaAENACUCCgAoAWgAoAWgBKYCUAJQISgApgLQAooEOFACigB4oAeKBDxQAVmaiZpgGaAFzQAbqAE3UAIWpgNJoAYTQIaaAEoAQ0CEoASgBaACgBaACmAUAJQAlAgoAKYC0AKKBCigB4FADgKBDwKAHigBuazNRM0DEzTAM0AJmgAzQAhNAhpNACGmA2gQlACUCEoAKACgBaAFpgFACUAJQAUCCmAUALQIcBQA4CgB4FADgKBDhQA4UAMzWZqNzTGJQAZoATNABmgBKAEoEIaYCUCEoASgQlABQAtABQAtMBKACgAoEFABimAYoEKBQA4CgB4FADwKBDgKAFFADhQBCazNhKAEpgFACUAFACUAFAhDTAbQISgAoEJQAUALQAtMAoAKADFABigQYoAMUALimIUCgBwFAh4FADgKAHUALQAtAENZmwlACUwEoAKAEoAKACgQlMBpoEJQAUCCgBcUAFABTAXFAC4oAMUAGKBBigAxQIKYCigQ8UAOFADhQAtAC0ALQBDWZqJQMSgBKYBQAlABQISgBKYCGgQlAC0CCgBcUAFABTAUCkA7FMAxQAYoEJQAUCCmAooEOFADxQA4UAFAC0ALQBDWZqJQAlACUxhQAlABQIKAEoASmISgBcUCCgBaACgBcUAKBQAuKYC0CEoAQ0AJQISmAooEPFADhQA4UALQAtAC0AQ1maiUAFACUAJTAKAEoAKAEoAMUxBigAxQIWgAoAKAFAoAWgBaYBQIQ0ANNACUCCmIUUAOFADxQA4UALQAtABQBFWZqFACUAFACYpgFACUAFACUAFAgxTEFABQAUALQAooAWgAoAKYDTQIaaAEpiCgQ4UAOFAh4oGOFAC0ALSAKYEdZmglABQAUDDFACUwEoASgAoAKBBQIKYBQAUALQAtAC0AJQAhpgNJoENJoATNMQCgQ8UCHigB4oAWgYtABQAUAMrM0CgAoAKADFACUxiUAJQAlAgoAKYgoAKACgYtAC0AFAhDTAQmgBhNAhpNACZpiFBoEPFAEi0CHigB1ABQAUDEoAbWZoFABQAtABTAQ0ANNAxDQAlAhaAEpiCgAoGFAC0AFABmgBCaYhpNADCaBDSaBBmgABpiJFNAEimgB4NADqAFzQAlACE0AJWZoFAC0AFAC0wEIoAaaAG0AJQAUCCgApjCgAoAKADNABmgBpNMQ0mgBpNAhhNAgzQAoNADwaAHqaAJAaBDgaYC5oATNACZoAWszQKACgBaBDqYCGgBpoAYaBiUCCgBKYBQMKACgAoAM0AITQIaTQA0mmA0mgQ3NAhKAHCgBwNADwaAHg0AOBpiFzQAZoATNAD6zNAoAKAFoEOpgBoAaaAGGmAw0AJmgAzQMM0AGaADNABmgBM0AITQIaTQAhNMQw0AJQIKAFFADhQA4GgBwNADs0xC5oAM0CDNAEtZmoUCCgBaAHUwCgBppgRtQAw0ANzQAZoAM0AGaADNABmgBKAEoAQ0ANNMQhoEJQAlMBaQDgaAFBoAcDTAdmgQuaADNAgzQBPWZqFAgoAWgBaYC0CGmmBG1AyM0ANJoATNACZoAXNABmgAzQAUAJQAhoAQ0xDTQISmAUALQAUgHA0AKDTAdmgQuaBBQAtAFiszQKACgBaAFFMAoEIaYEbUDI2oAYaAEoASgAzQAuaACgAoAKAENMQ00AJTEFAhKACgAoAXNACg0AOBoAWgQtAC0AWazNAoAKACgBaYBQIQ0AMNMYw0AMIoAbQAlMAoAKACgAzSAKYhKAENACUxBQIKACgBKACgBaAHCgQ4UALQAUAWqzNAoAKACgApgFACGgQ00xjTQAwigBCKAG4pgJQAlABQAUCCgBKACgBKYgoEFABQISgAoAWgBRQA4UALQAUCLdZmoUAFABQAlMAoASgBDQA00wENACYoATFMBpFADSKAEoEJQAUAFABQAlMQtAgoASgQUAJQAUAKKAHCgBaBBQBbrM1CgAoAKACmAUAJQAlADaYBQAlACYpgIRQA0igBpFAhtABQAUAFMAoEFABQIKAEoASgQUALQAooAWgQUAW81mbC0CCgApgFACUAIaAEpgJQAUAFABQAhFMBpFADSKAGkUCExQAYoAMUAGKADFMQYoAMUCExSATFABQIKYBQAtABQIt5qDYM0ALmgQtIApgIaAENADaACmAlAC0ALQAUwGkUANIoAaRQAmKBBigAxQAYoAMUAGKBBigBMUAJigQmKAExTAKBC0AFAFnNQaig0AKDQAtAgoASgBDQAlMBKACgAFADhQAtMBCKAGkUAIRQAmKADFABigQmKADFACYoAXFABigQmKAExQAmKBCYpgJigAoAnzUGgZoAcDQAuaBC0AJQAhoASmAlABQAtADhQAtMAoATFACEUAJigAxQAYoATFAhMUAFABQAuKADFABigBpWgBCKBCYpgJigB+ag0DNADgaBDgaAFzQITNACUAJTAKACgBRQAopgOoAWgBKAEoAKACgAoASgBpoEJQAooAWgBaBhigBMUCEIoAQigBMUAJSLCgBQaBDgaQC5oEFACUwCgBKACmAtADhQA4UALQAUAJQAUAJQAUAJQAhoENoAWgBRQAooGLQAUAGKAGkUAIRQIZSKEoGKKBDhQAUCCgAoAKBBQAUwFoGKKAHCgBaACgAoASgAoASgBCaAEoEJmgAoAUGgBQaAHZoGFABQAUANoAjpDEoAWgBaAFoEFACUALQAUCCmAUAOFAxRQAtAC0AJQAUAJQAmaBDSaAEzQAmaYBmgBQaAHA0gFzQAuaBhmgAzQAlAEdIYUALQAtAgoAKAEoEFAC0AFMAoAUUDFFAC0ALQAUAJQAhoENNACE0wEoATNABmgBc0ALmgBc0gDNAC5oATNABmgBKRQlACigB1AgoASgQlABTAWgBKACgBaBi0ALQAZoAM0AFACGgQ00wENACUAJQAUCFzQMM0ALmgAzQAZoAM0AGaQC0igoAUUALQIWgBDQISmAUAFACUAFABQAuaBi5oAM0AGaBBmgBKAEpgIaAG0AJQAUCFoAM0DDNAC5oATNABmgAzQBJUlBQAooAWgQtACGmIaaACgAoASgBKACgBc0DCgQUAGaADNABTASgBDQAlACUAFAgoAKBhQAUAFABQAlAE1SUFAxRQIWgQtMBDQIQ0AJQAlAhKBiUAFABmgBc0AGaADNABTAKACgBKAEoASgQlABQAUAFAC0AFACUAFAE1SaBQAUCHCgQtMBKBCUAJQISgBDQA00DEzQAuaADNMBc0AGaADNABQAUAJQAlABQISgAoAKACgBaACgBKAEoAnqTQSgBRQIcKBC0xCUAJQISgBKAENADDQAmaYwzQAuaADNAC0AFABQAUAFAhKACgBKACgAoAWgAoELQAlAxKAJqk0EoAWgQooELTEFADaBCUABoENNMY00ANNAwzQAZoAXNAC0AFAC0CFoASgAoASgBKACgAoAWgQtABQAUANNAyWpNAoAKBCimIWgQUCEoASmIQ0ANNADTQMaaAEoGLmgAzQAtADhQIWgBaACgQhoASgYlACUALQIWgBaACgBKAENAyWpNBKYBQIcKBC0CEoEJTAKBCUANNADDQMQ0ANoGFAC5oAUGgBwNAhRQIWgBaAENACGgBtAwoAKAFzQIXNABmgAoAQ0DJKRoJQAtAhRQSLQIKYCUCCgBDQA00AMNAxpoGNoAM0AGaAFBoAcDQIcKBDqACgBDQAhoAQ0DEoAKADNAC5oEGaBhmgAoAkpGgUCCgQooELQIKYhKACgBKAGmgBpoGMNAxDQAlAwzQIUUAOFAhwoAcKBC0AJQAhoGNNACUAFABQAZoAXNABQAUAS0ixKACgQoNAhaYgoEFACUABoAaaAGmgYw0DENAxtABQAooEOFADhQIcKAFoASgBDQAhoGJQAUAFACUALQIKBi0CJDSLEoATNAhc0CHZpiCgQUAJQIKBjTQAhoGNNAxpoATFABigBQKAHCgBwoAWgAoAKACgBKAEoASgAoASgBaAAUAOoEONIoaTQAZoAUGmIUGgQtAgzQISgAoAQ0DGmgYlAxKACgAxQAtACigBRQAtAxaACgAoATFABigBCKAG0CEoAWgBRTAUUAf//Z' export const defaultVideoThumb = Buffer.from(defaultVideoThumbB64, 'base64') export async function getVideoInfo(filePath: string) { - const size = fs.statSync(filePath).size; - return new Promise<{ - width: number, - height: number, - time: number, - format: string, - size: number, - filePath: string - }>((resolve, reject) => { - let ffmpegPath = getConfigUtil().getConfig().ffmpeg - ffmpegPath && ffmpeg.setFfmpegPath(ffmpegPath) - ffmpeg(filePath).ffprobe((err, metadata) => { - if (err) { - reject(err); - } else { - const videoStream = metadata.streams.find(s => s.codec_type === 'video'); - if (videoStream) { - console.log(`视频尺寸: ${videoStream.width}x${videoStream.height}`); - } else { - console.log('未找到视频流信息。'); - } - resolve({ - width: videoStream.width, height: videoStream.height, - time: parseInt(videoStream.duration), - format: metadata.format.format_name, - size, - filePath - }); - } - }); + const size = fs.statSync(filePath).size + return new Promise<{ + width: number + height: number + time: number + format: string + size: number + filePath: string + }>((resolve, reject) => { + let ffmpegPath = getConfigUtil().getConfig().ffmpeg + ffmpegPath && ffmpeg.setFfmpegPath(ffmpegPath) + ffmpeg(filePath).ffprobe((err, metadata) => { + if (err) { + reject(err) + } else { + const videoStream = metadata.streams.find((s) => s.codec_type === 'video') + if (videoStream) { + console.log(`视频尺寸: ${videoStream.width}x${videoStream.height}`) + } else { + console.log('未找到视频流信息。') + } + resolve({ + width: videoStream.width, + height: videoStream.height, + time: parseInt(videoStream.duration), + format: metadata.format.format_name, + size, + filePath, + }) + } }) + }) } export async function encodeMp4(filePath: string) { - let videoInfo = await getVideoInfo(filePath); - log("视频信息", videoInfo) - if (videoInfo.format.indexOf("mp4") === -1) { - log("视频需要转换为MP4格式", filePath) - // 转成mp4 - const newPath: string = await new Promise((resolve, reject) => { - const newPath = filePath + ".mp4" - ffmpeg(filePath) - .toFormat('mp4') - .on('error', (err) => { - reject(`转换视频格式失败: ${err.message}`); - }) - .on('end', () => { - log('视频转换为MP4格式完成'); - resolve(newPath); // 返回转换后的文件路径 - }) - .save(newPath); - }); - return await getVideoInfo(newPath) - } - return videoInfo + let videoInfo = await getVideoInfo(filePath) + log('视频信息', videoInfo) + if (videoInfo.format.indexOf('mp4') === -1) { + log('视频需要转换为MP4格式', filePath) + // 转成mp4 + const newPath: string = await new Promise((resolve, reject) => { + const newPath = filePath + '.mp4' + ffmpeg(filePath) + .toFormat('mp4') + .on('error', (err) => { + reject(`转换视频格式失败: ${err.message}`) + }) + .on('end', () => { + log('视频转换为MP4格式完成') + resolve(newPath) // 返回转换后的文件路径 + }) + .save(newPath) + }) + return await getVideoInfo(newPath) + } + return videoInfo } export function checkFfmpeg(newPath: string = null): Promise { - return new Promise((resolve, reject) => { - log("开始检查ffmpeg", newPath); - if (newPath) { - ffmpeg.setFfmpegPath(newPath); + return new Promise((resolve, reject) => { + log('开始检查ffmpeg', newPath) + if (newPath) { + ffmpeg.setFfmpegPath(newPath) + } + try { + ffmpeg.getAvailableFormats((err, formats) => { + if (err) { + log('ffmpeg is not installed or not found in PATH:', err) + resolve(false) + } else { + log('ffmpeg is installed.') + resolve(true) } - try { - ffmpeg.getAvailableFormats((err, formats) => { - if (err) { - log('ffmpeg is not installed or not found in PATH:', err); - resolve(false) - } else { - log('ffmpeg is installed.'); - resolve(true); - } - }) - } catch (e) { - resolve(false); - } - }); -} \ No newline at end of file + }) + } catch (e) { + resolve(false) + } + }) +} diff --git a/src/main/ipcsend.ts b/src/main/ipcsend.ts index 37d04b9..8efc722 100644 --- a/src/main/ipcsend.ts +++ b/src/main/ipcsend.ts @@ -1,13 +1,12 @@ -import {webContents} from 'electron'; - +import { webContents } from 'electron' function sendIPCMsg(channel: string, ...data: any) { - let contents = webContents.getAllWebContents(); - for (const content of contents) { - try { - content.send(channel, ...data) - } catch (e) { - console.log("llonebot send ipc msg to render error:", e) - } + let contents = webContents.getAllWebContents() + for (const content of contents) { + try { + content.send(channel, ...data) + } catch (e) { + console.log('llonebot send ipc msg to render error:', e) } + } } diff --git a/src/main/main.ts b/src/main/main.ts index e7c6d2e..14d0cf8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,485 +1,499 @@ // 运行在 Electron 主进程 下的插件入口 -import {BrowserWindow, dialog, ipcMain} from 'electron'; -import * as fs from 'node:fs'; -import {Config} from "../common/types"; +import { BrowserWindow, dialog, ipcMain } from 'electron' +import * as fs from 'node:fs' +import { Config } from '../common/types' import { - CHANNEL_CHECK_VERSION, - CHANNEL_ERROR, - CHANNEL_GET_CONFIG, - CHANNEL_LOG, - CHANNEL_SELECT_FILE, - CHANNEL_SET_CONFIG, - CHANNEL_UPDATE, -} from "../common/channels"; -import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer"; -import {DATA_DIR} from "../common/utils"; + CHANNEL_CHECK_VERSION, + CHANNEL_ERROR, + CHANNEL_GET_CONFIG, + CHANNEL_LOG, + CHANNEL_SELECT_FILE, + CHANNEL_SET_CONFIG, + CHANNEL_UPDATE, +} from '../common/channels' +import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer' +import { DATA_DIR } from '../common/utils' import { - friendRequests, - getFriend, - getGroup, - getGroupMember, groups, - llonebotError, - refreshGroupMembers, - selfInfo, - uidMaps -} from "../common/data"; -import {hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook} from "../ntqqapi/hook"; -import {OB11Constructor} from "../onebot11/constructor"; + friendRequests, + getFriend, + getGroup, + getGroupMember, + groups, + llonebotError, + refreshGroupMembers, + selfInfo, + uidMaps, +} from '../common/data' +import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook } from '../ntqqapi/hook' +import { OB11Constructor } from '../onebot11/constructor' import { - ChatType, - FriendRequestNotify, - GroupMemberRole, - GroupNotifies, - GroupNotifyTypes, - RawMessage -} from "../ntqqapi/types"; -import {httpHeart, ob11HTTPServer} from "../onebot11/server/http"; -import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent"; -import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent"; -import {postOB11Event} from "../onebot11/server/postOB11Event"; -import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket"; -import {OB11GroupAdminNoticeEvent} from "../onebot11/event/notice/OB11GroupAdminNoticeEvent"; -import {OB11GroupRequestEvent} from "../onebot11/event/request/OB11GroupRequest"; -import {OB11FriendRequestEvent} from "../onebot11/event/request/OB11FriendRequest"; -import * as path from "node:path"; -import {dbUtil} from "../common/db"; -import {setConfig} from "./setConfig"; -import {NTQQUserApi} from "../ntqqapi/api/user"; -import {NTQQGroupApi} from "../ntqqapi/api/group"; -import {crychic} from "../ntqqapi/external/crychic"; -import {OB11FriendPokeEvent, OB11GroupPokeEvent} from "../onebot11/event/notice/OB11PokeEvent"; -import {checkNewVersion, upgradeLLOneBot} from "../common/utils/upgrade"; -import {log} from "../common/utils/log"; -import {getConfigUtil} from "../common/config"; -import {checkFfmpeg} from "../common/utils/video"; -import {GroupDecreaseSubType, OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent"; + ChatType, + FriendRequestNotify, + GroupMemberRole, + GroupNotifies, + GroupNotifyTypes, + RawMessage, +} from '../ntqqapi/types' +import { httpHeart, ob11HTTPServer } from '../onebot11/server/http' +import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent' +import { OB11GroupRecallNoticeEvent } from '../onebot11/event/notice/OB11GroupRecallNoticeEvent' +import { postOB11Event } from '../onebot11/server/postOB11Event' +import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket' +import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent' +import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest' +import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest' +import * as path from 'node:path' +import { dbUtil } from '../common/db' +import { setConfig } from './setConfig' +import { NTQQUserApi } from '../ntqqapi/api/user' +import { NTQQGroupApi } from '../ntqqapi/api/group' +import { crychic } from '../ntqqapi/external/crychic' +import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent' +import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade' +import { log } from '../common/utils/log' +import { getConfigUtil } from '../common/config' +import { checkFfmpeg } from '../common/utils/video' +import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' -let running = false; +let running = false -let mainWindow: BrowserWindow | null = null; +let mainWindow: BrowserWindow | null = null // 加载插件时触发 function onLoad() { - log("llonebot main onLoad"); - ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => { - return checkNewVersion(); - }); - ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => { - return upgradeLLOneBot(); - }); - ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => { - const selectPath = new Promise((resolve, reject) => { - dialog - .showOpenDialog({ - title: "请选择ffmpeg", - properties: ["openFile"], - buttonLabel: "确定", - }) - .then((result) => { - log("选择文件", result); - if (!result.canceled) { - const _selectPath = path.join(result.filePaths[0]); - resolve(_selectPath); - // let config = getConfigUtil().getConfig() - // config.ffmpeg = path.join(result.filePaths[0]); - // getConfigUtil().setConfig(config); - } - resolve("") - }) - .catch((err) => { - reject(err); - }); + log('llonebot main onLoad') + ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => { + return checkNewVersion() + }) + ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => { + return upgradeLLOneBot() + }) + ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => { + const selectPath = new Promise((resolve, reject) => { + dialog + .showOpenDialog({ + title: '请选择ffmpeg', + properties: ['openFile'], + buttonLabel: '确定', + }) + .then((result) => { + log('选择文件', result) + if (!result.canceled) { + const _selectPath = path.join(result.filePaths[0]) + resolve(_selectPath) + // let config = getConfigUtil().getConfig() + // config.ffmpeg = path.join(result.filePaths[0]); + // getConfigUtil().setConfig(config); + } + resolve('') + }) + .catch((err) => { + reject(err) }) - try { - return await selectPath; - } catch (e) { - log("选择文件出错", e) - return "" - } }) - if (!fs.existsSync(DATA_DIR)) { - fs.mkdirSync(DATA_DIR, {recursive: true}); + try { + return await selectPath + } catch (e) { + log('选择文件出错', e) + return '' } - ipcMain.handle(CHANNEL_ERROR, async (event, arg) => { - const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg) - llonebotError.ffmpegError = ffmpegOk ? "" : "没有找到ffmpeg,音频只能发送wav和silk,视频尺寸可能异常" - let {httpServerError, wsServerError, otherError, ffmpegError} = llonebotError; - let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}` - error = error.replace("\n\n", "\n") - error = error.trim(); - log("查询llonebot错误信息", error); - return error; - }) - ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => { - const config = getConfigUtil().getConfig() - return config; - }) - ipcMain.on(CHANNEL_SET_CONFIG, (event, ask: boolean, config: Config) => { - if (!ask) { - setConfig(config).then().catch(e => { - log("保存设置失败", e.stack) - }); - return - } - dialog.showMessageBox(mainWindow, { - type: 'question', - buttons: ['确认', '取消'], - defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认" - title: '确认保存', - message: '是否保存?', - detail: 'LLOneBot配置已更改,是否保存?' - }).then(result => { - if (result.response === 0) { - setConfig(config).then().catch(e => { - log("保存设置失败", e.stack) - }); - } else { - } - }).catch(err => { - log("保存设置询问弹窗错误", err); - }); - }) - - ipcMain.on(CHANNEL_LOG, (event, arg) => { - log(arg); - }) - - async function postReceiveMsg(msgList: RawMessage[]) { - const {debug, reportSelfMessage} = getConfigUtil().getConfig(); - for (let message of msgList) { - // 过滤启动之前的消息 - if (parseInt(message.msgTime) < startTime / 1000) { - continue; - } - // log("收到新消息", message.msgId, message.msgSeq) - // if (message.senderUin !== selfInfo.uin){ - message.msgShortId = await dbUtil.addMsg(message); - // } - - OB11Constructor.message(message).then((msg) => { - if (debug) { - msg.raw = message; - } else { - if (msg.message.length === 0) { - return - } - } - const isSelfMsg = msg.user_id.toString() == selfInfo.uin - if (isSelfMsg && !reportSelfMessage) { - return - } - if (isSelfMsg) { - msg.target_id = parseInt(message.peerUin); - } - postOB11Event(msg); - // log("post msg", msg) - }).catch(e => log("constructMessage error: ", e.stack.toString())); - OB11Constructor.GroupEvent(message).then(groupEvent => { - if (groupEvent) { - // log("post group event", groupEvent); - postOB11Event(groupEvent); - } + }) + if (!fs.existsSync(DATA_DIR)) { + fs.mkdirSync(DATA_DIR, { recursive: true }) + } + ipcMain.handle(CHANNEL_ERROR, async (event, arg) => { + const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg) + llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到ffmpeg,音频只能发送wav和silk,视频尺寸可能异常' + let { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError + let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}` + error = error.replace('\n\n', '\n') + error = error.trim() + log('查询llonebot错误信息', error) + return error + }) + ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => { + const config = getConfigUtil().getConfig() + return config + }) + ipcMain.on(CHANNEL_SET_CONFIG, (event, ask: boolean, config: Config) => { + if (!ask) { + setConfig(config) + .then() + .catch((e) => { + log('保存设置失败', e.stack) + }) + return + } + dialog + .showMessageBox(mainWindow, { + type: 'question', + buttons: ['确认', '取消'], + defaultId: 0, // 默认选中的按钮,0 代表第一个按钮,即 "确认" + title: '确认保存', + message: '是否保存?', + detail: 'LLOneBot配置已更改,是否保存?', + }) + .then((result) => { + if (result.response === 0) { + setConfig(config) + .then() + .catch((e) => { + log('保存设置失败', e.stack) }) - } - } - - async function startReceiveHook() { - if (getConfigUtil().getConfig().enablePoke) { - crychic.loadNode() - crychic.registerPokeHandler((id, isGroup) => { - log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`) - let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent; - if (isGroup) { - pokeEvent = new OB11GroupPokeEvent(parseInt(id)); - } else { - pokeEvent = new OB11FriendPokeEvent(parseInt(id)); - } - postOB11Event(pokeEvent); - }) - } - registerReceiveHook<{ - msgList: Array - }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => { - try { - await postReceiveMsg(payload.msgList); - } catch (e) { - log("report message error: ", e.stack.toString()); - } - }) - registerReceiveHook<{ msgList: Array }>([ReceiveCmdS.UPDATE_MSG], async (payload) => { - for (const message of payload.msgList) { - // log("message update", message.sendStatus, message.msgId, message.msgSeq) - if (message.recallTime != "0") { //todo: 这个判断方法不太好,应该使用灰色消息元素来判断 - // 撤回消息上报 - const oriMessage = await dbUtil.getMsgByLongId(message.msgId) - if (!oriMessage) { - continue - } - oriMessage.recallTime = message.recallTime - dbUtil.updateMsg(oriMessage).then(); - if (message.chatType == ChatType.friend) { - const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message.senderUin), oriMessage.msgShortId); - postOB11Event(friendRecallEvent); - } else if (message.chatType == ChatType.group) { - let operatorId = message.senderUin - for (const element of message.elements) { - const operatorUid = element.grayTipElement?.revokeElement.operatorUid - const operator = await getGroupMember(message.peerUin, operatorUid) - operatorId = operator.uin - } - const groupRecallEvent = new OB11GroupRecallNoticeEvent( - parseInt(message.peerUin), - parseInt(message.senderUin), - parseInt(operatorId), - oriMessage.msgShortId - ) - postOB11Event(groupRecallEvent); - } - // 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了 - continue - } - dbUtil.updateMsg(message).then(); - } - }) - registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, async (payload) => { - const {reportSelfMessage} = getConfigUtil().getConfig(); - if (!reportSelfMessage) { - return - } - // log("reportSelfMessage", payload) - try { - await postReceiveMsg([payload.msgRecord]); - } catch (e) { - log("report self message error: ", e.stack.toString()); - } - }) - registerReceiveHook<{ - "doubt": boolean, - "oldestUnreadSeq": string, - "unreadCount": number - }>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => { - if (payload.unreadCount) { - // log("开始获取群通知详情") - let notify: GroupNotifies; - try { - notify = await NTQQGroupApi.getGroupNotifies(); - } catch (e) { - // log("获取群通知详情失败", e); - return - } - - const notifies = notify.notifies.slice(0, payload.unreadCount) - // log("获取群通知详情完成", notifies, payload); - - for (const notify of notifies) { - try { - notify.time = Date.now(); - // const notifyTime = parseInt(notify.seq) / 1000 - // log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`); - // if (notifyTime < startTime) { - // continue; - // } - let existNotify = await dbUtil.getGroupNotify(notify.seq); - if (existNotify) { - continue - } - log("收到群通知", notify); - await dbUtil.addGroupNotify(notify); - // let member2: GroupMember; - // if (notify.user2.uid) { - // member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid); - // } - if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET].includes(notify.type)) { - const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid); - log("有管理员变动通知"); - refreshGroupMembers(notify.group.groupCode).then() - let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent() - groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode); - log("开始获取变动的管理员") - if (member1) { - log("变动管理员获取成功") - groupAdminNoticeEvent.user_id = parseInt(member1.uin); - groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? "unset" : "set"; - // member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal; - postOB11Event(groupAdminNoticeEvent, true); - } else { - log("获取群通知的成员信息失败", notify, getGroup(notify.group.groupCode)); - } - } else if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) { - log("有成员退出通知", notify); - try { - const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid); - let operatorId = member1.uin; - let subType: GroupDecreaseSubType = "leave"; - if (notify.user2.uid) { - // 是被踢的 - const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid); - operatorId = member2.uin; - subType = "kick"; - } - let groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(notify.group.groupCode), parseInt(member1.uin), parseInt(operatorId), subType) - postOB11Event(groupDecreaseEvent, true); - } catch (e) { - log("获取群通知的成员信息失败", notify, e.stack.toString()) - } - } else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) { - log("有加群请求"); - let groupRequestEvent = new OB11GroupRequestEvent(); - groupRequestEvent.group_id = parseInt(notify.group.groupCode); - let requestQQ = "" - try { - requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin; - } catch (e) { - log("获取加群人QQ号失败", e) - } - groupRequestEvent.user_id = parseInt(requestQQ) || 0; - groupRequestEvent.sub_type = "add" - groupRequestEvent.comment = notify.postscript; - groupRequestEvent.flag = notify.seq; - postOB11Event(groupRequestEvent); - } else if (notify.type == GroupNotifyTypes.INVITE_ME) { - log("收到邀请我加群通知") - let groupInviteEvent = new OB11GroupRequestEvent(); - groupInviteEvent.group_id = parseInt(notify.group.groupCode); - let user_id = (await getFriend(notify.user2.uid))?.uin - if (!user_id) { - user_id = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin - } - groupInviteEvent.user_id = parseInt(user_id); - groupInviteEvent.sub_type = "invite"; - groupInviteEvent.flag = notify.seq; - postOB11Event(groupInviteEvent); - } - } catch (e) { - log("解析群通知失败", e.stack.toString()); - } - } - - } else if (payload.doubt) { - // 可能有群管理员变动 - } - }) - - registerReceiveHook(ReceiveCmdS.FRIEND_REQUEST, async (payload) => { - for (const req of payload.data.buddyReqs) { - let flag = req.friendUid + req.reqTime; - if (req.isUnread && (parseInt(req.reqTime) > startTime / 1000)) { - friendRequests[flag] = req; - log("有新的好友请求", req); - let friendRequestEvent = new OB11FriendRequestEvent(); - try { - let requester = await NTQQUserApi.getUserDetailInfo(req.friendUid) - friendRequestEvent.user_id = parseInt(requester.uin); - } catch (e) { - log("获取加好友者QQ号失败", e); - } - friendRequestEvent.flag = flag; - friendRequestEvent.comment = req.extWords; - postOB11Event(friendRequestEvent); - } - } - }) - } - - let startTime = 0; // 毫秒 - - async function start() { - log("llonebot pid", process.pid) - llonebotError.otherError = ""; - startTime = Date.now(); - dbUtil.getReceivedTempUinMap().then(m => { - for (const [key, value] of Object.entries(m)) { - uidMaps[value] = key; - } - }) - startReceiveHook().then(); - NTQQGroupApi.getGroups(true).then() - const config = getConfigUtil().getConfig() - if (config.ob11.enableHttp) { - ob11HTTPServer.start(config.ob11.httpPort) - } - if (config.ob11.enableWs) { - ob11WebsocketServer.start(config.ob11.wsPort); - } - if (config.ob11.enableWsReverse) { - ob11ReverseWebsockets.start(); - } - if (config.ob11.enableHttpHeart){ - httpHeart.start(); - } - - log("LLOneBot start") - } - - let getSelfNickCount = 0; - const init = async () => { - try { - log("start get self info") - const _ = await NTQQUserApi.getSelfInfo(); - log("get self info api result:", _); - Object.assign(selfInfo, _); - selfInfo.nick = selfInfo.uin; - } catch (e) { - log("retry get self info", e); - } - if (!selfInfo.uin) { - selfInfo.uin = globalThis.authData?.uin; - selfInfo.uid = globalThis.authData?.uid; - selfInfo.nick = selfInfo.uin; - } - log("self info", selfInfo, globalThis.authData); - if (selfInfo.uin) { - async function getUserNick() { - try { - getSelfNickCount++; - const userInfo = (await NTQQUserApi.getUserDetailInfo(selfInfo.uid)); - log("self info", userInfo); - if (userInfo) { - selfInfo.nick = userInfo.nick; - return - } - } catch (e) { - log("get self nickname failed", e.stack); - } - if (getSelfNickCount < 10) { - return setTimeout(getUserNick, 1000); - } - } - - getUserNick().then() - start().then(); } else { - setTimeout(init, 1000) } - } - setTimeout(init, 1000); -} + }) + .catch((err) => { + log('保存设置询问弹窗错误', err) + }) + }) + ipcMain.on(CHANNEL_LOG, (event, arg) => { + log(arg) + }) + + async function postReceiveMsg(msgList: RawMessage[]) { + const { debug, reportSelfMessage } = getConfigUtil().getConfig() + for (let message of msgList) { + // 过滤启动之前的消息 + if (parseInt(message.msgTime) < startTime / 1000) { + continue + } + // log("收到新消息", message.msgId, message.msgSeq) + // if (message.senderUin !== selfInfo.uin){ + message.msgShortId = await dbUtil.addMsg(message) + // } + + OB11Constructor.message(message) + .then((msg) => { + if (debug) { + msg.raw = message + } else { + if (msg.message.length === 0) { + return + } + } + const isSelfMsg = msg.user_id.toString() == selfInfo.uin + if (isSelfMsg && !reportSelfMessage) { + return + } + if (isSelfMsg) { + msg.target_id = parseInt(message.peerUin) + } + postOB11Event(msg) + // log("post msg", msg) + }) + .catch((e) => log('constructMessage error: ', e.stack.toString())) + OB11Constructor.GroupEvent(message).then((groupEvent) => { + if (groupEvent) { + // log("post group event", groupEvent); + postOB11Event(groupEvent) + } + }) + } + } + + async function startReceiveHook() { + if (getConfigUtil().getConfig().enablePoke) { + crychic.loadNode() + crychic.registerPokeHandler((id, isGroup) => { + log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`) + let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent + if (isGroup) { + pokeEvent = new OB11GroupPokeEvent(parseInt(id)) + } else { + pokeEvent = new OB11FriendPokeEvent(parseInt(id)) + } + postOB11Event(pokeEvent) + }) + } + registerReceiveHook<{ + msgList: Array + }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => { + try { + await postReceiveMsg(payload.msgList) + } catch (e) { + log('report message error: ', e.stack.toString()) + } + }) + registerReceiveHook<{ msgList: Array }>([ReceiveCmdS.UPDATE_MSG], async (payload) => { + for (const message of payload.msgList) { + // log("message update", message.sendStatus, message.msgId, message.msgSeq) + if (message.recallTime != '0') { + //todo: 这个判断方法不太好,应该使用灰色消息元素来判断 + // 撤回消息上报 + const oriMessage = await dbUtil.getMsgByLongId(message.msgId) + if (!oriMessage) { + continue + } + oriMessage.recallTime = message.recallTime + dbUtil.updateMsg(oriMessage).then() + if (message.chatType == ChatType.friend) { + const friendRecallEvent = new OB11FriendRecallNoticeEvent( + parseInt(message.senderUin), + oriMessage.msgShortId, + ) + postOB11Event(friendRecallEvent) + } else if (message.chatType == ChatType.group) { + let operatorId = message.senderUin + for (const element of message.elements) { + const operatorUid = element.grayTipElement?.revokeElement.operatorUid + const operator = await getGroupMember(message.peerUin, operatorUid) + operatorId = operator.uin + } + const groupRecallEvent = new OB11GroupRecallNoticeEvent( + parseInt(message.peerUin), + parseInt(message.senderUin), + parseInt(operatorId), + oriMessage.msgShortId, + ) + postOB11Event(groupRecallEvent) + } + // 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了 + continue + } + dbUtil.updateMsg(message).then() + } + }) + registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, async (payload) => { + const { reportSelfMessage } = getConfigUtil().getConfig() + if (!reportSelfMessage) { + return + } + // log("reportSelfMessage", payload) + try { + await postReceiveMsg([payload.msgRecord]) + } catch (e) { + log('report self message error: ', e.stack.toString()) + } + }) + registerReceiveHook<{ + doubt: boolean + oldestUnreadSeq: string + unreadCount: number + }>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => { + if (payload.unreadCount) { + // log("开始获取群通知详情") + let notify: GroupNotifies + try { + notify = await NTQQGroupApi.getGroupNotifies() + } catch (e) { + // log("获取群通知详情失败", e); + return + } + + const notifies = notify.notifies.slice(0, payload.unreadCount) + // log("获取群通知详情完成", notifies, payload); + + for (const notify of notifies) { + try { + notify.time = Date.now() + // const notifyTime = parseInt(notify.seq) / 1000 + // log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`); + // if (notifyTime < startTime) { + // continue; + // } + let existNotify = await dbUtil.getGroupNotify(notify.seq) + if (existNotify) { + continue + } + log('收到群通知', notify) + await dbUtil.addGroupNotify(notify) + // let member2: GroupMember; + // if (notify.user2.uid) { + // member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid); + // } + if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET].includes(notify.type)) { + const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid) + log('有管理员变动通知') + refreshGroupMembers(notify.group.groupCode).then() + let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent() + groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode) + log('开始获取变动的管理员') + if (member1) { + log('变动管理员获取成功') + groupAdminNoticeEvent.user_id = parseInt(member1.uin) + groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? 'unset' : 'set' + // member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal; + postOB11Event(groupAdminNoticeEvent, true) + } else { + log('获取群通知的成员信息失败', notify, getGroup(notify.group.groupCode)) + } + } else if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) { + log('有成员退出通知', notify) + try { + const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid) + let operatorId = member1.uin + let subType: GroupDecreaseSubType = 'leave' + if (notify.user2.uid) { + // 是被踢的 + const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid) + operatorId = member2.uin + subType = 'kick' + } + let groupDecreaseEvent = new OB11GroupDecreaseEvent( + parseInt(notify.group.groupCode), + parseInt(member1.uin), + parseInt(operatorId), + subType, + ) + postOB11Event(groupDecreaseEvent, true) + } catch (e) { + log('获取群通知的成员信息失败', notify, e.stack.toString()) + } + } else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) { + log('有加群请求') + let groupRequestEvent = new OB11GroupRequestEvent() + groupRequestEvent.group_id = parseInt(notify.group.groupCode) + let requestQQ = '' + try { + requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin + } catch (e) { + log('获取加群人QQ号失败', e) + } + groupRequestEvent.user_id = parseInt(requestQQ) || 0 + groupRequestEvent.sub_type = 'add' + groupRequestEvent.comment = notify.postscript + groupRequestEvent.flag = notify.seq + postOB11Event(groupRequestEvent) + } else if (notify.type == GroupNotifyTypes.INVITE_ME) { + log('收到邀请我加群通知') + let groupInviteEvent = new OB11GroupRequestEvent() + groupInviteEvent.group_id = parseInt(notify.group.groupCode) + let user_id = (await getFriend(notify.user2.uid))?.uin + if (!user_id) { + user_id = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin + } + groupInviteEvent.user_id = parseInt(user_id) + groupInviteEvent.sub_type = 'invite' + groupInviteEvent.flag = notify.seq + postOB11Event(groupInviteEvent) + } + } catch (e) { + log('解析群通知失败', e.stack.toString()) + } + } + } else if (payload.doubt) { + // 可能有群管理员变动 + } + }) + + registerReceiveHook(ReceiveCmdS.FRIEND_REQUEST, async (payload) => { + for (const req of payload.data.buddyReqs) { + let flag = req.friendUid + req.reqTime + if (req.isUnread && parseInt(req.reqTime) > startTime / 1000) { + friendRequests[flag] = req + log('有新的好友请求', req) + let friendRequestEvent = new OB11FriendRequestEvent() + try { + let requester = await NTQQUserApi.getUserDetailInfo(req.friendUid) + friendRequestEvent.user_id = parseInt(requester.uin) + } catch (e) { + log('获取加好友者QQ号失败', e) + } + friendRequestEvent.flag = flag + friendRequestEvent.comment = req.extWords + postOB11Event(friendRequestEvent) + } + } + }) + } + + let startTime = 0 // 毫秒 + + async function start() { + log('llonebot pid', process.pid) + llonebotError.otherError = '' + startTime = Date.now() + dbUtil.getReceivedTempUinMap().then((m) => { + for (const [key, value] of Object.entries(m)) { + uidMaps[value] = key + } + }) + startReceiveHook().then() + NTQQGroupApi.getGroups(true).then() + const config = getConfigUtil().getConfig() + if (config.ob11.enableHttp) { + ob11HTTPServer.start(config.ob11.httpPort) + } + if (config.ob11.enableWs) { + ob11WebsocketServer.start(config.ob11.wsPort) + } + if (config.ob11.enableWsReverse) { + ob11ReverseWebsockets.start() + } + if (config.ob11.enableHttpHeart) { + httpHeart.start() + } + + log('LLOneBot start') + } + + let getSelfNickCount = 0 + const init = async () => { + try { + log('start get self info') + const _ = await NTQQUserApi.getSelfInfo() + log('get self info api result:', _) + Object.assign(selfInfo, _) + selfInfo.nick = selfInfo.uin + } catch (e) { + log('retry get self info', e) + } + if (!selfInfo.uin) { + selfInfo.uin = globalThis.authData?.uin + selfInfo.uid = globalThis.authData?.uid + selfInfo.nick = selfInfo.uin + } + log('self info', selfInfo, globalThis.authData) + if (selfInfo.uin) { + async function getUserNick() { + try { + getSelfNickCount++ + const userInfo = await NTQQUserApi.getUserDetailInfo(selfInfo.uid) + log('self info', userInfo) + if (userInfo) { + selfInfo.nick = userInfo.nick + return + } + } catch (e) { + log('get self nickname failed', e.stack) + } + if (getSelfNickCount < 10) { + return setTimeout(getUserNick, 1000) + } + } + + getUserNick().then() + start().then() + } else { + setTimeout(init, 1000) + } + } + setTimeout(init, 1000) +} // 创建窗口时触发 function onBrowserWindowCreated(window: BrowserWindow) { - if (selfInfo.uid) { - return - } - mainWindow = window; - log("window create", window.webContents.getURL().toString()) - try { - hookNTQQApiCall(window); - hookNTQQApiReceive(window); - } catch (e) { - log("LLOneBot hook error: ", e.toString()) - } + if (selfInfo.uid) { + return + } + mainWindow = window + log('window create', window.webContents.getURL().toString()) + try { + hookNTQQApiCall(window) + hookNTQQApiReceive(window) + } catch (e) { + log('LLOneBot hook error: ', e.toString()) + } } try { - onLoad(); + onLoad() } catch (e: any) { - console.log(e.toString()) + console.log(e.toString()) } - // 这两个函数都是可选的 -export { - onBrowserWindowCreated -} \ No newline at end of file +export { onBrowserWindowCreated } diff --git a/src/main/setConfig.ts b/src/main/setConfig.ts index 423ee80..58baaf6 100644 --- a/src/main/setConfig.ts +++ b/src/main/setConfig.ts @@ -1,68 +1,67 @@ -import {Config} from "../common/types"; -import {httpHeart, ob11HTTPServer} from "../onebot11/server/http"; -import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer"; -import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket"; -import {llonebotError} from "../common/data"; -import {getConfigUtil} from "../common/config"; -import {checkFfmpeg, log} from "../common/utils"; +import { Config } from '../common/types' +import { httpHeart, ob11HTTPServer } from '../onebot11/server/http' +import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer' +import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket' +import { llonebotError } from '../common/data' +import { getConfigUtil } from '../common/config' +import { checkFfmpeg, log } from '../common/utils' export async function setConfig(config: Config) { - let oldConfig = {...(getConfigUtil().getConfig())}; - getConfigUtil().setConfig(config) - if (config.ob11.httpPort != oldConfig.ob11.httpPort && config.ob11.enableHttp) { - ob11HTTPServer.restart(config.ob11.httpPort); - } - // 判断是否启用或关闭HTTP服务 - if (!config.ob11.enableHttp) { - ob11HTTPServer.stop(); + let oldConfig = { ...getConfigUtil().getConfig() } + getConfigUtil().setConfig(config) + if (config.ob11.httpPort != oldConfig.ob11.httpPort && config.ob11.enableHttp) { + ob11HTTPServer.restart(config.ob11.httpPort) + } + // 判断是否启用或关闭HTTP服务 + if (!config.ob11.enableHttp) { + ob11HTTPServer.stop() + } else { + ob11HTTPServer.start(config.ob11.httpPort) + } + // 正向ws端口变化,重启服务 + if (config.ob11.wsPort != oldConfig.ob11.wsPort) { + ob11WebsocketServer.restart(config.ob11.wsPort) + llonebotError.wsServerError = '' + } + // 判断是否启用或关闭正向ws + if (config.ob11.enableWs != oldConfig.ob11.enableWs) { + if (config.ob11.enableWs) { + ob11WebsocketServer.start(config.ob11.wsPort) } else { - ob11HTTPServer.start(config.ob11.httpPort); - } - // 正向ws端口变化,重启服务 - if (config.ob11.wsPort != oldConfig.ob11.wsPort) { - ob11WebsocketServer.restart(config.ob11.wsPort); - llonebotError.wsServerError = '' - } - // 判断是否启用或关闭正向ws - if (config.ob11.enableWs != oldConfig.ob11.enableWs) { - if (config.ob11.enableWs) { - ob11WebsocketServer.start(config.ob11.wsPort); - } else { - ob11WebsocketServer.stop(); - } - } - // 判断是否启用或关闭反向ws - if (config.ob11.enableWsReverse != oldConfig.ob11.enableWsReverse) { - if (config.ob11.enableWsReverse) { - ob11ReverseWebsockets.start(); - } else { - ob11ReverseWebsockets.stop(); - } + ob11WebsocketServer.stop() } + } + // 判断是否启用或关闭反向ws + if (config.ob11.enableWsReverse != oldConfig.ob11.enableWsReverse) { if (config.ob11.enableWsReverse) { - // 判断反向ws地址有变化 - if (config.ob11.wsHosts.length != oldConfig.ob11.wsHosts.length) { - log("反向ws地址有变化, 重启反向ws服务") - ob11ReverseWebsockets.restart(); - } else { - for (const newHost of config.ob11.wsHosts) { - if (!oldConfig.ob11.wsHosts.includes(newHost)) { - log("反向ws地址有变化, 重启反向ws服务") - ob11ReverseWebsockets.restart(); - break; - } - } + ob11ReverseWebsockets.start() + } else { + ob11ReverseWebsockets.stop() + } + } + if (config.ob11.enableWsReverse) { + // 判断反向ws地址有变化 + if (config.ob11.wsHosts.length != oldConfig.ob11.wsHosts.length) { + log('反向ws地址有变化, 重启反向ws服务') + ob11ReverseWebsockets.restart() + } else { + for (const newHost of config.ob11.wsHosts) { + if (!oldConfig.ob11.wsHosts.includes(newHost)) { + log('反向ws地址有变化, 重启反向ws服务') + ob11ReverseWebsockets.restart() + break } + } } - if (config.ob11.enableHttpHeart){ - // 启动http心跳 - httpHeart.start(); - } - else{ - // 关闭http心跳 - httpHeart.stop(); - } - log("old config", oldConfig) - log("配置已更新", config) - checkFfmpeg(config.ffmpeg).then() -} \ No newline at end of file + } + if (config.ob11.enableHttpHeart) { + // 启动http心跳 + httpHeart.start() + } else { + // 关闭http心跳 + httpHeart.stop() + } + log('old config', oldConfig) + log('配置已更新', config) + checkFfmpeg(config.ffmpeg).then() +} diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index bcebd52..75b49e6 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -1,4 +1,4 @@ -import {callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod} from "../ntcall"; +import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' import { CacheFileList, CacheFileListItem, @@ -7,20 +7,25 @@ import { ChatCacheList, ChatCacheListItemBasic, ChatType, - ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage -} from "../types"; -import path from "path"; -import fs from "fs"; -import {ReceiveCmdS} from "../hook"; -import {log} from "../../common/utils/log"; -import http from "http"; -import {sleep} from "../../common/utils"; -import {hookApi} from "../external/moehook/hook"; + ElementType, + IMAGE_HTTP_HOST, + IMAGE_HTTP_HOST_NT, + RawMessage, +} from '../types' +import path from 'path' +import fs from 'fs' +import { ReceiveCmdS } from '../hook' +import { log } from '../../common/utils/log' +import http from 'http' +import { sleep } from '../../common/utils' +import { hookApi } from '../external/moehook/hook' export class NTQQFileApi { static async getFileType(filePath: string) { return await callNTQQApi<{ ext: string }>({ - className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_TYPE, args: [filePath] + className: NTQQApiClass.FS_API, + methodName: NTQQApiMethod.FILE_TYPE, + args: [filePath], }) } @@ -28,7 +33,7 @@ export class NTQQFileApi { return await callNTQQApi({ className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_MD5, - args: [filePath] + args: [filePath], }) } @@ -36,73 +41,86 @@ export class NTQQFileApi { return await callNTQQApi({ className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_COPY, - args: [{ - fromPath: filePath, - toPath: destPath - }] + args: [ + { + fromPath: filePath, + toPath: destPath, + }, + ], }) } static async getFileSize(filePath: string) { return await callNTQQApi({ - className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_SIZE, args: [filePath] + className: NTQQApiClass.FS_API, + methodName: NTQQApiMethod.FILE_SIZE, + args: [filePath], }) } // 上传文件到QQ的文件夹 static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { - const md5 = await NTQQFileApi.getFileMd5(filePath); + const md5 = await NTQQFileApi.getFileMd5(filePath) let ext = (await NTQQFileApi.getFileType(filePath))?.ext if (ext) { - ext = "." + ext + ext = '.' + ext } else { - ext = "" + ext = '' } - let fileName = `${path.basename(filePath)}`; - if (fileName.indexOf(".") === -1) { - fileName += ext; + let fileName = `${path.basename(filePath)}` + if (fileName.indexOf('.') === -1) { + fileName += ext } const mediaPath = await callNTQQApi({ methodName: NTQQApiMethod.MEDIA_FILE_PATH, - args: [{ - path_info: { - md5HexStr: md5, - fileName: fileName, - elementType: elementType, - elementSubType, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: "" - } - }] + args: [ + { + path_info: { + md5HexStr: md5, + fileName: fileName, + elementType: elementType, + elementSubType, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '', + }, + }, + ], }) - log("media path", mediaPath) - await NTQQFileApi.copyFile(filePath, mediaPath); - const fileSize = await NTQQFileApi.getFileSize(filePath); + log('media path', mediaPath) + await NTQQFileApi.copyFile(filePath, mediaPath) + const fileSize = await NTQQFileApi.getFileSize(filePath) return { md5, fileName, path: mediaPath, fileSize, - ext + ext, } } - static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, force: boolean = false) { + static async downloadMedia( + msgId: string, + chatType: ChatType, + peerUid: string, + elementId: string, + thumbPath: string, + sourcePath: string, + force: boolean = false, + ) { // 用于下载收到的消息中的图片等 if (sourcePath && fs.existsSync(sourcePath)) { - if (force){ + if (force) { fs.unlinkSync(sourcePath) - } - else{ + } else { return sourcePath } } const apiParams = [ { getReq: { - fileModelId: "0", + fileModelId: '0', downloadSourceType: 0, triggerType: 1, msgId: msgId, @@ -121,225 +139,257 @@ export class NTQQFileApi { methodName: NTQQApiMethod.DOWNLOAD_MEDIA, args: apiParams, cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, - cmdCB: (payload: { notifyInfo: { filePath: string, msgId: string } }) => { - log("media 下载完成判断", payload.notifyInfo.msgId, msgId); - return payload.notifyInfo.msgId == msgId; - } + cmdCB: (payload: { notifyInfo: { filePath: string; msgId: string } }) => { + log('media 下载完成判断', payload.notifyInfo.msgId, msgId) + return payload.notifyInfo.msgId == msgId + }, }) return sourcePath } static async getImageSize(filePath: string) { - return await callNTQQApi<{ width: number, height: number }>({ - className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.IMAGE_SIZE, args: [filePath] + return await callNTQQApi<{ width: number; height: number }>({ + className: NTQQApiClass.FS_API, + methodName: NTQQApiMethod.IMAGE_SIZE, + args: [filePath], }) } static async getImageUrl(msg: RawMessage) { - const msgElement = msg.elements.find(e => !!e.picElement); + const msgElement = msg.elements.find((e) => !!e.picElement) if (!msgElement) { - return ''; + return '' } - const url = msgElement.picElement.originImageUrl; // 没有域名 - const md5HexStr = msgElement.picElement.md5HexStr; - const fileMd5 = msgElement.picElement.md5HexStr; - const fileUuid = msgElement.picElement.fileUuid; - let imageUrl = ''; + const url = msgElement.picElement.originImageUrl // 没有域名 + const md5HexStr = msgElement.picElement.md5HexStr + const fileMd5 = msgElement.picElement.md5HexStr + const fileUuid = msgElement.picElement.fileUuid + let imageUrl = '' // let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64" if (url) { if (url.startsWith('/download')) { // console.log('rkey', rkey); if (url.includes('&rkey=')) { - imageUrl = IMAGE_HTTP_HOST_NT + url; + imageUrl = IMAGE_HTTP_HOST_NT + url } else { if (!hookApi.isAvailable()) { - log('hookApi is not available'); - return ''; + log('hookApi is not available') + return '' } - let rkey = hookApi.getRKey(); + let rkey = hookApi.getRKey() const refreshRKey = async () => { - log('正在获取图片rkey...'); - NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, msgElement.elementId, '', msgElement.picElement.sourcePath, true).then().catch(() => { - }); - await sleep(300); - rkey = hookApi.getRKey(); + log('正在获取图片rkey...') + NTQQFileApi.downloadMedia( + msg.msgId, + msg.chatType, + msg.peerUid, + msgElement.elementId, + '', + msgElement.picElement.sourcePath, + true, + ) + .then() + .catch(() => {}) + await sleep(300) + rkey = hookApi.getRKey() if (rkey) { - log('图片rkey获取成功', rkey); + log('图片rkey获取成功', rkey) } - }; + } if (!rkey) { // 下载一次图片获取rkey try { - await refreshRKey(); + await refreshRKey() } catch (e) { - log('获取图片rkey失败', e); - return ''; + log('获取图片rkey失败', e) + return '' } } - imageUrl = IMAGE_HTTP_HOST_NT + url + `${rkey}`; + imageUrl = IMAGE_HTTP_HOST_NT + url + `${rkey}` // 调用head请求获取图片rkey是否正常 const checkUrl = new Promise((resolve, reject) => { - const uri = new URL(imageUrl); + const uri = new URL(imageUrl) const options = { method: 'GET', host: uri.host, path: uri.pathname + uri.search, headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', - 'Accept': '*/*', - 'Range': 'bytes=0-0' - } - }; + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', + Accept: '*/*', + Range: 'bytes=0-0', + }, + } const req = http.request(options, (res) => { - console.log(`Check rkey status: ${res.statusCode}`); - console.log(`Check rkey headers: ${JSON.stringify(res.headers)}`); + console.log(`Check rkey status: ${res.statusCode}`) + console.log(`Check rkey headers: ${JSON.stringify(res.headers)}`) if (res.statusCode == 200 || res.statusCode === 206) { // console.log('The image URL is accessible.'); - resolve('ok'); + resolve('ok') } else { - reject('The image URL is not accessible.'); + reject('The image URL is not accessible.') } - }); + }) req.setTimeout(3000, () => { - req.destroy(); - reject('Check rkey request timed out'); - }); + req.destroy() + reject('Check rkey request timed out') + }) req.on('error', (e) => { - console.error(`Problem with rkey request: ${e.message}`); - reject(e.message); - }); - req.end(); - }); + console.error(`Problem with rkey request: ${e.message}`) + reject(e.message) + }) + req.end() + }) try { - const start = Date.now(); - await checkUrl; - const end = Date.now(); - console.log('Check rkey request time:', end - start); + const start = Date.now() + await checkUrl + const end = Date.now() + console.log('Check rkey request time:', end - start) } catch (e) { try { - await refreshRKey(); - imageUrl = IMAGE_HTTP_HOST_NT + url + `${rkey}`; + await refreshRKey() + imageUrl = IMAGE_HTTP_HOST_NT + url + `${rkey}` } catch (e) { - log('获取rkey失败', e); + log('获取rkey失败', e) } } } } else { - imageUrl = IMAGE_HTTP_HOST + url; + imageUrl = IMAGE_HTTP_HOST + url } } else if (fileMd5 || md5HexStr) { - imageUrl = `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`; + imageUrl = `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0` } - return imageUrl; + return imageUrl } - } export class NTQQFileCacheApi { static async setCacheSilentScan(isSilent: boolean = true) { return await callNTQQApi({ methodName: NTQQApiMethod.CACHE_SET_SILENCE, - args: [{ - isSilent - }, null] - }); + args: [ + { + isSilent, + }, + null, + ], + }) } static getCacheSessionPathList() { - return callNTQQApi<{ - key: string, - value: string - }[]>({ + return callNTQQApi< + { + key: string + value: string + }[] + >({ className: NTQQApiClass.OS_API, methodName: NTQQApiMethod.CACHE_PATH_SESSION, - }); + }) } static clearCache(cacheKeys: Array = ['tmp', 'hotUpdate']) { - return callNTQQApi({ // TODO: 目前还不知道真正的返回值是什么 + return callNTQQApi({ + // TODO: 目前还不知道真正的返回值是什么 methodName: NTQQApiMethod.CACHE_CLEAR, - args: [{ - keys: cacheKeys - }, null] - }); + args: [ + { + keys: cacheKeys, + }, + null, + ], + }) } static addCacheScannedPaths(pathMap: object = {}) { return callNTQQApi({ methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH, - args: [{ - pathMap: {...pathMap}, - }, null] - }); + args: [ + { + pathMap: { ...pathMap }, + }, + null, + ], + }) } static scanCache() { callNTQQApi({ methodName: ReceiveCmdS.CACHE_SCAN_FINISH, classNameIsRegister: true, - }).then(); + }).then() return callNTQQApi({ methodName: NTQQApiMethod.CACHE_SCAN, args: [null, null], timeoutSecond: 300, - }); + }) } static getHotUpdateCachePath() { return callNTQQApi({ className: NTQQApiClass.HOTUPDATE_API, - methodName: NTQQApiMethod.CACHE_PATH_HOT_UPDATE - }); + methodName: NTQQApiMethod.CACHE_PATH_HOT_UPDATE, + }) } static getDesktopTmpPath() { return callNTQQApi({ className: NTQQApiClass.BUSINESS_API, - methodName: NTQQApiMethod.CACHE_PATH_DESKTOP_TEMP - }); + methodName: NTQQApiMethod.CACHE_PATH_DESKTOP_TEMP, + }) } static getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) { return new Promise((res, rej) => { callNTQQApi({ methodName: NTQQApiMethod.CACHE_CHAT_GET, - args: [{ - chatType: type, - pageSize, - order: 1, - pageIndex - }, null] - }).then(list => res(list)) - .catch(e => rej(e)); - }); + args: [ + { + chatType: type, + pageSize, + order: 1, + pageIndex, + }, + null, + ], + }) + .then((list) => res(list)) + .catch((e) => rej(e)) + }) } static getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) { - const _lastRecord = lastRecord ? lastRecord : {fileType: fileType}; + const _lastRecord = lastRecord ? lastRecord : { fileType: fileType } return callNTQQApi({ methodName: NTQQApiMethod.CACHE_FILE_GET, - args: [{ - fileType: fileType, - restart: true, - pageSize: pageSize, - order: 1, - lastRecord: _lastRecord, - }, null] + args: [ + { + fileType: fileType, + restart: true, + pageSize: pageSize, + order: 1, + lastRecord: _lastRecord, + }, + null, + ], }) } static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { return await callNTQQApi({ methodName: NTQQApiMethod.CACHE_CHAT_CLEAR, - args: [{ - chats, - fileKeys - }, null] - }); + args: [ + { + chats, + fileKeys, + }, + null, + ], + }) } - -} \ No newline at end of file +} diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 3be7b9d..1604c29 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -1,61 +1,62 @@ -import {Friend, FriendRequest} from "../types"; -import {ReceiveCmdS} from "../hook"; -import {callNTQQApi, GeneralCallResult, NTQQApiMethod} from "../ntcall"; -import {friendRequests} from "../../common/data"; +import { Friend, FriendRequest } from '../types' +import { ReceiveCmdS } from '../hook' +import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' +import { friendRequests } from '../../common/data' -export class NTQQFriendApi{ - static async getFriends(forced = false) { - const data = await callNTQQApi<{ - data: { - categoryId: number, - categroyName: string, - categroyMbCount: number, - buddyList: Friend[] - }[] - }>( - { - methodName: NTQQApiMethod.FRIENDS, - args: [{force_update: forced}, undefined], - cbCmd: ReceiveCmdS.FRIENDS - }) - let _friends: Friend[] = []; - for (const fData of data.data) { - _friends.push(...fData.buddyList) - } - return _friends +export class NTQQFriendApi { + static async getFriends(forced = false) { + const data = await callNTQQApi<{ + data: { + categoryId: number + categroyName: string + categroyMbCount: number + buddyList: Friend[] + }[] + }>({ + methodName: NTQQApiMethod.FRIENDS, + args: [{ force_update: forced }, undefined], + cbCmd: ReceiveCmdS.FRIENDS, + }) + let _friends: Friend[] = [] + for (const fData of data.data) { + _friends.push(...fData.buddyList) } - static async likeFriend(uid: string, count = 1) { - return await callNTQQApi({ - methodName: NTQQApiMethod.LIKE_FRIEND, - args: [{ - doLikeUserInfo: { - friendUid: uid, - sourceId: 71, - doLikeCount: count, - doLikeTollCount: 0 - } - }, null] - }) + return _friends + } + static async likeFriend(uid: string, count = 1) { + return await callNTQQApi({ + methodName: NTQQApiMethod.LIKE_FRIEND, + args: [ + { + doLikeUserInfo: { + friendUid: uid, + sourceId: 71, + doLikeCount: count, + doLikeTollCount: 0, + }, + }, + null, + ], + }) + } + static async handleFriendRequest(flag: string, accept: boolean) { + const request: FriendRequest = friendRequests[flag] + if (!request) { + throw `flat: ${flag}, 对应的好友请求不存在` } - static async handleFriendRequest(flag: string, accept: boolean,) { - const request: FriendRequest = friendRequests[flag] - if (!request) { - throw `flat: ${flag}, 对应的好友请求不存在` - } - const result = await callNTQQApi({ - methodName: NTQQApiMethod.HANDLE_FRIEND_REQUEST, - args: [ - { - "approvalInfo": { - "friendUid": request.friendUid, - "reqTime": request.reqTime, - accept - } - } - ] - }) - delete friendRequests[flag]; - return result; - } - -} \ No newline at end of file + const result = await callNTQQApi({ + methodName: NTQQApiMethod.HANDLE_FRIEND_REQUEST, + args: [ + { + approvalInfo: { + friendUid: request.friendUid, + reqTime: request.reqTime, + accept, + }, + }, + ], + }) + delete friendRequests[flag] + return result + } +} diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 8730a58..a9d7bad 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -1,220 +1,232 @@ -import {ReceiveCmdS} from "../hook"; -import {Group, GroupMember, GroupMemberRole, GroupNotifies, GroupNotify, GroupRequestOperateTypes} from "../types"; -import {callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod} from "../ntcall"; -import {deleteGroup, uidMaps} from "../../common/data"; -import {dbUtil} from "../../common/db"; -import {log} from "../../common/utils/log"; -import {NTQQWindowApi, NTQQWindows} from "./window"; +import { ReceiveCmdS } from '../hook' +import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupNotify, GroupRequestOperateTypes } from '../types' +import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' +import { deleteGroup, uidMaps } from '../../common/data' +import { dbUtil } from '../../common/db' +import { log } from '../../common/utils/log' +import { NTQQWindowApi, NTQQWindows } from './window' -export class NTQQGroupApi{ - static async getGroups(forced = false) { - let cbCmd = ReceiveCmdS.GROUPS - if (process.platform != "win32") { - cbCmd = ReceiveCmdS.GROUPS_STORE +export class NTQQGroupApi { + static async getGroups(forced = false) { + let cbCmd = ReceiveCmdS.GROUPS + if (process.platform != 'win32') { + cbCmd = ReceiveCmdS.GROUPS_STORE + } + const result = await callNTQQApi<{ + updateType: number + groupList: Group[] + }>({ methodName: NTQQApiMethod.GROUPS, args: [{ force_update: forced }, undefined], cbCmd }) + return result.groupList + } + static async getGroupMembers(groupQQ: string, num = 3000): Promise { + const sceneId = await callNTQQApi({ + methodName: NTQQApiMethod.GROUP_MEMBER_SCENE, + args: [ + { + groupCode: groupQQ, + scene: 'groupMemberList_MainWindow', + }, + ], + }) + // log("get group member sceneId", sceneId); + try { + const result = await callNTQQApi<{ + result: { infos: any } + }>({ + methodName: NTQQApiMethod.GROUP_MEMBERS, + args: [ + { + sceneId: sceneId, + num: num, + }, + null, + ], + }) + // log("members info", typeof result.result.infos, Object.keys(result.result.infos)) + const values = result.result.infos.values() + + const members: GroupMember[] = Array.from(values) + for (const member of members) { + uidMaps[member.uid] = member.uin + } + // log(uidMaps); + // log("members info", values); + log(`get group ${groupQQ} members success`) + return members + } catch (e) { + log(`get group ${groupQQ} members failed`, e) + return [] + } + } + static async getGroupNotifies() { + // 获取管理员变更 + // 加群通知,退出通知,需要管理员权限 + callNTQQApi({ + methodName: ReceiveCmdS.GROUP_NOTIFY, + classNameIsRegister: true, + }).then() + return await callNTQQApi({ + methodName: NTQQApiMethod.GET_GROUP_NOTICE, + cbCmd: ReceiveCmdS.GROUP_NOTIFY, + afterFirstCmd: false, + args: [{ doubt: false, startSeq: '', number: 14 }, null], + }) + } + static async getGroupIgnoreNotifies() { + await NTQQGroupApi.getGroupNotifies() + return await NTQQWindowApi.openWindow(NTQQWindows.GroupNotifyFilterWindow, [], ReceiveCmdS.GROUP_NOTIFY) + } + static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) { + const notify: GroupNotify = await dbUtil.getGroupNotify(seq) + if (!notify) { + throw `${seq}对应的加群通知不存在` + } + // delete groupNotifies[seq]; + return await callNTQQApi({ + methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST, + args: [ + { + doubt: false, + operateMsg: { + operateType: operateType, // 2 拒绝 + targetMsg: { + seq: seq, // 通知序列号 + type: notify.type, + groupCode: notify.group.groupCode, + postscript: reason, + }, + }, + }, + null, + ], + }) + } + static async quitGroup(groupQQ: string) { + const result = await callNTQQApi({ + methodName: NTQQApiMethod.QUIT_GROUP, + args: [{ groupCode: groupQQ }, null], + }) + if (result.result === 0) { + deleteGroup(groupQQ) + } + return result + } + static async kickMember( + groupQQ: string, + kickUids: string[], + refuseForever: boolean = false, + kickReason: string = '', + ) { + return await callNTQQApi({ + methodName: NTQQApiMethod.KICK_MEMBER, + args: [ + { + groupCode: groupQQ, + kickUids, + refuseForever, + kickReason, + }, + ], + }) + } + static async banMember(groupQQ: string, memList: Array<{ uid: string; timeStamp: number }>) { + // timeStamp为秒数, 0为解除禁言 + return await callNTQQApi({ + methodName: NTQQApiMethod.MUTE_MEMBER, + args: [ + { + groupCode: groupQQ, + memList, + }, + ], + }) + } + static async banGroup(groupQQ: string, shutUp: boolean) { + return await callNTQQApi({ + methodName: NTQQApiMethod.MUTE_GROUP, + args: [ + { + groupCode: groupQQ, + shutUp, + }, + null, + ], + }) + } + static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) { + return await callNTQQApi({ + methodName: NTQQApiMethod.SET_MEMBER_CARD, + args: [ + { + groupCode: groupQQ, + uid: memberUid, + cardName, + }, + null, + ], + }) + } + static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { + return await callNTQQApi({ + methodName: NTQQApiMethod.SET_MEMBER_ROLE, + args: [ + { + groupCode: groupQQ, + uid: memberUid, + role, + }, + null, + ], + }) + } + static async setGroupName(groupQQ: string, groupName: string) { + return await callNTQQApi({ + methodName: NTQQApiMethod.SET_GROUP_NAME, + args: [ + { + groupCode: groupQQ, + groupName, + }, + null, + ], + }) + } + + static async getGroupAtAllRemainCount(groupCode: string) { + return await callNTQQApi< + GeneralCallResult & { + atInfo: { + canAtAll: boolean + RemainAtAllCountForUin: number + RemainAtAllCountForGroup: number + atTimesMsg: string + canNotAtAllMsg: '' } - const result = await callNTQQApi<{ - updateType: number, - groupList: Group[] - }>({methodName: NTQQApiMethod.GROUPS, args: [{force_update: forced}, undefined], cbCmd}) - return result.groupList - } - static async getGroupMembers(groupQQ: string, num = 3000): Promise { - const sceneId = await callNTQQApi({ - methodName: NTQQApiMethod.GROUP_MEMBER_SCENE, - args: [{ - groupCode: groupQQ, - scene: "groupMemberList_MainWindow" - }] - }) - // log("get group member sceneId", sceneId); - try { - const result = await callNTQQApi<{ - result: { infos: any } - }>({ - methodName: NTQQApiMethod.GROUP_MEMBERS, - args: [{ - sceneId: sceneId, - num: num - }, - null - ] - }) - // log("members info", typeof result.result.infos, Object.keys(result.result.infos)) - const values = result.result.infos.values() + } + >({ + methodName: NTQQApiMethod.GROUP_AT_ALL_REMAIN_COUNT, + args: [ + { + groupCode, + }, + null, + ], + }) + } - const members: GroupMember[] = Array.from(values) - for (const member of members) { - uidMaps[member.uid] = member.uin; - } - // log(uidMaps); - // log("members info", values); - log(`get group ${groupQQ} members success`) - return members - } catch (e) { - log(`get group ${groupQQ} members failed`, e) - return [] - } - } - static async getGroupNotifies() { - // 获取管理员变更 - // 加群通知,退出通知,需要管理员权限 - callNTQQApi({ - methodName: ReceiveCmdS.GROUP_NOTIFY, - classNameIsRegister: true, - }).then() - return await callNTQQApi({ - methodName: NTQQApiMethod.GET_GROUP_NOTICE, - cbCmd: ReceiveCmdS.GROUP_NOTIFY, - afterFirstCmd: false, - args: [ - {"doubt": false, "startSeq": "", "number": 14}, - null - ] - }); - } - static async getGroupIgnoreNotifies() { - await NTQQGroupApi.getGroupNotifies(); - return await NTQQWindowApi.openWindow(NTQQWindows.GroupNotifyFilterWindow,[], ReceiveCmdS.GROUP_NOTIFY); - } - static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) { - const notify: GroupNotify = await dbUtil.getGroupNotify(seq) - if (!notify) { - throw `${seq}对应的加群通知不存在` - } - // delete groupNotifies[seq]; - return await callNTQQApi({ - methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST, - args: [ - { - "doubt": false, - "operateMsg": { - "operateType": operateType, // 2 拒绝 - "targetMsg": { - "seq": seq, // 通知序列号 - "type": notify.type, - "groupCode": notify.group.groupCode, - "postscript": reason - } - } - }, - null - ] - }); - } - static async quitGroup(groupQQ: string) { - const result = await callNTQQApi({ - methodName: NTQQApiMethod.QUIT_GROUP, - args: [ - {"groupCode": groupQQ}, - null - ] - }) - if (result.result === 0){ - deleteGroup(groupQQ); - } - return result; - } - static async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') { - return await callNTQQApi( - { - methodName: NTQQApiMethod.KICK_MEMBER, - args: [ - { - groupCode: groupQQ, - kickUids, - refuseForever, - kickReason, - } - ] - } - ) - } - static async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) { - // timeStamp为秒数, 0为解除禁言 - return await callNTQQApi( - { - methodName: NTQQApiMethod.MUTE_MEMBER, - args: [ - { - groupCode: groupQQ, - memList, - } - ] - } - ) - } - static async banGroup(groupQQ: string, shutUp: boolean) { - return await callNTQQApi({ - methodName: NTQQApiMethod.MUTE_GROUP, - args: [ - { - groupCode: groupQQ, - shutUp - }, null - ] - }) - } - static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) { - return await callNTQQApi({ - methodName: NTQQApiMethod.SET_MEMBER_CARD, - args: [ - { - groupCode: groupQQ, - uid: memberUid, - cardName - }, null - ] - }) - } - static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { - return await callNTQQApi({ - methodName: NTQQApiMethod.SET_MEMBER_ROLE, - args: [ - { - groupCode: groupQQ, - uid: memberUid, - role - }, null - ] - }) - } - static async setGroupName(groupQQ: string, groupName: string) { - return await callNTQQApi({ - methodName: NTQQApiMethod.SET_GROUP_NAME, - args: [ - { - groupCode: groupQQ, - groupName - }, null - ] - }) - } - - static async getGroupAtAllRemainCount(groupCode: string){ - return await callNTQQApi({ - methodName: NTQQApiMethod.GROUP_AT_ALL_REMAIN_COUNT, - args: [ - { - groupCode - }, null - ] - }) - } - - // 头衔不可用 - static async setGroupTitle(groupQQ: string, uid: string, title: string) { - return await callNTQQApi({ - methodName: NTQQApiMethod.SET_GROUP_TITLE, - args: [ - { - groupCode: groupQQ, - uid, - title - }, null - ] - }) - } - static publishGroupBulletin(groupQQ: string, title: string, content: string) { - - } -} \ No newline at end of file + // 头衔不可用 + static async setGroupTitle(groupQQ: string, uid: string, title: string) { + return await callNTQQApi({ + methodName: NTQQApiMethod.SET_GROUP_TITLE, + args: [ + { + groupCode: groupQQ, + uid, + title, + }, + null, + ], + }) + } + static publishGroupBulletin(groupQQ: string, title: string, content: string) {} +} diff --git a/src/ntqqapi/api/index.ts b/src/ntqqapi/api/index.ts index 461f2a7..b52cb64 100644 --- a/src/ntqqapi/api/index.ts +++ b/src/ntqqapi/api/index.ts @@ -1,7 +1,7 @@ -export * from "./file"; -export * from "./friend"; -export * from "./group"; -export * from "./msg"; -export * from "./user"; -export * from "./webapi"; -export * from "./window"; +export * from './file' +export * from './friend' +export * from './group' +export * from './msg' +export * from './user' +export * from './webapi' +export * from './window' diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index e0350fd..821479f 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -1,232 +1,247 @@ -import {callNTQQApi, GeneralCallResult, NTQQApiMethod} from "../ntcall"; -import {ChatType, RawMessage, SendMessageElement} from "../types"; -import {dbUtil} from "../../common/db"; -import {selfInfo} from "../../common/data"; -import {ReceiveCmdS, registerReceiveHook} from "../hook"; -import {log} from "../../common/utils/log"; -import {sleep} from "../../common/utils/helper"; -import {isQQ998} from "../../common/utils"; +import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' +import { ChatType, RawMessage, SendMessageElement } from '../types' +import { dbUtil } from '../../common/db' +import { selfInfo } from '../../common/data' +import { ReceiveCmdS, registerReceiveHook } from '../hook' +import { log } from '../../common/utils/log' +import { sleep } from '../../common/utils/helper' +import { isQQ998 } from '../../common/utils' -export let sendMessagePool: Record void) | null> = {}// peerUid: callbackFunnc +export let sendMessagePool: Record void) | null> = {} // peerUid: callbackFunnc export interface Peer { - chatType: ChatType - peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串 - guildId?: "" + chatType: ChatType + peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串 + guildId?: '' } export class NTQQMsgApi { - static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean=true){ - // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 - // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid - // 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType - return await callNTQQApi({ - methodName: NTQQApiMethod.EMOJI_LIKE, - args: [{ - peer, msgSeq, emojiId, emojiType: emojiId.length > 3 ? "2" : "1", setEmoji: set - }, null] - }) - } - static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) { - return await callNTQQApi({ - methodName: NTQQApiMethod.GET_MULTI_MSG, - args: [{ - peer, - rootMsgId, - parentMsgId - }, null] - }) - } + static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { + // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 + // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid + // 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType + return await callNTQQApi({ + methodName: NTQQApiMethod.EMOJI_LIKE, + args: [ + { + peer, + msgSeq, + emojiId, + emojiType: emojiId.length > 3 ? '2' : '1', + setEmoji: set, + }, + null, + ], + }) + } + static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) { + return await callNTQQApi({ + methodName: NTQQApiMethod.GET_MULTI_MSG, + args: [ + { + peer, + rootMsgId, + parentMsgId, + }, + null, + ], + }) + } - static async activateChat(peer: Peer) { - // await this.fetchRecentContact(); - // await sleep(500); - return await callNTQQApi({ - methodName: NTQQApiMethod.ACTIVE_CHAT_PREVIEW, - args: [{peer, cnt: 20}, null] - }) - } - static async activateChatAndGetHistory(peer: Peer) { - // await this.fetchRecentContact(); - // await sleep(500); - return await callNTQQApi({ - methodName: NTQQApiMethod.ACTIVE_CHAT_HISTORY, - // 参数似乎不是这样 - args: [{peer, cnt: 20}, null] - }) - } - static async getMsgHistory(peer: Peer, msgId: string, count: number) { - // 消息时间从旧到新 - return await callNTQQApi({ - methodName: isQQ998 ? NTQQApiMethod.ACTIVE_CHAT_HISTORY : NTQQApiMethod.HISTORY_MSG, - args: [{ - peer, - msgId, - cnt: count, - queryOrder: true, - }, null] - }) - } - static async fetchRecentContact(){ - await callNTQQApi({ - methodName: NTQQApiMethod.RECENT_CONTACT, - args: [ - { - fetchParam: { - anchorPointContact: { - contactId: '', - sortField: '', - pos: 0, - }, - relativeMoveCount: 0, - listType: 2, // 1普通消息,2群助手内的消息 - count: 200, - fetchOld: true, - }, - } - ] - }) - } - - static async recallMsg(peer: Peer, msgIds: string[]) { - return await callNTQQApi({ - methodName: NTQQApiMethod.RECALL_MSG, - args: [{ - peer, - msgIds - }, null] - }) - } - - static async sendMsg(peer: Peer, msgElements: SendMessageElement[], - waitComplete = true, timeout = 10000) { - const peerUid = peer.peerUid - - // 等待上一个相同的peer发送完 - let checkLastSendUsingTime = 0; - const waitLastSend = async () => { - if (checkLastSendUsingTime > timeout) { - throw ("发送超时") - } - let lastSending = sendMessagePool[peer.peerUid] - if (lastSending) { - // log("有正在发送的消息,等待中...") - await sleep(500); - checkLastSendUsingTime += 500; - return await waitLastSend(); - } else { - return; - } - } - await waitLastSend(); - - let sentMessage: RawMessage = null; - sendMessagePool[peerUid] = async (rawMessage: RawMessage) => { - delete sendMessagePool[peerUid]; - sentMessage = rawMessage; - } - - let checkSendCompleteUsingTime = 0; - const checkSendComplete = async (): Promise => { - if (sentMessage) { - if (waitComplete) { - if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) { - return sentMessage - } - } else { - return sentMessage - } - // log(`给${peerUid}发送消息成功`) - } - checkSendCompleteUsingTime += 500 - if (checkSendCompleteUsingTime > timeout) { - throw ('发送超时') - } - await sleep(500) - return await checkSendComplete() - } - - callNTQQApi({ - methodName: NTQQApiMethod.SEND_MSG, - args: [{ - msgId: "0", - peer, msgElements, - msgAttributeInfos: new Map(), - }, null] - }).then() - return await checkSendComplete() - } - - static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { - return await callNTQQApi({ - methodName: NTQQApiMethod.FORWARD_MSG, - args: [ - { - msgIds: msgIds, - srcContact: srcPeer, - dstContacts: [ - destPeer - ], - commentElements: [], - msgAttributeInfos: new Map() - }, - null, - ] - }) - } - - static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { - const msgInfos = msgIds.map(id => { - return {msgId: id, senderShowName: selfInfo.nick} - }) - const apiArgs = [ - { - msgInfos, - srcContact: srcPeer, - dstContact: destPeer, - commentElements: [], - msgAttributeInfos: new Map() + static async activateChat(peer: Peer) { + // await this.fetchRecentContact(); + // await sleep(500); + return await callNTQQApi({ + methodName: NTQQApiMethod.ACTIVE_CHAT_PREVIEW, + args: [{ peer, cnt: 20 }, null], + }) + } + static async activateChatAndGetHistory(peer: Peer) { + // await this.fetchRecentContact(); + // await sleep(500); + return await callNTQQApi({ + methodName: NTQQApiMethod.ACTIVE_CHAT_HISTORY, + // 参数似乎不是这样 + args: [{ peer, cnt: 20 }, null], + }) + } + static async getMsgHistory(peer: Peer, msgId: string, count: number) { + // 消息时间从旧到新 + return await callNTQQApi({ + methodName: isQQ998 ? NTQQApiMethod.ACTIVE_CHAT_HISTORY : NTQQApiMethod.HISTORY_MSG, + args: [ + { + peer, + msgId, + cnt: count, + queryOrder: true, + }, + null, + ], + }) + } + static async fetchRecentContact() { + await callNTQQApi({ + methodName: NTQQApiMethod.RECENT_CONTACT, + args: [ + { + fetchParam: { + anchorPointContact: { + contactId: '', + sortField: '', + pos: 0, }, - null, - ] - return await new Promise((resolve, reject) => { - let complete = false - setTimeout(() => { - if (!complete) { - reject("转发消息超时"); - } - }, 5000) - registerReceiveHook(ReceiveCmdS.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => { - const msg = payload.msgRecord - // 需要判断它是转发的消息,并且识别到是当前转发的这一条 - const arkElement = msg.elements.find(ele => ele.arkElement) - if (!arkElement) { - // log("收到的不是转发消息") - return - } - const forwardData: any = JSON.parse(arkElement.arkElement.bytesData) - if (forwardData.app != 'com.tencent.multimsg') { - return - } - if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) { - complete = true - await dbUtil.addMsg(msg) - resolve(msg) - log('转发消息成功:', payload) - } - }) - callNTQQApi({ - methodName: NTQQApiMethod.MULTI_FORWARD_MSG, - args: apiArgs - }).then(result => { - log("转发消息结果:", result, apiArgs) - if (result.result !== 0) { - complete = true; - reject("转发消息失败," + JSON.stringify(result)); - } - }) - }) + relativeMoveCount: 0, + listType: 2, // 1普通消息,2群助手内的消息 + count: 200, + fetchOld: true, + }, + }, + ], + }) + } + + static async recallMsg(peer: Peer, msgIds: string[]) { + return await callNTQQApi({ + methodName: NTQQApiMethod.RECALL_MSG, + args: [ + { + peer, + msgIds, + }, + null, + ], + }) + } + + static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { + const peerUid = peer.peerUid + + // 等待上一个相同的peer发送完 + let checkLastSendUsingTime = 0 + const waitLastSend = async () => { + if (checkLastSendUsingTime > timeout) { + throw '发送超时' + } + let lastSending = sendMessagePool[peer.peerUid] + if (lastSending) { + // log("有正在发送的消息,等待中...") + await sleep(500) + checkLastSendUsingTime += 500 + return await waitLastSend() + } else { + return + } + } + await waitLastSend() + + let sentMessage: RawMessage = null + sendMessagePool[peerUid] = async (rawMessage: RawMessage) => { + delete sendMessagePool[peerUid] + sentMessage = rawMessage } + let checkSendCompleteUsingTime = 0 + const checkSendComplete = async (): Promise => { + if (sentMessage) { + if (waitComplete) { + if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) { + return sentMessage + } + } else { + return sentMessage + } + // log(`给${peerUid}发送消息成功`) + } + checkSendCompleteUsingTime += 500 + if (checkSendCompleteUsingTime > timeout) { + throw '发送超时' + } + await sleep(500) + return await checkSendComplete() + } -} \ No newline at end of file + callNTQQApi({ + methodName: NTQQApiMethod.SEND_MSG, + args: [ + { + msgId: '0', + peer, + msgElements, + msgAttributeInfos: new Map(), + }, + null, + ], + }).then() + return await checkSendComplete() + } + + static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { + return await callNTQQApi({ + methodName: NTQQApiMethod.FORWARD_MSG, + args: [ + { + msgIds: msgIds, + srcContact: srcPeer, + dstContacts: [destPeer], + commentElements: [], + msgAttributeInfos: new Map(), + }, + null, + ], + }) + } + + static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { + const msgInfos = msgIds.map((id) => { + return { msgId: id, senderShowName: selfInfo.nick } + }) + const apiArgs = [ + { + msgInfos, + srcContact: srcPeer, + dstContact: destPeer, + commentElements: [], + msgAttributeInfos: new Map(), + }, + null, + ] + return await new Promise((resolve, reject) => { + let complete = false + setTimeout(() => { + if (!complete) { + reject('转发消息超时') + } + }, 5000) + registerReceiveHook(ReceiveCmdS.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => { + const msg = payload.msgRecord + // 需要判断它是转发的消息,并且识别到是当前转发的这一条 + const arkElement = msg.elements.find((ele) => ele.arkElement) + if (!arkElement) { + // log("收到的不是转发消息") + return + } + const forwardData: any = JSON.parse(arkElement.arkElement.bytesData) + if (forwardData.app != 'com.tencent.multimsg') { + return + } + if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) { + complete = true + await dbUtil.addMsg(msg) + resolve(msg) + log('转发消息成功:', payload) + } + }) + callNTQQApi({ + methodName: NTQQApiMethod.MULTI_FORWARD_MSG, + args: apiArgs, + }).then((result) => { + log('转发消息结果:', result, apiArgs) + if (result.result !== 0) { + complete = true + reject('转发消息失败,' + JSON.stringify(result)) + } + }) + }) + } +} diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index 60e778e..82c13ae 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -1,148 +1,159 @@ -import {callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod} from "../ntcall"; -import {Group, SelfInfo, User} from "../types"; -import {ReceiveCmdS} from "../hook"; -import {selfInfo, uidMaps} from "../../common/data"; -import {NTQQWindowApi, NTQQWindows} from "./window"; -import {isQQ998, log, sleep} from "../../common/utils"; +import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' +import { Group, SelfInfo, User } from '../types' +import { ReceiveCmdS } from '../hook' +import { selfInfo, uidMaps } from '../../common/data' +import { NTQQWindowApi, NTQQWindows } from './window' +import { isQQ998, log, sleep } from '../../common/utils' -let userInfoCache: Record = {}; // uid: User +let userInfoCache: Record = {} // uid: User -export class NTQQUserApi{ - static async setQQAvatar(filePath: string) { - return await callNTQQApi({ - methodName: NTQQApiMethod.SET_QQ_AVATAR, - args: [{ - path:filePath - }, null], - timeoutSecond: 10 // 10秒不一定够 - }); +export class NTQQUserApi { + static async setQQAvatar(filePath: string) { + return await callNTQQApi({ + methodName: NTQQApiMethod.SET_QQ_AVATAR, + args: [ + { + path: filePath, + }, + null, + ], + timeoutSecond: 10, // 10秒不一定够 + }) + } + + static async getSelfInfo() { + return await callNTQQApi({ + className: NTQQApiClass.GLOBAL_DATA, + methodName: NTQQApiMethod.SELF_INFO, + timeoutSecond: 2, + }) + } + static async getUserInfo(uid: string) { + const result = await callNTQQApi<{ profiles: Map }>({ + methodName: NTQQApiMethod.USER_INFO, + args: [{ force: true, uids: [uid] }, undefined], + cbCmd: ReceiveCmdS.USER_INFO, + }) + return result.profiles.get(uid) + } + static async getUserDetailInfo(uid: string, getLevel = false) { + // this.getUserInfo(uid); + let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO + const fetchInfo = async () => { + const result = await callNTQQApi<{ info: User }>({ + methodName, + cbCmd: ReceiveCmdS.USER_DETAIL_INFO, + afterFirstCmd: false, + cmdCB: (payload) => { + const success = payload.info.uid == uid + // log("get user detail info", success, uid, payload) + return success + }, + args: [ + { + uid, + }, + null, + ], + }) + const info = result.info + if (info?.uin) { + uidMaps[info.uid] = info.uin + } + return info + } + // 首次请求两次才能拿到的等级信息 + if (!userInfoCache[uid] && getLevel) { + await fetchInfo() + await sleep(1000) + } + let userInfo = await fetchInfo() + userInfoCache[uid] = userInfo + return userInfo + } + + // return 'p_uin=o0xxx; p_skey=orXDssiGF8axxxxxxxxxxxxxx_; skey=' + static async getCookieWithoutSkey() { + return await callNTQQApi({ + className: NTQQApiClass.GROUP_HOME_WORK, + methodName: NTQQApiMethod.UPDATE_SKEY, + args: [ + { + domain: 'qun.qq.com', + }, + ], + }) + } + static async getSkey(groupName: string, groupCode: string): Promise<{ data: string }> { + return await NTQQWindowApi.openWindow<{ data: string }>( + NTQQWindows.GroupHomeWorkWindow, + [ + { + groupName, + groupCode, + source: 'funcbar', + }, + ], + ReceiveCmdS.SKEY_UPDATE, + 1, + ) + // return await callNTQQApi({ + // className: NTQQApiClass.GROUP_HOME_WORK, + // methodName: NTQQApiMethod.UPDATE_SKEY, + // args: [ + // { + // domain: "qun.qq.com" + // } + // ] + // }) + // return await callNTQQApi({ + // methodName: NTQQApiMethod.GET_SKEY, + // args: [ + // { + // "domains": [ + // "qzone.qq.com", + // "qlive.qq.com", + // "qun.qq.com", + // "gamecenter.qq.com", + // "vip.qq.com", + // "qianbao.qq.com", + // "qidian.qq.com" + // ], + // "isForNewPCQQ": false + // }, + // null + // ] + // }) + } + + static async getCookie(group: Group) { + let cookies = await this.getCookieWithoutSkey() + let skey = '' + for (let i = 0; i < 2; i++) { + skey = (await this.getSkey(group.groupName, group.groupCode)).data + skey = skey.trim() + if (skey) { + break + } + await sleep(1000) + } + if (!skey) { + throw new Error('获取skey失败') + } + const bkn = NTQQUserApi.genBkn(skey) + cookies = cookies.replace('skey=;', `skey=${skey};`) + return { cookies, bkn } + } + + static genBkn(sKey: string) { + sKey = sKey || '' + let hash = 5381 + + for (let i = 0; i < sKey.length; i++) { + const code = sKey.charCodeAt(i) + hash = hash + (hash << 5) + code } - static async getSelfInfo() { - return await callNTQQApi({ - className: NTQQApiClass.GLOBAL_DATA, - methodName: NTQQApiMethod.SELF_INFO, timeoutSecond: 2 - }) - } - static async getUserInfo(uid: string) { - const result = await callNTQQApi<{ profiles: Map }>({ - methodName: NTQQApiMethod.USER_INFO, - args: [{force: true, uids: [uid]}, undefined], - cbCmd: ReceiveCmdS.USER_INFO - }) - return result.profiles.get(uid) - } - static async getUserDetailInfo(uid: string, getLevel=false) { - // this.getUserInfo(uid); - let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO - const fetchInfo = async ()=>{ - const result = await callNTQQApi<{ info: User }>({ - methodName, - cbCmd: ReceiveCmdS.USER_DETAIL_INFO, - afterFirstCmd: false, - cmdCB: (payload) => { - const success = payload.info.uid == uid - // log("get user detail info", success, uid, payload) - return success - }, - args: [ - { - uid - }, - null - ] - }) - const info = result.info - if (info?.uin) { - uidMaps[info.uid] = info.uin - } - return info - } - // 首次请求两次才能拿到的等级信息 - if (!userInfoCache[uid] && getLevel) { - await fetchInfo() - await sleep(1000); - } - let userInfo = await fetchInfo() - userInfoCache[uid] = userInfo - return userInfo - } - - // return 'p_uin=o0xxx; p_skey=orXDssiGF8axxxxxxxxxxxxxx_; skey=' - static async getCookieWithoutSkey() { - return await callNTQQApi({ - className: NTQQApiClass.GROUP_HOME_WORK, - methodName: NTQQApiMethod.UPDATE_SKEY, - args: [ - { - domain: "qun.qq.com" - } - ] - }) - } - static async getSkey(groupName: string, groupCode: string): Promise<{data: string}> { - return await NTQQWindowApi.openWindow<{data: string}>(NTQQWindows.GroupHomeWorkWindow, [{ - groupName, - groupCode, - "source": "funcbar" - }], ReceiveCmdS.SKEY_UPDATE, 1); - // return await callNTQQApi({ - // className: NTQQApiClass.GROUP_HOME_WORK, - // methodName: NTQQApiMethod.UPDATE_SKEY, - // args: [ - // { - // domain: "qun.qq.com" - // } - // ] - // }) - // return await callNTQQApi({ - // methodName: NTQQApiMethod.GET_SKEY, - // args: [ - // { - // "domains": [ - // "qzone.qq.com", - // "qlive.qq.com", - // "qun.qq.com", - // "gamecenter.qq.com", - // "vip.qq.com", - // "qianbao.qq.com", - // "qidian.qq.com" - // ], - // "isForNewPCQQ": false - // }, - // null - // ] - // }) - } - - static async getCookie(group: Group){ - let cookies = await this.getCookieWithoutSkey(); - let skey = "" - for (let i = 0; i < 2; i++) { - skey = (await this.getSkey(group.groupName, group.groupCode)).data; - skey = skey.trim(); - if (skey) { - break; - } - await sleep(1000); - } - if (!skey) { - throw new Error("获取skey失败"); - } - const bkn = NTQQUserApi.genBkn(skey); - cookies = cookies.replace("skey=;", `skey=${skey};`); - return {cookies, bkn}; - } - - static genBkn(sKey: string){ - sKey = sKey || ""; - let hash = 5381; - - for (let i = 0; i < sKey.length; i++) { - const code = sKey.charCodeAt(i); - hash = hash + (hash << 5) + code; - } - - return (hash & 0x7FFFFFFF).toString(); - } -} \ No newline at end of file + return (hash & 0x7fffffff).toString() + } +} diff --git a/src/ntqqapi/api/webapi.ts b/src/ntqqapi/api/webapi.ts index 8fcfe9d..542d6bb 100644 --- a/src/ntqqapi/api/webapi.ts +++ b/src/ntqqapi/api/webapi.ts @@ -1,78 +1,76 @@ -import {groups} from "../../common/data"; -import {log} from "../../common/utils"; -import {NTQQUserApi} from "./user"; +import { groups } from '../../common/data' +import { log } from '../../common/utils' +import { NTQQUserApi } from './user' -export class WebApi{ - private static bkn: string; - private static skey: string; - private static pskey: string; - private static cookie: string - private defaultHeaders: Record = { - "User-Agent": "QQ/8.9.28.635 CFNetwork/1312 Darwin/21.0.0" +export class WebApi { + private static bkn: string + private static skey: string + private static pskey: string + private static cookie: string + private defaultHeaders: Record = { + 'User-Agent': 'QQ/8.9.28.635 CFNetwork/1312 Darwin/21.0.0', + } + + constructor() {} + + public async addGroupDigest(groupCode: string, msgSeq: string) { + const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292` + const res = await this.request(url) + return await res.json() + } + + public async getGroupDigest(groupCode: string) { + const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20` + const res = await this.request(url) + log(res.headers) + return await res.json() + } + + private genBkn(sKey: string) { + return NTQQUserApi.genBkn(sKey) + } + private async init() { + if (!WebApi.bkn) { + const group = groups[0] + WebApi.skey = (await NTQQUserApi.getSkey(group.groupName, group.groupCode)).data + WebApi.bkn = this.genBkn(WebApi.skey) + let cookie = await NTQQUserApi.getCookieWithoutSkey() + const pskeyRegex = /p_skey=([^;]+)/ + const match = cookie.match(pskeyRegex) + const pskeyValue = match ? match[1] : null + WebApi.pskey = pskeyValue + if (cookie.indexOf('skey=;') !== -1) { + cookie = cookie.replace('skey=;', `skey=${WebApi.skey};`) + } + WebApi.cookie = cookie + // for(const kv of WebApi.cookie.split(";")){ + // const [key, value] = kv.split("="); + // } + // log("set cookie", key, value) + // await session.defaultSession.cookies.set({ + // url: 'https://qun.qq.com', // 你要请求的域名 + // name: key.trim(), + // value: value.trim(), + // expirationDate: Date.now() / 1000 + 300000, // Cookie 过期时间,例如设置为当前时间之后的300秒 + // }); + // } } + } - constructor() { - + private async request(url: string, method: 'GET' | 'POST' = 'GET', headers: Record = {}) { + await this.init() + url += '&bkn=' + WebApi.bkn + let _headers: Record = { + ...this.defaultHeaders, + ...headers, + Cookie: WebApi.cookie, + credentials: 'include', } - - public async addGroupDigest(groupCode: string, msgSeq: string){ - const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292` - const res = await this.request(url) - return await res.json() + log('request', url, _headers) + const options = { + method: method, + headers: _headers, } - - public async getGroupDigest(groupCode: string){ - const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20` - const res = await this.request(url) - log(res.headers) - return await res.json() - } - - private genBkn(sKey: string){ - return NTQQUserApi.genBkn(sKey); - } - private async init(){ - if (!WebApi.bkn) { - const group = groups[0]; - WebApi.skey = (await NTQQUserApi.getSkey(group.groupName, group.groupCode)).data; - WebApi.bkn = this.genBkn(WebApi.skey); - let cookie = await NTQQUserApi.getCookieWithoutSkey(); - const pskeyRegex = /p_skey=([^;]+)/; - const match = cookie.match(pskeyRegex); - const pskeyValue = match ? match[1] : null; - WebApi.pskey = pskeyValue; - if (cookie.indexOf("skey=;") !== -1) { - cookie = cookie.replace("skey=;", `skey=${WebApi.skey};`); - } - WebApi.cookie = cookie; - // for(const kv of WebApi.cookie.split(";")){ - // const [key, value] = kv.split("="); - // } - // log("set cookie", key, value) - // await session.defaultSession.cookies.set({ - // url: 'https://qun.qq.com', // 你要请求的域名 - // name: key.trim(), - // value: value.trim(), - // expirationDate: Date.now() / 1000 + 300000, // Cookie 过期时间,例如设置为当前时间之后的300秒 - // }); - // } - } - } - - private async request(url: string, method: "GET" | "POST" = "GET", headers: Record = {}){ - - await this.init(); - url += "&bkn=" + WebApi.bkn; - let _headers: Record = { - ...this.defaultHeaders, ...headers, - "Cookie": WebApi.cookie, - credentials: 'include' - } - log("request", url, _headers) - const options = { - method: method, - headers: _headers - } - return fetch(url, options) - } -} \ No newline at end of file + return fetch(url, options) + } +} diff --git a/src/ntqqapi/api/window.ts b/src/ntqqapi/api/window.ts index 6e84bd8..767ad95 100644 --- a/src/ntqqapi/api/window.ts +++ b/src/ntqqapi/api/window.ts @@ -1,49 +1,50 @@ -import {callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod} from "../ntcall"; -import {ReceiveCmd} from "../hook"; -import {BrowserWindow} from "electron"; +import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' +import { ReceiveCmd } from '../hook' +import { BrowserWindow } from 'electron' -export interface NTQQWindow{ - windowName: string, - windowUrlHash: string, +export interface NTQQWindow { + windowName: string + windowUrlHash: string } -export class NTQQWindows{ - static GroupHomeWorkWindow: NTQQWindow = { - windowName: "GroupHomeWorkWindow", - windowUrlHash: "#/group-home-work" - } - static GroupNotifyFilterWindow: NTQQWindow = { - windowName: "GroupNotifyFilterWindow", - windowUrlHash: "#/group-notify-filter" - } - static GroupEssenceWindow: NTQQWindow = { - windowName: "GroupEssenceWindow", - windowUrlHash: "#/group-essence" - } +export class NTQQWindows { + static GroupHomeWorkWindow: NTQQWindow = { + windowName: 'GroupHomeWorkWindow', + windowUrlHash: '#/group-home-work', + } + static GroupNotifyFilterWindow: NTQQWindow = { + windowName: 'GroupNotifyFilterWindow', + windowUrlHash: '#/group-notify-filter', + } + static GroupEssenceWindow: NTQQWindow = { + windowName: 'GroupEssenceWindow', + windowUrlHash: '#/group-essence', + } } -export class NTQQWindowApi{ - - // 打开窗口并获取对应的下发事件 - static async openWindow(ntQQWindow: NTQQWindow, args: any[], cbCmd: ReceiveCmd=null, autoCloseSeconds: number=2){ - const result = await callNTQQApi({ - className: NTQQApiClass.WINDOW_API, - methodName: NTQQApiMethod.OPEN_EXTRA_WINDOW, - cbCmd, - afterFirstCmd: false, - args: [ - ntQQWindow.windowName, - ...args - ] - }) - setTimeout(() => { - for (const w of BrowserWindow.getAllWindows()) { - // log("close window", w.webContents.getURL()) - if (w.webContents.getURL().indexOf(ntQQWindow.windowUrlHash) != -1) { - w.close(); - } - } - }, autoCloseSeconds * 1000); - return result; - } -} \ No newline at end of file +export class NTQQWindowApi { + // 打开窗口并获取对应的下发事件 + static async openWindow( + ntQQWindow: NTQQWindow, + args: any[], + cbCmd: ReceiveCmd = null, + autoCloseSeconds: number = 2, + ) { + const result = await callNTQQApi({ + className: NTQQApiClass.WINDOW_API, + methodName: NTQQApiMethod.OPEN_EXTRA_WINDOW, + cbCmd, + afterFirstCmd: false, + args: [ntQQWindow.windowName, ...args], + }) + setTimeout(() => { + for (const w of BrowserWindow.getAllWindows()) { + // log("close window", w.webContents.getURL()) + if (w.webContents.getURL().indexOf(ntQQWindow.windowUrlHash) != -1) { + w.close() + } + } + }, autoCloseSeconds * 1000) + return result + } +} diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index ae198d7..5e93e26 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -1,6 +1,7 @@ import { AtType, - ElementType, FaceIndex, + ElementType, + FaceIndex, FaceType, PicType, SendArkElement, @@ -11,20 +12,18 @@ import { SendPttElement, SendReplyElement, SendTextElement, - SendVideoElement -} from "./types"; -import {promises as fs} from "node:fs"; -import ffmpeg from "fluent-ffmpeg" -import {NTQQFileApi} from "./api/file"; -import {calculateFileMD5, isGIF} from "../common/utils/file"; -import {log} from "../common/utils/log"; -import {defaultVideoThumb, getVideoInfo} from "../common/utils/video"; -import {encodeSilk} from "../common/utils/audio"; -import {isNull} from "../common/utils"; - + SendVideoElement, +} from './types' +import { promises as fs } from 'node:fs' +import ffmpeg from 'fluent-ffmpeg' +import { NTQQFileApi } from './api/file' +import { calculateFileMD5, isGIF } from '../common/utils/file' +import { log } from '../common/utils/log' +import { defaultVideoThumb, getVideoInfo } from '../common/utils/video' +import { encodeSilk } from '../common/utils/audio' +import { isNull } from '../common/utils' export class SendMsgElementConstructor { - static poke(groupCode: string, uin: string) { return null } @@ -32,50 +31,50 @@ export class SendMsgElementConstructor { static text(content: string): SendTextElement { return { elementType: ElementType.TEXT, - elementId: "", + elementId: '', textElement: { content, atType: AtType.notAt, - atUid: "", - atTinyId: "", - atNtUid: "", + atUid: '', + atTinyId: '', + atNtUid: '', }, - }; + } } static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement { return { elementType: ElementType.TEXT, - elementId: "", + elementId: '', textElement: { content: `@${atName}`, atType, atUid, - atTinyId: "", + atTinyId: '', atNtUid, }, - }; + } } static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { return { elementType: ElementType.REPLY, - elementId: "", + elementId: '', replyElement: { replayMsgSeq: msgSeq, // raw.msgSeq - replayMsgId: msgId, // raw.msgId + replayMsgId: msgId, // raw.msgId senderUin: senderUin, senderUinStr: senderUinStr, - } + }, } } - static async pic(picPath: string, summary: string = "", subType: 0 | 1 = 0): Promise { - const {md5, fileName, path, fileSize} = await NTQQFileApi.uploadFile(picPath, ElementType.PIC, subType); + static async pic(picPath: string, summary: string = '', subType: 0 | 1 = 0): Promise { + const { md5, fileName, path, fileSize } = await NTQQFileApi.uploadFile(picPath, ElementType.PIC, subType) if (fileSize === 0) { - throw "文件异常,大小为0"; + throw '文件异常,大小为0' } - const imageSize = await NTQQFileApi.getImageSize(picPath); + const imageSize = await NTQQFileApi.getImageSize(picPath) const picElement = { md5HexStr: md5, fileSize: fileSize.toString(), @@ -86,83 +85,87 @@ export class SendMsgElementConstructor { original: true, picType: isGIF(picPath) ? PicType.gif : PicType.jpg, picSubType: subType, - fileUuid: "", - fileSubId: "", + fileUuid: '', + fileSubId: '', thumbFileSize: 0, - summary - }; - log("图片信息", picElement) + summary, + } + log('图片信息', picElement) return { elementType: ElementType.PIC, - elementId: "", + elementId: '', picElement, - }; + } } - static async file(filePath: string, fileName: string = ""): Promise { - const {md5, fileName: _fileName, path, fileSize} = await NTQQFileApi.uploadFile(filePath, ElementType.FILE); + static async file(filePath: string, fileName: string = ''): Promise { + const { md5, fileName: _fileName, path, fileSize } = await NTQQFileApi.uploadFile(filePath, ElementType.FILE) if (fileSize === 0) { - throw "文件异常,大小为0"; + throw '文件异常,大小为0' } let element: SendFileElement = { elementType: ElementType.FILE, - elementId: "", + elementId: '', fileElement: { fileName: fileName || _fileName, - "filePath": path, - "fileSize": (fileSize).toString(), - } + filePath: path, + fileSize: fileSize.toString(), + }, } - return element; + return element } - static async video(filePath: string, fileName: string = "", diyThumbPath: string = ""): Promise { - let {fileName: _fileName, path, fileSize, md5} = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO); + static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise { + let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO) if (fileSize === 0) { - throw "文件异常,大小为0"; + throw '文件异常,大小为0' } - const pathLib = require("path"); + const pathLib = require('path') let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`) thumbDir = pathLib.dirname(thumbDir) // log("thumb 目录", thumb) let videoInfo = { - width: 1920, height: 1080, + width: 1920, + height: 1080, time: 15, - format: "mp4", + format: 'mp4', size: fileSize, - filePath - }; + filePath, + } try { - videoInfo = await getVideoInfo(path); - log("视频信息", videoInfo) + videoInfo = await getVideoInfo(path) + log('视频信息', videoInfo) } catch (e) { - log("获取视频信息失败", e) + log('获取视频信息失败', e) } const createThumb = new Promise((resolve, reject) => { const thumbFileName = `${md5}_0.png` const thumbPath = pathLib.join(thumbDir, thumbFileName) - log("开始生成视频缩略图", filePath); - let completed = false; + log('开始生成视频缩略图', filePath) + let completed = false function useDefaultThumb() { - if (completed) return; - log("获取视频封面失败,使用默认封面"); - fs.writeFile(thumbPath, defaultVideoThumb).then(() => { - resolve(thumbPath); - }).catch(reject) + if (completed) return + log('获取视频封面失败,使用默认封面') + fs.writeFile(thumbPath, defaultVideoThumb) + .then(() => { + resolve(thumbPath) + }) + .catch(reject) } - setTimeout(useDefaultThumb, 5000); + setTimeout(useDefaultThumb, 5000) ffmpeg(filePath) - .on("end", () => { - }) - .on("error", (err) => { + .on('end', () => {}) + .on('error', (err) => { if (diyThumbPath) { - fs.copyFile(diyThumbPath, thumbPath).then(() => { - completed = true; - resolve(thumbPath); - }).catch(reject) + fs.copyFile(diyThumbPath, thumbPath) + .then(() => { + completed = true + resolve(thumbPath) + }) + .catch(reject) } else { useDefaultThumb() } @@ -171,23 +174,24 @@ export class SendMsgElementConstructor { timestamps: [0], filename: thumbFileName, folder: thumbDir, - size: videoInfo.width + "x" + videoInfo.height - }).on("end", () => { - log("生成视频缩略图", thumbPath) - completed = true; - resolve(thumbPath); - }) + size: videoInfo.width + 'x' + videoInfo.height, + }) + .on('end', () => { + log('生成视频缩略图', thumbPath) + completed = true + resolve(thumbPath) + }) }) let thumbPath = new Map() - const _thumbPath = await createThumb; - log("生成缩略图", _thumbPath) - const thumbSize = (await fs.stat(_thumbPath)).size; + const _thumbPath = await createThumb + log('生成缩略图', _thumbPath) + const thumbSize = (await fs.stat(_thumbPath)).size // log("生成缩略图", _thumbPath) thumbPath.set(0, _thumbPath) - const thumbMd5 = await calculateFileMD5(_thumbPath); + const thumbMd5 = await calculateFileMD5(_thumbPath) let element: SendVideoElement = { elementType: ElementType.VIDEO, - elementId: "", + elementId: '', videoElement: { fileName: fileName || _fileName, filePath: path, @@ -198,7 +202,7 @@ export class SendMsgElementConstructor { thumbSize, thumbWidth: videoInfo.width, thumbHeight: videoInfo.height, - fileSize: "" + fileSize, + fileSize: '' + fileSize, // fileUuid: "", // transferStatus: 0, // progress: 0, @@ -209,25 +213,25 @@ export class SendMsgElementConstructor { // fileFormat: 2, // import_rich_media_context: null, // sourceVideoCodecFormat: 2 - } + }, } - log("videoElement", element) - return element; + log('videoElement', element) + return element } static async ptt(pttPath: string): Promise { - const {converted, path: silkPath, duration} = await encodeSilk(pttPath); + const { converted, path: silkPath, duration } = await encodeSilk(pttPath) // log("生成语音", silkPath, duration); - const {md5, fileName, path, fileSize} = await NTQQFileApi.uploadFile(silkPath, ElementType.PTT); + const { md5, fileName, path, fileSize } = await NTQQFileApi.uploadFile(silkPath, ElementType.PTT) if (fileSize === 0) { - throw "文件异常,大小为0"; + throw '文件异常,大小为0' } if (converted) { - fs.unlink(silkPath).then(); + fs.unlink(silkPath).then() } return { elementType: ElementType.PTT, - elementId: "", + elementId: '', pttElement: { fileName: fileName, filePath: path, @@ -239,35 +243,33 @@ export class SendMsgElementConstructor { voiceType: 1, voiceChangeType: 0, canConvert2Text: true, - waveAmplitudes: [ - 0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17, - ], - fileSubId: "", + waveAmplitudes: [0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17], + fileSubId: '', playState: 1, autoConvertText: 0, - } - }; - } - - static face(faceId: number): SendFaceElement { - faceId = parseInt(faceId.toString()); - return { - elementType: ElementType.FACE, - elementId: "", - faceElement: { - faceIndex: faceId, - faceType: faceId < 222 ? FaceType.normal : FaceType.normal2, - } + }, } } - static mface(emojiPackageId: number, emojiId: string, key: string):SendMarketFaceElement{ + static face(faceId: number): SendFaceElement { + faceId = parseInt(faceId.toString()) + return { + elementType: ElementType.FACE, + elementId: '', + faceElement: { + faceIndex: faceId, + faceType: faceId < 222 ? FaceType.normal : FaceType.normal2, + }, + } + } + + static mface(emojiPackageId: number, emojiId: string, key: string): SendMarketFaceElement { return { elementType: ElementType.MFACE, marketFaceElement: { emojiPackageId, emojiId, - key + key, }, } } @@ -276,56 +278,56 @@ export class SendMsgElementConstructor { // 实际测试并不能控制结果 // 随机1到6 - if (isNull(resultId)) resultId = Math.floor(Math.random() * 6) + 1; + if (isNull(resultId)) resultId = Math.floor(Math.random() * 6) + 1 return { elementType: ElementType.FACE, - elementId: "", + elementId: '', faceElement: { faceIndex: FaceIndex.dice, faceType: FaceType.dice, - "faceText": "[骰子]", - "packId": "1", - "stickerId": "33", - "sourceType": 1, - "stickerType": 2, + faceText: '[骰子]', + packId: '1', + stickerId: '33', + sourceType: 1, + stickerType: 2, resultId: resultId.toString(), - "surpriseId": "", + surpriseId: '', // "randomType": 1, - } + }, } } // 猜拳(石头剪刀布)表情 static rps(resultId: number | null): SendFaceElement { // 实际测试并不能控制结果 - if (isNull(resultId)) resultId = Math.floor(Math.random() * 3) + 1; + if (isNull(resultId)) resultId = Math.floor(Math.random() * 3) + 1 return { elementType: ElementType.FACE, - elementId: "", + elementId: '', faceElement: { - "faceIndex": FaceIndex.RPS, - "faceText": "[包剪锤]", - "faceType": 3, - "packId": "1", - "stickerId": "34", - "sourceType": 1, - "stickerType": 2, - "resultId": resultId.toString(), - "surpriseId": "", + faceIndex: FaceIndex.RPS, + faceText: '[包剪锤]', + faceType: 3, + packId: '1', + stickerId: '34', + sourceType: 1, + stickerType: 2, + resultId: resultId.toString(), + surpriseId: '', // "randomType": 1, - } + }, } } static ark(data: any): SendArkElement { return { elementType: ElementType.ARK, - elementId: "", + elementId: '', arkElement: { bytesData: data, linkInfo: null, - subElementType: null - } + subElementType: null, + }, } } -} \ No newline at end of file +} diff --git a/src/ntqqapi/external/crychic/index.ts b/src/ntqqapi/external/crychic/index.ts index f090c01..c5af416 100644 --- a/src/ntqqapi/external/crychic/index.ts +++ b/src/ntqqapi/external/crychic/index.ts @@ -1,53 +1,52 @@ -import {log} from "../../../common/utils"; -import {NTQQApi} from "../../ntcall"; +import { log } from '../../../common/utils' +import { NTQQApi } from '../../ntcall' type PokeHandler = (id: string, isGroup: boolean) => void type CrychicHandler = (event: string, id: string, isGroup: boolean) => void let pokeRecords: Record = {} -class Crychic{ - private crychic: any = undefined - - loadNode(){ - if (!this.crychic){ - try { - this.crychic = require("./crychic-win32-x64.node") - this.crychic.init() - }catch (e) { - log("crychic加载失败", e) - } +class Crychic { + private crychic: any = undefined + loadNode() { + if (!this.crychic) { + try { + this.crychic = require('./crychic-win32-x64.node') + this.crychic.init() + } catch (e) { + log('crychic加载失败', e) + } + } + } + registerPokeHandler(fn: PokeHandler) { + this.registerHandler((event, id, isGroup) => { + if (event === 'poke') { + let existTime = pokeRecords[id] + if (existTime) { + if (Date.now() - existTime < 1500) { + return + } } - } - registerPokeHandler(fn: PokeHandler){ - this.registerHandler((event, id, isGroup)=>{ - if (event === "poke"){ - let existTime = pokeRecords[id] - if (existTime) { - if (Date.now() - existTime < 1500) { - return - } - } - pokeRecords[id] = Date.now() - fn(id, isGroup); - } - }) - } - registerHandler(fn: CrychicHandler){ - if (!this.crychic) return; - this.crychic.setCryHandler(fn) - } - sendFriendPoke(friendUid: string){ - if (!this.crychic) return; - this.crychic.sendFriendPoke(parseInt(friendUid)) - NTQQApi.fetchUnitedCommendConfig().then() - } - sendGroupPoke(groupCode: string, memberUin: string){ - if (!this.crychic) return; - this.crychic.sendGroupPoke(parseInt(memberUin), parseInt(groupCode)) - NTQQApi.fetchUnitedCommendConfig().then() - } + pokeRecords[id] = Date.now() + fn(id, isGroup) + } + }) + } + registerHandler(fn: CrychicHandler) { + if (!this.crychic) return + this.crychic.setCryHandler(fn) + } + sendFriendPoke(friendUid: string) { + if (!this.crychic) return + this.crychic.sendFriendPoke(parseInt(friendUid)) + NTQQApi.fetchUnitedCommendConfig().then() + } + sendGroupPoke(groupCode: string, memberUin: string) { + if (!this.crychic) return + this.crychic.sendGroupPoke(parseInt(memberUin), parseInt(groupCode)) + NTQQApi.fetchUnitedCommendConfig().then() + } } -export const crychic = new Crychic() \ No newline at end of file +export const crychic = new Crychic() diff --git a/src/ntqqapi/external/moehook/hook.ts b/src/ntqqapi/external/moehook/hook.ts index 0a51249..52f68a5 100644 --- a/src/ntqqapi/external/moehook/hook.ts +++ b/src/ntqqapi/external/moehook/hook.ts @@ -1,30 +1,29 @@ -import {log} from "../../../common/utils"; +import { log } from '../../../common/utils' interface MoeHook { - GetRkey: () => string, // Return '&rkey=xxx' + GetRkey: () => string // Return '&rkey=xxx' HookRkey: () => string } - class HookApi { - private readonly moeHook: MoeHook | null = null; + private readonly moeHook: MoeHook | null = null constructor() { try { - this.moeHook = require('./MoeHook.node'); - console.log("hook rkey地址", this.moeHook!.HookRkey()); + this.moeHook = require('./MoeHook.node') + console.log('hook rkey地址', this.moeHook!.HookRkey()) } catch (e) { - console.log('加载 moehoo 失败', e); + console.log('加载 moehoo 失败', e) } } getRKey(): string { - return this.moeHook?.GetRkey() || ''; + return this.moeHook?.GetRkey() || '' } isAvailable() { - return !!this.moeHook; + return !!this.moeHook } } -export const hookApi = new HookApi(); +export const hookApi = new HookApi() diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index fe78af7..09002aa 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -1,7 +1,7 @@ -import {BrowserWindow} from 'electron'; -import {NTQQApiClass, NTQQApiMethod} from "./ntcall"; -import {NTQQMsgApi, sendMessagePool} from "./api/msg" -import {ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User} from "./types"; +import { BrowserWindow } from 'electron' +import { NTQQApiClass, NTQQApiMethod } from './ntcall' +import { NTQQMsgApi, sendMessagePool } from './api/msg' +import { ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types' import { deleteGroup, friends, @@ -10,104 +10,100 @@ import { groups, selfInfo, tempGroupCodeMap, - uidMaps -} from "../common/data"; -import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent"; -import {v4 as uuidv4} from "uuid" -import {postOB11Event} from "../onebot11/server/postOB11Event"; -import {getConfigUtil, HOOK_LOG} from "../common/config"; -import fs from "fs"; -import {dbUtil} from "../common/db"; -import {NTQQGroupApi} from "./api/group"; -import {log} from "../common/utils/log"; -import {isNumeric, sleep} from "../common/utils/helper"; -import {OB11Constructor} from "../onebot11/constructor"; + uidMaps, +} from '../common/data' +import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' +import { v4 as uuidv4 } from 'uuid' +import { postOB11Event } from '../onebot11/server/postOB11Event' +import { getConfigUtil, HOOK_LOG } from '../common/config' +import fs from 'fs' +import { dbUtil } from '../common/db' +import { NTQQGroupApi } from './api/group' +import { log } from '../common/utils/log' +import { isNumeric, sleep } from '../common/utils/helper' +import { OB11Constructor } from '../onebot11/constructor' export let hookApiCallbacks: Record void> = {} export let ReceiveCmdS = { - RECENT_CONTACT: "nodeIKernelRecentContactListener/onRecentContactListChangedVer2", - UPDATE_MSG: "nodeIKernelMsgListener/onMsgInfoListUpdate", - UPDATE_ACTIVE_MSG: "nodeIKernelMsgListener/onActiveMsgInfoUpdate", + RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2', + UPDATE_MSG: 'nodeIKernelMsgListener/onMsgInfoListUpdate', + UPDATE_ACTIVE_MSG: 'nodeIKernelMsgListener/onActiveMsgInfoUpdate', NEW_MSG: `nodeIKernelMsgListener/onRecvMsg`, NEW_ACTIVE_MSG: `nodeIKernelMsgListener/onRecvActiveMsg`, - SELF_SEND_MSG: "nodeIKernelMsgListener/onAddSendMsg", - USER_INFO: "nodeIKernelProfileListener/onProfileSimpleChanged", - USER_DETAIL_INFO: "nodeIKernelProfileListener/onProfileDetailInfoChanged", - GROUPS: "nodeIKernelGroupListener/onGroupListUpdate", - GROUPS_STORE: "onGroupListUpdate", - GROUP_MEMBER_INFO_UPDATE: "nodeIKernelGroupListener/onMemberInfoChange", - FRIENDS: "onBuddyListChange", - MEDIA_DOWNLOAD_COMPLETE: "nodeIKernelMsgListener/onRichMediaDownloadComplete", - UNREAD_GROUP_NOTIFY: "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated", - GROUP_NOTIFY: "nodeIKernelGroupListener/onGroupSingleScreenNotifies", - FRIEND_REQUEST: "nodeIKernelBuddyListener/onBuddyReqChange", + SELF_SEND_MSG: 'nodeIKernelMsgListener/onAddSendMsg', + USER_INFO: 'nodeIKernelProfileListener/onProfileSimpleChanged', + USER_DETAIL_INFO: 'nodeIKernelProfileListener/onProfileDetailInfoChanged', + GROUPS: 'nodeIKernelGroupListener/onGroupListUpdate', + GROUPS_STORE: 'onGroupListUpdate', + GROUP_MEMBER_INFO_UPDATE: 'nodeIKernelGroupListener/onMemberInfoChange', + FRIENDS: 'onBuddyListChange', + MEDIA_DOWNLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaDownloadComplete', + UNREAD_GROUP_NOTIFY: 'nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated', + GROUP_NOTIFY: 'nodeIKernelGroupListener/onGroupSingleScreenNotifies', + FRIEND_REQUEST: 'nodeIKernelBuddyListener/onBuddyReqChange', SELF_STATUS: 'nodeIKernelProfileListener/onSelfStatusChanged', - CACHE_SCAN_FINISH: "nodeIKernelStorageCleanListener/onFinishScan", - MEDIA_UPLOAD_COMPLETE: "nodeIKernelMsgListener/onRichMediaUploadComplete", - SKEY_UPDATE: "onSkeyUpdate" + CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan', + MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete', + SKEY_UPDATE: 'onSkeyUpdate', } -export type ReceiveCmd = typeof ReceiveCmdS[keyof typeof ReceiveCmdS] +export type ReceiveCmd = (typeof ReceiveCmdS)[keyof typeof ReceiveCmdS] interface NTQQApiReturnData extends Array { 0: { - "type": "request", - "eventName": NTQQApiClass, - "callbackId"?: string - }, - 1: - { - cmdName: ReceiveCmd, - cmdType: "event", - payload: PayloadType - }[] + type: 'request' + eventName: NTQQApiClass + callbackId?: string + } + 1: { + cmdName: ReceiveCmd + cmdType: 'event' + payload: PayloadType + }[] } let receiveHooks: Array<{ - method: ReceiveCmd[], - hookFunc: ((payload: any) => void | Promise) + method: ReceiveCmd[] + hookFunc: (payload: any) => void | Promise id: string }> = [] let callHooks: Array<{ - method: NTQQApiMethod[], - hookFunc: ((callParams: unknown[]) => void | Promise) + method: NTQQApiMethod[] + hookFunc: (callParams: unknown[]) => void | Promise }> = [] - export function hookNTQQApiReceive(window: BrowserWindow) { - const originalSend = window.webContents.send; + const originalSend = window.webContents.send const patchSend = (channel: string, ...args: NTQQApiReturnData) => { // console.log("hookNTQQApiReceive", channel, args) let isLogger = false try { - isLogger = args[0]?.eventName?.startsWith("ns-LoggerApi") - } catch (e) { - - } + isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi') + } catch (e) {} if (!isLogger) { try { HOOK_LOG && log(`received ntqq api message: ${channel}`, args) } catch (e) { - log("hook log error", e, args) + log('hook log error', e, args) } } try { if (args?.[1] instanceof Array) { for (let receiveData of args?.[1]) { - const ntQQApiMethodName = receiveData.cmdName; + const ntQQApiMethodName = receiveData.cmdName // log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData)) for (let hook of receiveHooks) { if (hook.method.includes(ntQQApiMethodName)) { new Promise((resolve, reject) => { try { let _ = hook.hookFunc(receiveData.payload) - if (hook.hookFunc.constructor.name === "AsyncFunction") { - (_ as Promise).then() + if (hook.hookFunc.constructor.name === 'AsyncFunction') { + ;(_ as Promise).then() } } catch (e) { - log("hook error", e, receiveData.payload) + log('hook error', e, receiveData.payload) } }).then() } @@ -116,101 +112,96 @@ export function hookNTQQApiReceive(window: BrowserWindow) { } if (args[0]?.callbackId) { // log("hookApiCallback", hookApiCallbacks, args) - const callbackId = args[0].callbackId; + const callbackId = args[0].callbackId if (hookApiCallbacks[callbackId]) { // log("callback found") new Promise((resolve, reject) => { - hookApiCallbacks[callbackId](args[1]); + hookApiCallbacks[callbackId](args[1]) }).then() - delete hookApiCallbacks[callbackId]; + delete hookApiCallbacks[callbackId] } } } catch (e) { - log("hookNTQQApiReceive error", e.stack.toString(), args) + log('hookNTQQApiReceive error', e.stack.toString(), args) } - originalSend.call(window.webContents, channel, ...args); + originalSend.call(window.webContents, channel, ...args) } - window.webContents.send = patchSend; + window.webContents.send = patchSend } export function hookNTQQApiCall(window: BrowserWindow) { // 监听调用NTQQApi - let webContents = window.webContents as any; - const ipc_message_proxy = webContents._events["-ipc-message"]?.[0] || webContents._events["-ipc-message"]; + let webContents = window.webContents as any + const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message'] const proxyIpcMsg = new Proxy(ipc_message_proxy, { apply(target, thisArg, args) { // console.log(thisArg, args); let isLogger = false try { - isLogger = args[3][0].eventName.startsWith("ns-LoggerApi") - } catch (e) { - - } + isLogger = args[3][0].eventName.startsWith('ns-LoggerApi') + } catch (e) {} if (!isLogger) { try { - HOOK_LOG && log("call NTQQ api", thisArg, args); - } catch (e) { - - } + HOOK_LOG && log('call NTQQ api', thisArg, args) + } catch (e) {} try { - const _args: unknown[] = args[3][1]; - const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod; - const callParams = _args.slice(1); - callHooks.forEach(hook => { + const _args: unknown[] = args[3][1] + const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod + const callParams = _args.slice(1) + callHooks.forEach((hook) => { if (hook.method.includes(cmdName)) { new Promise((resolve, reject) => { try { let _ = hook.hookFunc(callParams) - if (hook.hookFunc.constructor.name === "AsyncFunction") { - (_ as Promise).then() + if (hook.hookFunc.constructor.name === 'AsyncFunction') { + ;(_ as Promise).then() } } catch (e) { - log("hook call error", e, _args) + log('hook call error', e, _args) } }).then() } }) - } catch (e) { - - } + } catch (e) {} } - return target.apply(thisArg, args); + return target.apply(thisArg, args) }, - }); - if (webContents._events["-ipc-message"]?.[0]) { - webContents._events["-ipc-message"][0] = proxyIpcMsg; + }) + if (webContents._events['-ipc-message']?.[0]) { + webContents._events['-ipc-message'][0] = proxyIpcMsg } else { - webContents._events["-ipc-message"] = proxyIpcMsg; + webContents._events['-ipc-message'] = proxyIpcMsg } - const ipc_invoke_proxy = webContents._events["-ipc-invoke"]?.[0] || webContents._events["-ipc-invoke"]; + const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke'] const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, { apply(target, thisArg, args) { // console.log(args); - HOOK_LOG && log("call NTQQ invoke api", thisArg, args) - args[0]["_replyChannel"]["sendReply"] = new Proxy(args[0]["_replyChannel"]["sendReply"], { + HOOK_LOG && log('call NTQQ invoke api', thisArg, args) + args[0]['_replyChannel']['sendReply'] = new Proxy(args[0]['_replyChannel']['sendReply'], { apply(sendtarget, sendthisArg, sendargs) { - sendtarget.apply(sendthisArg, sendargs); - } - }); - let ret = target.apply(thisArg, args); + sendtarget.apply(sendthisArg, sendargs) + }, + }) + let ret = target.apply(thisArg, args) try { - HOOK_LOG && log("call NTQQ invoke api return", ret) - } catch (e) { - - } - return ret; - } - }); - if (webContents._events["-ipc-invoke"]?.[0]) { - webContents._events["-ipc-invoke"][0] = proxyIpcInvoke; + HOOK_LOG && log('call NTQQ invoke api return', ret) + } catch (e) {} + return ret + }, + }) + if (webContents._events['-ipc-invoke']?.[0]) { + webContents._events['-ipc-invoke'][0] = proxyIpcInvoke } else { - webContents._events["-ipc-invoke"] = proxyIpcInvoke; + webContents._events['-ipc-invoke'] = proxyIpcInvoke } } -export function registerReceiveHook(method: ReceiveCmd | ReceiveCmd[], hookFunc: (payload: PayloadType) => void): string { +export function registerReceiveHook( + method: ReceiveCmd | ReceiveCmd[], + hookFunc: (payload: PayloadType) => void, +): string { const id = uuidv4() if (!Array.isArray(method)) { method = [method] @@ -218,59 +209,64 @@ export function registerReceiveHook(method: ReceiveCmd | ReceiveCmd receiveHooks.push({ method, hookFunc, - id + id, }) - return id; + return id } -export function registerCallHook(method: NTQQApiMethod | NTQQApiMethod[], hookFunc: (callParams: unknown[]) => void | Promise): void { +export function registerCallHook( + method: NTQQApiMethod | NTQQApiMethod[], + hookFunc: (callParams: unknown[]) => void | Promise, +): void { if (!Array.isArray(method)) { method = [method] } callHooks.push({ method, - hookFunc + hookFunc, }) } export function removeReceiveHook(id: string) { - const index = receiveHooks.findIndex(h => h.id === id) - receiveHooks.splice(index, 1); + const index = receiveHooks.findIndex((h) => h.id === id) + receiveHooks.splice(index, 1) } -let activatedGroups: string[] = []; +let activatedGroups: string[] = [] async function updateGroups(_groups: Group[], needUpdate: boolean = true) { for (let group of _groups) { - log("update group", group); - if (group.privilegeFlag === 0){ - deleteGroup(group.groupCode); - continue; + log('update group', group) + if (group.privilegeFlag === 0) { + deleteGroup(group.groupCode) + continue } - log("update group", group) + log('update group', group) // if (!activatedGroups.includes(group.groupCode)) { - NTQQMsgApi.activateChat({peerUid: group.groupCode, chatType: ChatType.group}).then((r) => { - // activatedGroups.push(group.groupCode); - // log(`激活群聊天窗口${group.groupName}(${group.groupCode})`, r) - // if (r.result !== 0) { - // setTimeout(() => NTQQMsgApi.activateGroupChat(group.groupCode).then(r => log(`再次激活群聊天窗口${group.groupName}(${group.groupCode})`, r)), 500); - // }else { - // } - }).catch(log) + NTQQMsgApi.activateChat({ peerUid: group.groupCode, chatType: ChatType.group }) + .then((r) => { + // activatedGroups.push(group.groupCode); + // log(`激活群聊天窗口${group.groupName}(${group.groupCode})`, r) + // if (r.result !== 0) { + // setTimeout(() => NTQQMsgApi.activateGroupChat(group.groupCode).then(r => log(`再次激活群聊天窗口${group.groupName}(${group.groupCode})`, r)), 500); + // }else { + // } + }) + .catch(log) // } - let existGroup = groups.find(g => g.groupCode == group.groupCode); + let existGroup = groups.find((g) => g.groupCode == group.groupCode) if (existGroup) { - Object.assign(existGroup, group); + Object.assign(existGroup, group) } else { - groups.push(group); - existGroup = group; + groups.push(group) + existGroup = group } if (needUpdate) { - const members = await NTQQGroupApi.getGroupMembers(group.groupCode); + const members = await NTQQGroupApi.getGroupMembers(group.groupCode) if (members) { - existGroup.members = members; + existGroup.members = members } } } @@ -278,22 +274,22 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) { async function processGroupEvent(payload: { groupList: Group[] }) { try { - const newGroupList = payload.groupList; + const newGroupList = payload.groupList for (const group of newGroupList) { - let existGroup = groups.find(g => g.groupCode == group.groupCode); + let existGroup = groups.find((g) => g.groupCode == group.groupCode) if (existGroup) { if (existGroup.memberCount > group.memberCount) { - log(`群(${group.groupCode})成员数量减少${existGroup.memberCount} -> ${group.memberCount}`); - const oldMembers = existGroup.members; + log(`群(${group.groupCode})成员数量减少${existGroup.memberCount} -> ${group.memberCount}`) + const oldMembers = existGroup.members - await sleep(200); // 如果请求QQ API的速度过快,通常无法正确拉取到最新的群信息,因此这里人为引入一个延时 - const newMembers = await NTQQGroupApi.getGroupMembers(group.groupCode); + await sleep(200) // 如果请求QQ API的速度过快,通常无法正确拉取到最新的群信息,因此这里人为引入一个延时 + const newMembers = await NTQQGroupApi.getGroupMembers(group.groupCode) - group.members = newMembers; - const newMembersSet = new Set(); // 建立索引降低时间复杂度 + group.members = newMembers + const newMembersSet = new Set() // 建立索引降低时间复杂度 for (const member of newMembers) { - newMembersSet.add(member.uin); + newMembersSet.add(member.uin) } // 判断bot是否是管理员,如果是管理员不需要从这里得知有人退群,这里的退群无法得知是主动退群还是被踢 @@ -303,60 +299,67 @@ async function processGroupEvent(payload: { groupList: Group[] }) { } for (const member of oldMembers) { if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) { - postOB11Event(new OB11GroupDecreaseEvent(parseInt(group.groupCode), parseInt(member.uin), parseInt(member.uin), "leave")); - break; + postOB11Event( + new OB11GroupDecreaseEvent( + parseInt(group.groupCode), + parseInt(member.uin), + parseInt(member.uin), + 'leave', + ), + ) + break } } } - if (group.privilegeFlag === 0){ - deleteGroup(group.groupCode); + if (group.privilegeFlag === 0) { + deleteGroup(group.groupCode) } } } - updateGroups(newGroupList, false).then(); + updateGroups(newGroupList, false).then() } catch (e) { - updateGroups(payload.groupList).then(); - log("更新群信息错误", e.stack.toString()); + updateGroups(payload.groupList).then() + log('更新群信息错误', e.stack.toString()) } } // 群列表变动 -registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmdS.GROUPS, (payload) => { +registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => { // updateType 3是群列表变动,2是群成员变动 // log("群列表变动", payload.updateType, payload.groupList) if (payload.updateType != 2) { - updateGroups(payload.groupList).then(); + updateGroups(payload.groupList).then() } else { - if (process.platform == "win32") { - processGroupEvent(payload).then(); + if (process.platform == 'win32') { + processGroupEvent(payload).then() } } }) -registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => { +registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => { // updateType 3是群列表变动,2是群成员变动 // log("群列表变动", payload.updateType, payload.groupList) if (payload.updateType != 2) { - updateGroups(payload.groupList).then(); + updateGroups(payload.groupList).then() } else { - if (process.platform != "win32") { - processGroupEvent(payload).then(); + if (process.platform != 'win32') { + processGroupEvent(payload).then() } } }) registerReceiveHook<{ - groupCode: string, - dataSource: number, + groupCode: string + dataSource: number members: Set }>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => { - const groupCode = payload.groupCode; - const members = Array.from(payload.members.values()); + const groupCode = payload.groupCode + const members = Array.from(payload.members.values()) // log("群成员信息变动", groupCode, members) for (const member of members) { - const existMember = await getGroupMember(groupCode, member.uin); + const existMember = await getGroupMember(groupCode, member.uin) if (existMember) { - Object.assign(existMember, member); + Object.assign(existMember, member) } } // const existGroup = groups.find(g => g.groupCode == groupCode); @@ -377,13 +380,13 @@ registerReceiveHook<{ // 好友列表变动 registerReceiveHook<{ - data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[] -}>(ReceiveCmdS.FRIENDS, payload => { + data: { categoryId: number; categroyName: string; categroyMbCount: number; buddyList: User[] }[] +}>(ReceiveCmdS.FRIENDS, (payload) => { for (const fData of payload.data) { - const _friends = fData.buddyList; + const _friends = fData.buddyList for (let friend of _friends) { - NTQQMsgApi.activateChat({peerUid: friend.uid, chatType: ChatType.friend}).then() - let existFriend = friends.find(f => f.uin == friend.uin) + NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then() + let existFriend = friends.find((f) => f.uin == friend.uin) if (!existFriend) { friends.push(friend) } else { @@ -396,24 +399,23 @@ registerReceiveHook<{ registerReceiveHook<{ msgList: Array }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => { // 保存一下uid for (const message of payload.msgList) { - const uid = message.senderUid; - const uin = message.senderUin; + const uid = message.senderUid + const uin = message.senderUin if (uid && uin) { if (message.chatType === ChatType.temp) { - dbUtil.getReceivedTempUinMap().then(receivedTempUinMap => { + dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => { if (!receivedTempUinMap[uin]) { - receivedTempUinMap[uin] = uid; + receivedTempUinMap[uin] = uid dbUtil.setReceivedTempUinMap(receivedTempUinMap) } }) } - uidMaps[uid] = uin; + uidMaps[uid] = uin } } - // 自动清理新消息文件 - const {autoDeleteFile} = getConfigUtil().getConfig(); + const { autoDeleteFile } = getConfigUtil().getConfig() if (!autoDeleteFile) { return } @@ -436,15 +438,15 @@ registerReceiveHook<{ msgList: Array }>([ReceiveCmdS.NEW_MSG, Receiv } const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement if (aioOpGrayTipElement) { - tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat; + tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat } // log("需要清理的文件", pathList); for (const path of pathList) { if (path) { fs.unlink(picPath, () => { - log("删除文件成功", path) - }); + log('删除文件成功', path) + }) } } }, getConfigUtil().getConfig().autoDeleteFileSecond * 1000) @@ -452,18 +454,18 @@ registerReceiveHook<{ msgList: Array }>([ReceiveCmdS.NEW_MSG, Receiv } }) -registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({msgRecord}) => { - const message = msgRecord; - const peerUid = message.peerUid; +registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => { + const message = msgRecord + const peerUid = message.peerUid // log("收到自己发送成功的消息", Object.keys(sendMessagePool), message); // log("收到自己发送成功的消息", message.msgId, message.msgSeq); dbUtil.addMsg(message).then() const sendCallback = sendMessagePool[peerUid] if (sendCallback) { try { - sendCallback(message); + sendCallback(message) } catch (e) { - log("receive self msg error", e.stack) + log('receive self msg error', e.stack) } } }) @@ -472,35 +474,33 @@ registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info selfInfo.online = info.info.status !== 20 }) - let activatedPeerUids: string[] = [] registerReceiveHook<{ changedRecentContactLists: { - listType: number, sortedContactList: string[], + listType: number + sortedContactList: string[] changedList: { - id: string, // peerUid + id: string // peerUid chatType: ChatType }[] }[] }>(ReceiveCmdS.RECENT_CONTACT, async (payload) => { for (const recentContact of payload.changedRecentContactLists) { for (const changedContact of recentContact.changedList) { - if (activatedPeerUids.includes(changedContact.id)) continue; + if (activatedPeerUids.includes(changedContact.id)) continue activatedPeerUids.push(changedContact.id) - const peer = {peerUid: changedContact.id, chatType: changedContact.chatType} + const peer = { peerUid: changedContact.id, chatType: changedContact.chatType } if (changedContact.chatType === ChatType.temp) { - log("收到临时会话消息", peer) - NTQQMsgApi.activateChatAndGetHistory(peer).then( - () => { - NTQQMsgApi.getMsgHistory(peer, "", 20).then(({msgList}) => { - let lastTempMsg = msgList.pop() - log("激活窗口之前的第一条临时会话消息:", lastTempMsg) - if ((Date.now() / 1000) - parseInt(lastTempMsg.msgTime) < 5) { - OB11Constructor.message(lastTempMsg).then(r => postOB11Event(r)) - } - }) - } - ) + log('收到临时会话消息', peer) + NTQQMsgApi.activateChatAndGetHistory(peer).then(() => { + NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => { + let lastTempMsg = msgList.pop() + log('激活窗口之前的第一条临时会话消息:', lastTempMsg) + if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) { + OB11Constructor.message(lastTempMsg).then((r) => postOB11Event(r)) + } + }) + }) } else { NTQQMsgApi.activateChat(peer).then() } @@ -509,21 +509,20 @@ registerReceiveHook<{ }) registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => { - const peerUid = payload[0] as string; - log("激活的聊天窗口被删除,准备重新激活", peerUid); - let chatType = ChatType.friend; + const peerUid = payload[0] as string + log('激活的聊天窗口被删除,准备重新激活', peerUid) + let chatType = ChatType.friend if (isNumeric(peerUid)) { - chatType = ChatType.group; - } - else{ + chatType = ChatType.group + } else { // 检查是否好友 - if (!(await getFriend(peerUid))){ - chatType = ChatType.temp; + if (!(await getFriend(peerUid))) { + chatType = ChatType.temp } } - const peer = {peerUid, chatType} - await sleep(1000); + const peer = { peerUid, chatType } + await sleep(1000) NTQQMsgApi.activateChat(peer).then((r) => { - log("重新激活聊天窗口", peer, {result: r.result, errMsg: r.errMsg}) - }); -}) \ No newline at end of file + log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg }) + }) +}) diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index ebfa8b3..e65201b 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -1,215 +1,216 @@ -import {ipcMain} from "electron"; -import {hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook} from "./hook"; +import { ipcMain } from 'electron' +import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook } from './hook' -import {v4 as uuidv4} from "uuid" -import {log} from "../common/utils/log"; -import {NTQQWindow, NTQQWindowApi, NTQQWindows} from "./api/window"; -import {WebApi} from "./api/webapi"; -import {HOOK_LOG} from "../common/config"; +import { v4 as uuidv4 } from 'uuid' +import { log } from '../common/utils/log' +import { NTQQWindow, NTQQWindowApi, NTQQWindows } from './api/window' +import { WebApi } from './api/webapi' +import { HOOK_LOG } from '../common/config' export enum NTQQApiClass { - NT_API = "ns-ntApi", - FS_API = "ns-FsApi", - OS_API = "ns-OsApi", - WINDOW_API = "ns-WindowApi", - HOTUPDATE_API = "ns-HotUpdateApi", - BUSINESS_API = "ns-BusinessApi", - GLOBAL_DATA = "ns-GlobalDataApi", - SKEY_API = "ns-SkeyApi", - GROUP_HOME_WORK = "ns-GroupHomeWork", - GROUP_ESSENCE = "ns-GroupEssence", + NT_API = 'ns-ntApi', + FS_API = 'ns-FsApi', + OS_API = 'ns-OsApi', + WINDOW_API = 'ns-WindowApi', + HOTUPDATE_API = 'ns-HotUpdateApi', + BUSINESS_API = 'ns-BusinessApi', + GLOBAL_DATA = 'ns-GlobalDataApi', + SKEY_API = 'ns-SkeyApi', + GROUP_HOME_WORK = 'ns-GroupHomeWork', + GROUP_ESSENCE = 'ns-GroupEssence', } export enum NTQQApiMethod { - RECENT_CONTACT = "nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact", - ACTIVE_CHAT_PREVIEW = "nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat", // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息 - ACTIVE_CHAT_HISTORY = "nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat", // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息 - HISTORY_MSG = "nodeIKernelMsgService/getMsgsIncludeSelf", - GET_MULTI_MSG = "nodeIKernelMsgService/getMultiMsg", - DELETE_ACTIVE_CHAT = "nodeIKernelMsgService/deleteActiveChatByUid", + RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact', + ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息 + ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息 + HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf', + GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg', + DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid', - LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike", - SELF_INFO = "fetchAuthData", - FRIENDS = "nodeIKernelBuddyService/getBuddyList", - GROUPS = "nodeIKernelGroupService/getGroupList", - GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene", - GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList", - USER_INFO = "nodeIKernelProfileService/getUserSimpleInfo", - USER_DETAIL_INFO = "nodeIKernelProfileService/getUserDetailInfo", - USER_DETAIL_INFO_WITH_BIZ_INFO = "nodeIKernelProfileService/getUserDetailInfoWithBizInfo", - FILE_TYPE = "getFileType", - FILE_MD5 = "getFileMd5", - FILE_COPY = "copyFile", - IMAGE_SIZE = "getImageSizeFromPath", - FILE_SIZE = "getFileSize", - MEDIA_FILE_PATH = "nodeIKernelMsgService/getRichMediaFilePathForGuild", + LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike', + SELF_INFO = 'fetchAuthData', + FRIENDS = 'nodeIKernelBuddyService/getBuddyList', + GROUPS = 'nodeIKernelGroupService/getGroupList', + GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene', + GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList', + USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo', + USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo', + USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo', + FILE_TYPE = 'getFileType', + FILE_MD5 = 'getFileMd5', + FILE_COPY = 'copyFile', + IMAGE_SIZE = 'getImageSizeFromPath', + FILE_SIZE = 'getFileSize', + MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild', - RECALL_MSG = "nodeIKernelMsgService/recallMsg", - SEND_MSG = "nodeIKernelMsgService/sendMsg", - EMOJI_LIKE = "nodeIKernelMsgService/setMsgEmojiLikes", + RECALL_MSG = 'nodeIKernelMsgService/recallMsg', + SEND_MSG = 'nodeIKernelMsgService/sendMsg', + EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes', - DOWNLOAD_MEDIA = "nodeIKernelMsgService/downloadRichMedia", - FORWARD_MSG = "nodeIKernelMsgService/forwardMsgWithComment", - MULTI_FORWARD_MSG = "nodeIKernelMsgService/multiForwardMsgWithComment", // 合并转发 - GET_GROUP_NOTICE = "nodeIKernelGroupService/getSingleScreenNotifies", - HANDLE_GROUP_REQUEST = "nodeIKernelGroupService/operateSysNotify", - QUIT_GROUP = "nodeIKernelGroupService/quitGroup", - GROUP_AT_ALL_REMAIN_COUNT = "nodeIKernelGroupService/getGroupRemainAtTimes", - // READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange" - HANDLE_FRIEND_REQUEST = "nodeIKernelBuddyService/approvalFriendRequest", - KICK_MEMBER = "nodeIKernelGroupService/kickMember", - MUTE_MEMBER = "nodeIKernelGroupService/setMemberShutUp", - MUTE_GROUP = "nodeIKernelGroupService/setGroupShutUp", - SET_MEMBER_CARD = "nodeIKernelGroupService/modifyMemberCardName", - SET_MEMBER_ROLE = "nodeIKernelGroupService/modifyMemberRole", - PUBLISH_GROUP_BULLETIN = "nodeIKernelGroupService/publishGroupBulletinBulletin", - SET_GROUP_NAME = "nodeIKernelGroupService/modifyGroupName", - SET_GROUP_TITLE = "nodeIKernelGroupService/modifyMemberSpecialTitle", + DOWNLOAD_MEDIA = 'nodeIKernelMsgService/downloadRichMedia', + FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment', + MULTI_FORWARD_MSG = 'nodeIKernelMsgService/multiForwardMsgWithComment', // 合并转发 + GET_GROUP_NOTICE = 'nodeIKernelGroupService/getSingleScreenNotifies', + HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify', + QUIT_GROUP = 'nodeIKernelGroupService/quitGroup', + GROUP_AT_ALL_REMAIN_COUNT = 'nodeIKernelGroupService/getGroupRemainAtTimes', + // READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange" + HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest', + KICK_MEMBER = 'nodeIKernelGroupService/kickMember', + MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp', + MUTE_GROUP = 'nodeIKernelGroupService/setGroupShutUp', + SET_MEMBER_CARD = 'nodeIKernelGroupService/modifyMemberCardName', + SET_MEMBER_ROLE = 'nodeIKernelGroupService/modifyMemberRole', + PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin', + SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName', + SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle', - CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', - CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', - CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath', - CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath', - CACHE_PATH_SESSION = 'getCleanableAppSessionPathList', - CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache', - CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys', + CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', + CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', + CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath', + CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath', + CACHE_PATH_SESSION = 'getCleanableAppSessionPathList', + CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache', + CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys', - CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo', - CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo', - CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo', + CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo', + CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo', + CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo', - OPEN_EXTRA_WINDOW = 'openExternalWindow', + OPEN_EXTRA_WINDOW = 'openExternalWindow', - SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader', - GET_SKEY = "nodeIKernelTipOffService/getPskey", - UPDATE_SKEY = "updatePskey", + SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader', + GET_SKEY = 'nodeIKernelTipOffService/getPskey', + UPDATE_SKEY = 'updatePskey', - FETCH_UNITED_COMMEND_CONFIG = "nodeIKernelUnitedConfigService/fetchUnitedCommendConfig" // 发包需要调用的 + FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的 } enum NTQQApiChannel { - IPC_UP_2 = "IPC_UP_2", - IPC_UP_3 = "IPC_UP_3", - IPC_UP_1 = "IPC_UP_1", + IPC_UP_2 = 'IPC_UP_2', + IPC_UP_3 = 'IPC_UP_3', + IPC_UP_1 = 'IPC_UP_1', } interface NTQQApiParams { - methodName: NTQQApiMethod | string, - className?: NTQQApiClass, - channel?: NTQQApiChannel, - classNameIsRegister?: boolean - args?: unknown[], - cbCmd?: ReceiveCmd | null, - cmdCB?: (payload: any) => boolean; - afterFirstCmd?: boolean, // 是否在methodName调用完之后再去hook cbCmd - timeoutSecond?: number, + methodName: NTQQApiMethod | string + className?: NTQQApiClass + channel?: NTQQApiChannel + classNameIsRegister?: boolean + args?: unknown[] + cbCmd?: ReceiveCmd | null + cmdCB?: (payload: any) => boolean + afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd + timeoutSecond?: number } export function callNTQQApi(params: NTQQApiParams) { - let { - className, methodName, channel, args, - cbCmd, timeoutSecond: timeout, - classNameIsRegister, cmdCB, afterFirstCmd - } = params; - className = className ?? NTQQApiClass.NT_API; - channel = channel ?? NTQQApiChannel.IPC_UP_2; - args = args ?? []; - timeout = timeout ?? 5; - afterFirstCmd = afterFirstCmd ?? true; - const uuid = uuidv4(); - HOOK_LOG && log("callNTQQApi", channel, className, methodName, args, uuid) - return new Promise((resolve: (data: ReturnType) => void, reject) => { - // log("callNTQQApiPromise", channel, className, methodName, args, uuid) - const _timeout = timeout * 1000 - let success = false - let eventName = className + "-" + channel[channel.length - 1]; - if (classNameIsRegister) { - eventName += "-register"; - } - const apiArgs = [methodName, ...args] - if (!cbCmd) { - // QQ后端会返回结果,并且可以根据uuid识别 - hookApiCallbacks[uuid] = (r: ReturnType) => { - success = true - resolve(r) - }; + let { + className, + methodName, + channel, + args, + cbCmd, + timeoutSecond: timeout, + classNameIsRegister, + cmdCB, + afterFirstCmd, + } = params + className = className ?? NTQQApiClass.NT_API + channel = channel ?? NTQQApiChannel.IPC_UP_2 + args = args ?? [] + timeout = timeout ?? 5 + afterFirstCmd = afterFirstCmd ?? true + const uuid = uuidv4() + HOOK_LOG && log('callNTQQApi', channel, className, methodName, args, uuid) + return new Promise((resolve: (data: ReturnType) => void, reject) => { + // log("callNTQQApiPromise", channel, className, methodName, args, uuid) + const _timeout = timeout * 1000 + let success = false + let eventName = className + '-' + channel[channel.length - 1] + if (classNameIsRegister) { + eventName += '-register' + } + const apiArgs = [methodName, ...args] + if (!cbCmd) { + // QQ后端会返回结果,并且可以根据uuid识别 + hookApiCallbacks[uuid] = (r: ReturnType) => { + success = true + resolve(r) + } + } else { + // 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据 + const secondCallback = () => { + const hookId = registerReceiveHook(cbCmd, (payload) => { + // log(methodName, "second callback", cbCmd, payload, cmdCB); + if (!!cmdCB) { + if (cmdCB(payload)) { + removeReceiveHook(hookId) + success = true + resolve(payload) + } + } else { + removeReceiveHook(hookId) + success = true + resolve(payload) + } + }) + } + !afterFirstCmd && secondCallback() + hookApiCallbacks[uuid] = (result: GeneralCallResult) => { + log(`${methodName} callback`, result) + if (result?.result == 0 || result === undefined) { + afterFirstCmd && secondCallback() } else { - // 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据 - const secondCallback = () => { - const hookId = registerReceiveHook(cbCmd, (payload) => { - // log(methodName, "second callback", cbCmd, payload, cmdCB); - if (!!cmdCB) { - if (cmdCB(payload)) { - removeReceiveHook(hookId); - success = true - resolve(payload); - } - } else { - removeReceiveHook(hookId); - success = true - resolve(payload); - } - }) - } - !afterFirstCmd && secondCallback(); - hookApiCallbacks[uuid] = (result: GeneralCallResult) => { - log(`${methodName} callback`, result) - if (result?.result == 0 || result === undefined) { - afterFirstCmd && secondCallback(); - } else { - success = true - reject(`ntqq api call failed, ${result.errMsg}`); - } - } + success = true + reject(`ntqq api call failed, ${result.errMsg}`) } - setTimeout(() => { - // log("ntqq api timeout", success, channel, className, methodName) - if (!success) { - log(`ntqq api timeout ${channel}, ${eventName}, ${methodName}`, apiArgs); - reject(`ntqq api timeout ${channel}, ${eventName}, ${methodName}, ${apiArgs}`) - } - }, _timeout) + } + } + setTimeout(() => { + // log("ntqq api timeout", success, channel, className, methodName) + if (!success) { + log(`ntqq api timeout ${channel}, ${eventName}, ${methodName}`, apiArgs) + reject(`ntqq api timeout ${channel}, ${eventName}, ${methodName}, ${apiArgs}`) + } + }, _timeout) - ipcMain.emit( - channel, - { - sender: { - send: (..._args: unknown[]) => { - }, - }, - }, - {type: 'request', callbackId: uuid, eventName}, - apiArgs - ) - }) + ipcMain.emit( + channel, + { + sender: { + send: (..._args: unknown[]) => {}, + }, + }, + { type: 'request', callbackId: uuid, eventName }, + apiArgs, + ) + }) } - export interface GeneralCallResult { - result: number, // 0: success - errMsg: string + result: number // 0: success + errMsg: string } - export class NTQQApi { - static async call(className: NTQQApiClass, cmdName: string, args: any[],) { - return await callNTQQApi({ - className, - methodName: cmdName, - args: [ - ...args, - ] - }) - } + static async call(className: NTQQApiClass, cmdName: string, args: any[]) { + return await callNTQQApi({ + className, + methodName: cmdName, + args: [...args], + }) + } - static async fetchUnitedCommendConfig() { - return await callNTQQApi({ - methodName: NTQQApiMethod.FETCH_UNITED_COMMEND_CONFIG, - args:[ - { - groups: ['100243'] - } - ] - }) - } -} \ No newline at end of file + static async fetchUnitedCommendConfig() { + return await callNTQQApi({ + methodName: NTQQApiMethod.FETCH_UNITED_COMMEND_CONFIG, + args: [ + { + groups: ['100243'], + }, + ], + }) + } +} diff --git a/src/ntqqapi/types/cache.ts b/src/ntqqapi/types/cache.ts index e4407d7..8e910d9 100644 --- a/src/ntqqapi/types/cache.ts +++ b/src/ntqqapi/types/cache.ts @@ -1,65 +1,66 @@ -import {ChatType} from "./msg"; +import { ChatType } from './msg' export interface CacheScanResult { - result: number, - size: [ // 单位为字节 - string, // 系统总存储空间 - string, // 系统可用存储空间 - string, // 系统已用存储空间 - string, // QQ总大小 - string, // 「聊天与文件」大小 - string, // 未知 - string, // 「缓存数据」大小 - string, // 「其他数据」大小 - string, // 未知 - ] + result: number + size: [ + // 单位为字节 + string, // 系统总存储空间 + string, // 系统可用存储空间 + string, // 系统已用存储空间 + string, // QQ总大小 + string, // 「聊天与文件」大小 + string, // 未知 + string, // 「缓存数据」大小 + string, // 「其他数据」大小 + string, // 未知 + ] } export interface ChatCacheList { - pageCount: number, - infos: ChatCacheListItem[] + pageCount: number + infos: ChatCacheListItem[] } export interface ChatCacheListItem { - chatType: ChatType, - basicChatCacheInfo: ChatCacheListItemBasic, - guildChatCacheInfo: unknown[] // TODO: 没用过频道所以不知道这里边的详细内容 + chatType: ChatType + basicChatCacheInfo: ChatCacheListItemBasic + guildChatCacheInfo: unknown[] // TODO: 没用过频道所以不知道这里边的详细内容 } export interface ChatCacheListItemBasic { - chatSize: string, - chatTime: string, - uid: string, - uin: string, - remarkName: string, - nickName: string, - chatType?: ChatType, - isChecked?: boolean + chatSize: string + chatTime: string + uid: string + uin: string + remarkName: string + nickName: string + chatType?: ChatType + isChecked?: boolean } export enum CacheFileType { - IMAGE = 0, - VIDEO = 1, - AUDIO = 2, - DOCUMENT = 3, - OTHER = 4, + IMAGE = 0, + VIDEO = 1, + AUDIO = 2, + DOCUMENT = 3, + OTHER = 4, } export interface CacheFileList { - infos: CacheFileListItem[], + infos: CacheFileListItem[] } export interface CacheFileListItem { - fileSize: string, - fileTime: string, - fileKey: string, - elementId: string, - elementIdStr: string, - fileType: CacheFileType, - path: string, - fileName: string, - senderId: string, - previewPath: string, - senderName: string, - isChecked?: boolean, + fileSize: string + fileTime: string + fileKey: string + elementId: string + elementIdStr: string + fileType: CacheFileType + path: string + fileName: string + senderId: string + previewPath: string + senderName: string + isChecked?: boolean } diff --git a/src/ntqqapi/types/group.ts b/src/ntqqapi/types/group.ts index 1daffd1..fc17b72 100644 --- a/src/ntqqapi/types/group.ts +++ b/src/ntqqapi/types/group.ts @@ -1,56 +1,56 @@ -import {QQLevel, Sex} from "./user"; +import { QQLevel, Sex } from './user' export interface Group { - groupCode: string, - maxMember: number, - memberCount: number, - groupName: string, - groupStatus: 0, - memberRole: 2, - isTop: boolean, - toppedTimestamp: "0", - privilegeFlag: number, //65760 - isConf: boolean, - hasModifyConfGroupFace: boolean, - hasModifyConfGroupName: boolean, - remarkName: string, - hasMemo: boolean, - groupShutupExpireTime: string, //"0", - personShutupExpireTime: string, //"0", - discussToGroupUin: string, //"0", - discussToGroupMaxMsgSeq: number, - discussToGroupTime: number, - groupFlagExt: number, //1073938496, - authGroupType: number, //0, - groupCreditLevel: number, //0, - groupFlagExt3: number, //0, - groupOwnerId: { - "memberUin": string, //"0", - "memberUid": string, //"u_fbf8N7aeuZEnUiJAbQ9R8Q" - }, - members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段 + groupCode: string + maxMember: number + memberCount: number + groupName: string + groupStatus: 0 + memberRole: 2 + isTop: boolean + toppedTimestamp: '0' + privilegeFlag: number //65760 + isConf: boolean + hasModifyConfGroupFace: boolean + hasModifyConfGroupName: boolean + remarkName: string + hasMemo: boolean + groupShutupExpireTime: string //"0", + personShutupExpireTime: string //"0", + discussToGroupUin: string //"0", + discussToGroupMaxMsgSeq: number + discussToGroupTime: number + groupFlagExt: number //1073938496, + authGroupType: number //0, + groupCreditLevel: number //0, + groupFlagExt3: number //0, + groupOwnerId: { + memberUin: string //"0", + memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q" + } + members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段 } export enum GroupMemberRole { - normal = 2, - admin = 3, - owner = 4 + normal = 2, + admin = 3, + owner = 4, } export interface GroupMember { - memberSpecialTitle: string; - avatarPath: string; - cardName: string; - cardType: number; - isDelete: boolean; - nick: string; - qid: string; - remark: string; - role: GroupMemberRole; // 群主:4, 管理员:3,群员:2 - shutUpTime: number; // 禁言时间,单位是什么暂时不清楚 - uid: string; // 加密的字符串 - uin: string; // QQ号 - isRobot: boolean; - sex?: Sex - qqLevel?: QQLevel -} \ No newline at end of file + memberSpecialTitle: string + avatarPath: string + cardName: string + cardType: number + isDelete: boolean + nick: string + qid: string + remark: string + role: GroupMemberRole // 群主:4, 管理员:3,群员:2 + shutUpTime: number // 禁言时间,单位是什么暂时不清楚 + uid: string // 加密的字符串 + uin: string // QQ号 + isRobot: boolean + sex?: Sex + qqLevel?: QQLevel +} diff --git a/src/ntqqapi/types/index.ts b/src/ntqqapi/types/index.ts index dc69d2a..189fb51 100644 --- a/src/ntqqapi/types/index.ts +++ b/src/ntqqapi/types/index.ts @@ -1,7 +1,5 @@ - -export * from './user'; -export * from './group'; -export * from './msg'; -export * from './notify'; -export * from './cache'; - +export * from './user' +export * from './group' +export * from './msg' +export * from './notify' +export * from './cache' diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index fd30bc2..15582a9 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -1,404 +1,414 @@ -import {GroupMemberRole} from "./group"; -import exp from "constants"; +import { GroupMemberRole } from './group' +import exp from 'constants' export enum ElementType { - TEXT = 1, - PIC = 2, - FILE = 3, - PTT = 4, - VIDEO = 5, - FACE = 6, - REPLY = 7, - ARK = 10, - MFACE = 11, + TEXT = 1, + PIC = 2, + FILE = 3, + PTT = 4, + VIDEO = 5, + FACE = 6, + REPLY = 7, + ARK = 10, + MFACE = 11, } export interface SendTextElement { - elementType: ElementType.TEXT, - elementId: "", - textElement: { - content: string, - atType: number, - atUid: string, - atTinyId: string, - atNtUid: string, - } + elementType: ElementType.TEXT + elementId: '' + textElement: { + content: string + atType: number + atUid: string + atTinyId: string + atNtUid: string + } } export interface SendPttElement { - elementType: ElementType.PTT, - elementId: "", - pttElement: { - fileName: string, - filePath: string, - md5HexStr: string, - fileSize: number, - duration: number, // 单位是秒 - formatType: number, - voiceType: number, - voiceChangeType: number, - canConvert2Text: boolean, - waveAmplitudes: number[], - fileSubId: "", - playState: number, - autoConvertText: number, - } + elementType: ElementType.PTT + elementId: '' + pttElement: { + fileName: string + filePath: string + md5HexStr: string + fileSize: number + duration: number // 单位是秒 + formatType: number + voiceType: number + voiceChangeType: number + canConvert2Text: boolean + waveAmplitudes: number[] + fileSubId: '' + playState: number + autoConvertText: number + } } export enum PicType { - gif = 2000, - jpg = 1000 + gif = 2000, + jpg = 1000, } export enum PicSubType { - normal = 0, // 普通图片,大图 - face = 1 // 表情包小图 + normal = 0, // 普通图片,大图 + face = 1, // 表情包小图 } export interface SendPicElement { - elementType: ElementType.PIC, - elementId: "", - picElement: { - md5HexStr: string, - fileSize: number | string, - picWidth: number, - picHeight: number, - fileName: string, - sourcePath: string, - original: boolean, - picType: PicType, - picSubType: PicSubType, - fileUuid: string, - fileSubId: string, - thumbFileSize: number, - summary: string, - }, + elementType: ElementType.PIC + elementId: '' + picElement: { + md5HexStr: string + fileSize: number | string + picWidth: number + picHeight: number + fileName: string + sourcePath: string + original: boolean + picType: PicType + picSubType: PicSubType + fileUuid: string + fileSubId: string + thumbFileSize: number + summary: string + } } export interface SendReplyElement { - elementType: ElementType.REPLY, - elementId: "", - replyElement: { - replayMsgSeq: string, - replayMsgId: string, - senderUin: string, - senderUinStr: string, - } + elementType: ElementType.REPLY + elementId: '' + replyElement: { + replayMsgSeq: string + replayMsgId: string + senderUin: string + senderUinStr: string + } } export interface SendFaceElement { - elementType: ElementType.FACE, - elementId: "", - faceElement: FaceElement + elementType: ElementType.FACE + elementId: '' + faceElement: FaceElement } export interface SendMarketFaceElement { - elementType: ElementType.MFACE, - marketFaceElement: MarketFaceElement + elementType: ElementType.MFACE + marketFaceElement: MarketFaceElement } export interface FileElement { - "fileMd5"?: "", - "fileName": string, - "filePath": string, - "fileSize": string, - "picHeight"?: number, - "picWidth"?: number, - "picThumbPath"?: {}, - "file10MMd5"?: "", - "fileSha"?: "", - "fileSha3"?: "", - "fileUuid"?: "", - "fileSubId"?: "", - "thumbFileSize"?: number, - fileBizId?: number + fileMd5?: '' + fileName: string + filePath: string + fileSize: string + picHeight?: number + picWidth?: number + picThumbPath?: {} + file10MMd5?: '' + fileSha?: '' + fileSha3?: '' + fileUuid?: '' + fileSubId?: '' + thumbFileSize?: number + fileBizId?: number } export interface SendFileElement { - elementType: ElementType.FILE - elementId: "", - fileElement: FileElement + elementType: ElementType.FILE + elementId: '' + fileElement: FileElement } export interface SendVideoElement { - elementType: ElementType.VIDEO - elementId: "", - videoElement: VideoElement + elementType: ElementType.VIDEO + elementId: '' + videoElement: VideoElement } export interface SendArkElement { - elementType: ElementType.ARK, - elementId: "", - arkElement: ArkElement - + elementType: ElementType.ARK + elementId: '' + arkElement: ArkElement } -export type SendMessageElement = SendTextElement | SendPttElement | - SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement | SendVideoElement | SendArkElement +export type SendMessageElement = + | SendTextElement + | SendPttElement + | SendPicElement + | SendReplyElement + | SendFaceElement + | SendMarketFaceElement + | SendFileElement + | SendVideoElement + | SendArkElement export enum AtType { - notAt = 0, - atAll = 1, - atUser = 2 + notAt = 0, + atAll = 1, + atUser = 2, } export enum ChatType { - friend = 1, - group = 2, - temp = 100 + friend = 1, + group = 2, + temp = 100, } export interface PttElement { - canConvert2Text: boolean; - duration: number; // 秒数 - fileBizId: null; - fileId: number; // 0 - fileName: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr" - filePath: string; // "/Users//Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr" - fileSize: string; // "4261" - fileSubId: string; // "0" - fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" - formatType: string; // 1 - invalidState: number; // 0 - md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6" - playState: number; // 0 - progress: number; // 0 - text: string; // "" - transferStatus: number; // 0 - translateStatus: number; // 0 - voiceChangeType: number; // 0 - voiceType: number; // 0 - waveAmplitudes: number[]; + canConvert2Text: boolean + duration: number // 秒数 + fileBizId: null + fileId: number // 0 + fileName: string // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr" + filePath: string // "/Users//Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr" + fileSize: string // "4261" + fileSubId: string // "0" + fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" + formatType: string // 1 + invalidState: number // 0 + md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6" + playState: number // 0 + progress: number // 0 + text: string // "" + transferStatus: number // 0 + translateStatus: number // 0 + voiceChangeType: number // 0 + voiceType: number // 0 + waveAmplitudes: number[] } export interface ArkElement { - bytesData: string; - linkInfo: null, - subElementType: null + bytesData: string + linkInfo: null + subElementType: null } -export const IMAGE_HTTP_HOST = "https://gchat.qpic.cn" -export const IMAGE_HTTP_HOST_NT = "https://multimedia.nt.qq.com.cn" +export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn' +export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn' export interface PicElement { - originImageUrl: string; // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn - originImageMd5?: string; - sourcePath: string; // 图片本地路径 - thumbPath: Map; - picWidth: number; - picHeight: number; - fileSize: number; - fileName: string; - fileUuid: string; - md5HexStr?: string; + originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn + originImageMd5?: string + sourcePath: string // 图片本地路径 + thumbPath: Map + picWidth: number + picHeight: number + fileSize: number + fileName: string + fileUuid: string + md5HexStr?: string } export enum GrayTipElementSubType { - INVITE_NEW_MEMBER = 12, - MEMBER_NEW_TITLE = 17 + INVITE_NEW_MEMBER = 12, + MEMBER_NEW_TITLE = 17, } export interface GrayTipElement { - subElementType: GrayTipElementSubType; - revokeElement: { - operatorRole: string; - operatorUid: string; - operatorNick: string; - operatorRemark: string; - operatorMemRemark?: string; - wording: string; // 自定义的撤回提示语 - } - aioOpGrayTipElement: TipAioOpGrayTipElement, - groupElement: TipGroupElement, - xmlElement: { - templId: string; - content: string; - }, - jsonGrayTipElement: { - jsonStr: string; - } + subElementType: GrayTipElementSubType + revokeElement: { + operatorRole: string + operatorUid: string + operatorNick: string + operatorRemark: string + operatorMemRemark?: string + wording: string // 自定义的撤回提示语 + } + aioOpGrayTipElement: TipAioOpGrayTipElement + groupElement: TipGroupElement + xmlElement: { + templId: string + content: string + } + jsonGrayTipElement: { + jsonStr: string + } } export enum FaceType { - normal=1, // 小黄脸 - normal2=2, // 新小黄脸, 从faceIndex 222开始? - dice=3 // 骰子 + normal = 1, // 小黄脸 + normal2 = 2, // 新小黄脸, 从faceIndex 222开始? + dice = 3, // 骰子 } export enum FaceIndex { - dice = 358, - RPS = 359 // 石头剪刀布 + dice = 358, + RPS = 359, // 石头剪刀布 } export interface FaceElement { - faceIndex: number, - faceType: FaceType, - faceText?: string, - packId?: string, - stickerId?: string, - sourceType?: number, - stickerType?: number, - resultId?: string, - surpriseId?: string, - randomType?: number + faceIndex: number + faceType: FaceType + faceText?: string + packId?: string + stickerId?: string + sourceType?: number + stickerType?: number + resultId?: string + surpriseId?: string + randomType?: number } export interface MarketFaceElement { - "emojiPackageId": number, - "emojiId": string, - "key": string, + emojiPackageId: number + emojiId: string + key: string } export interface VideoElement { - "filePath": string, - "fileName": string, - "videoMd5"?: string, - "thumbMd5"?: string - "fileTime"?: number, // second - "thumbSize"?: number, // byte - "fileFormat"?: number, // 2表示mp4? - "fileSize"?: string, // byte - "thumbWidth"?: number, - "thumbHeight"?: number, - "busiType"?: 0, // 未知 - "subBusiType"?: 0, // 未知 - "thumbPath"?: Map, - "transferStatus"?: 0, // 未知 - "progress"?: 0, // 下载进度? - "invalidState"?: 0, // 未知 - "fileUuid"?: string, // 可以用于下载链接? - "fileSubId"?: "", - "fileBizId"?: null, - "originVideoMd5"?: "", - "import_rich_media_context"?: null, - "sourceVideoCodecFormat"?: number + filePath: string + fileName: string + videoMd5?: string + thumbMd5?: string + fileTime?: number // second + thumbSize?: number // byte + fileFormat?: number // 2表示mp4? + fileSize?: string // byte + thumbWidth?: number + thumbHeight?: number + busiType?: 0 // 未知 + subBusiType?: 0 // 未知 + thumbPath?: Map + transferStatus?: 0 // 未知 + progress?: 0 // 下载进度? + invalidState?: 0 // 未知 + fileUuid?: string // 可以用于下载链接? + fileSubId?: '' + fileBizId?: null + originVideoMd5?: '' + import_rich_media_context?: null + sourceVideoCodecFormat?: number } export interface MarkdownElement { - content: string, + content: string } -export interface InlineKeyboardElementRowButton{ - "id": "", - "label": string, - "visitedLabel": string, - "style": 1, // 未知 - "type": 2, // 未知 - "clickLimit": 0, // 未知 - "unsupportTips": "请升级新版手机QQ", - "data": string, - "atBotShowChannelList": false, - "permissionType": 2, - "specifyRoleIds": [], - "specifyTinyids": [], - "isReply": false, - "anchor": 0, - "enter": false, - "subscribeDataTemplateIds": [] +export interface InlineKeyboardElementRowButton { + id: '' + label: string + visitedLabel: string + style: 1 // 未知 + type: 2 // 未知 + clickLimit: 0 // 未知 + unsupportTips: '请升级新版手机QQ' + data: string + atBotShowChannelList: false + permissionType: 2 + specifyRoleIds: [] + specifyTinyids: [] + isReply: false + anchor: 0 + enter: false + subscribeDataTemplateIds: [] } export interface InlineKeyboardElement { - rows: [{ - buttons: InlineKeyboardElementRowButton[] - }] + rows: [ + { + buttons: InlineKeyboardElementRowButton[] + }, + ] } -export interface TipAioOpGrayTipElement { // 这是什么提示来着? - operateType: number, - peerUid: string, - fromGrpCodeOfTmpChat: string, +export interface TipAioOpGrayTipElement { + // 这是什么提示来着? + operateType: number + peerUid: string + fromGrpCodeOfTmpChat: string } export enum TipGroupElementType { - memberIncrease = 1, - kicked = 3, // 被移出群 - ban = 8 + memberIncrease = 1, + kicked = 3, // 被移出群 + ban = 8, } export interface TipGroupElement { - "type": TipGroupElementType, // 1是表示有人加入群, 自己加入群也会收到这个 - "role": 0, // 暂时不知 - "groupName": string, // 暂时获取不到 - "memberUid": string, - "memberNick": string, - "memberRemark": string, - "adminUid": string, - "adminNick": string, - "adminRemark": string, - "createGroup": null, - "memberAdd"?: { - "showType": 1, - "otherAdd": null, - "otherAddByOtherQRCode": null, - "otherAddByYourQRCode": null, - "youAddByOtherQRCode": null, - "otherInviteOther": null, - "otherInviteYou": null, - "youInviteOther": null - }, - "shutUp"?: { - "curTime": string, - "duration": string, // 禁言时间,秒 - "admin": { - "uid": string, - "card": string, - "name": string, - "role": GroupMemberRole - }, - "member": { - "uid": string - "card": string, - "name": string, - "role": GroupMemberRole - } + type: TipGroupElementType // 1是表示有人加入群, 自己加入群也会收到这个 + role: 0 // 暂时不知 + groupName: string // 暂时获取不到 + memberUid: string + memberNick: string + memberRemark: string + adminUid: string + adminNick: string + adminRemark: string + createGroup: null + memberAdd?: { + showType: 1 + otherAdd: null + otherAddByOtherQRCode: null + otherAddByYourQRCode: null + youAddByOtherQRCode: null + otherInviteOther: null + otherInviteYou: null + youInviteOther: null + } + shutUp?: { + curTime: string + duration: string // 禁言时间,秒 + admin: { + uid: string + card: string + name: string + role: GroupMemberRole } + member: { + uid: string + card: string + name: string + role: GroupMemberRole + } + } } -export interface MultiForwardMsgElement{ - xmlContent: string, // xml格式的消息内容 - resId: string, - fileName: string, +export interface MultiForwardMsgElement { + xmlContent: string // xml格式的消息内容 + resId: string + fileName: string } export interface RawMessage { - msgId: string; - msgShortId?: number; // 自己维护的消息id - msgTime: string; // 时间戳,秒 - msgSeq: string; - senderUid: string; - senderUin?: string; // 发送者QQ号 - peerUid: string; // 群号 或者 QQ uid - peerUin: string; // 群号 或者 发送者QQ号 - sendNickName: string; - sendMemberName?: string; // 发送者群名片 - chatType: ChatType; - sendStatus?: number; // 消息状态,别人发的2是已撤回,自己发的2是已发送 - recallTime: string; // 撤回时间, "0"是没有撤回 - elements: { - elementId: string, - elementType: ElementType; - replyElement: { - senderUid: string; // 原消息发送者QQ号 - sourceMsgIsIncPic: boolean; // 原消息是否有图片 - sourceMsgText: string; - replayMsgSeq: string; // 源消息的msgSeq,可以通过这个找到源消息的msgId - }; - textElement: { - atType: AtType; - atUid: string; // QQ号 - content: string; - atNtUid: string; // uid号 - }; - picElement: PicElement; - pttElement: PttElement; - arkElement: ArkElement; - grayTipElement: GrayTipElement; - faceElement: FaceElement; - mfaceElement: MarketFaceElement; - videoElement: VideoElement; - fileElement: FileElement; - marketFaceElement: MarketFaceElement; - inlineKeyboardElement: InlineKeyboardElement; - markdownElement: MarkdownElement; - multiForwardMsgElement: MultiForwardMsgElement; - }[]; -} \ No newline at end of file + msgId: string + msgShortId?: number // 自己维护的消息id + msgTime: string // 时间戳,秒 + msgSeq: string + senderUid: string + senderUin?: string // 发送者QQ号 + peerUid: string // 群号 或者 QQ uid + peerUin: string // 群号 或者 发送者QQ号 + sendNickName: string + sendMemberName?: string // 发送者群名片 + chatType: ChatType + sendStatus?: number // 消息状态,别人发的2是已撤回,自己发的2是已发送 + recallTime: string // 撤回时间, "0"是没有撤回 + elements: { + elementId: string + elementType: ElementType + replyElement: { + senderUid: string // 原消息发送者QQ号 + sourceMsgIsIncPic: boolean // 原消息是否有图片 + sourceMsgText: string + replayMsgSeq: string // 源消息的msgSeq,可以通过这个找到源消息的msgId + } + textElement: { + atType: AtType + atUid: string // QQ号 + content: string + atNtUid: string // uid号 + } + picElement: PicElement + pttElement: PttElement + arkElement: ArkElement + grayTipElement: GrayTipElement + faceElement: FaceElement + mfaceElement: MarketFaceElement + videoElement: VideoElement + fileElement: FileElement + marketFaceElement: MarketFaceElement + inlineKeyboardElement: InlineKeyboardElement + markdownElement: MarkdownElement + multiForwardMsgElement: MultiForwardMsgElement + }[] +} diff --git a/src/ntqqapi/types/notify.ts b/src/ntqqapi/types/notify.ts index 93eae37..2da2f68 100644 --- a/src/ntqqapi/types/notify.ts +++ b/src/ntqqapi/types/notify.ts @@ -1,65 +1,64 @@ - export enum GroupNotifyTypes { - INVITE_ME = 1, - INVITED_JOIN = 4, // 有人接受了邀请入群 - JOIN_REQUEST = 7, - ADMIN_SET = 8, - KICK_MEMBER = 9, - MEMBER_EXIT = 11, // 主动退出 - ADMIN_UNSET = 12, - + INVITE_ME = 1, + INVITED_JOIN = 4, // 有人接受了邀请入群 + JOIN_REQUEST = 7, + ADMIN_SET = 8, + KICK_MEMBER = 9, + MEMBER_EXIT = 11, // 主动退出 + ADMIN_UNSET = 12, } export interface GroupNotifies { - doubt: boolean, - nextStartSeq: string, - notifies: GroupNotify[], + doubt: boolean + nextStartSeq: string + notifies: GroupNotify[] } export enum GroupNotifyStatus { - IGNORE = 0, - WAIT_HANDLE = 1, - APPROVE = 2, - REJECT = 3 + IGNORE = 0, + WAIT_HANDLE = 1, + APPROVE = 2, + REJECT = 3, } export interface GroupNotify { - time: number; // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify - seq: string, // 唯一标识符,转成数字再除以1000应该就是时间戳? - type: GroupNotifyTypes, - status: GroupNotifyStatus, // 0是已忽略?,1是未处理,2是已同意 - group: { groupCode: string, groupName: string }, - user1: { uid: string, nickName: string }, // 被设置管理员的人 - user2: { uid: string, nickName: string }, // 操作者 - actionUser: { uid: string, nickName: string }, //未知 - actionTime: string, - invitationExt: { - srcType: number, // 0?未知 - groupCode: string, waitStatus: number - }, - postscript: string, // 加群用户填写的验证信息 - repeatSeqs: [], - warningTips: string + time: number // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify + seq: string // 唯一标识符,转成数字再除以1000应该就是时间戳? + type: GroupNotifyTypes + status: GroupNotifyStatus // 0是已忽略?,1是未处理,2是已同意 + group: { groupCode: string; groupName: string } + user1: { uid: string; nickName: string } // 被设置管理员的人 + user2: { uid: string; nickName: string } // 操作者 + actionUser: { uid: string; nickName: string } //未知 + actionTime: string + invitationExt: { + srcType: number // 0?未知 + groupCode: string + waitStatus: number + } + postscript: string // 加群用户填写的验证信息 + repeatSeqs: [] + warningTips: string } export enum GroupRequestOperateTypes { - approve = 1, - reject = 2 + approve = 1, + reject = 2, } export interface FriendRequest { - friendUid: string, - reqTime: string, // 时间戳,秒 - extWords: string, // 申请人填写的验证消息 - isUnread: boolean, - friendNick: string, - sourceId: number, - groupCode: string + friendUid: string + reqTime: string // 时间戳,秒 + extWords: string // 申请人填写的验证消息 + isUnread: boolean + friendNick: string + sourceId: number + groupCode: string } export interface FriendRequestNotify { - data: { - unreadNums: number, - buddyReqs: FriendRequest[] - } + data: { + unreadNums: number + buddyReqs: FriendRequest[] + } } diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts index d316619..ef07164 100644 --- a/src/ntqqapi/types/user.ts +++ b/src/ntqqapi/types/user.ts @@ -1,75 +1,75 @@ export enum Sex { - male = 0, - female = 2, - unknown = 255, + male = 0, + female = 2, + unknown = 255, } export interface QQLevel { - "crownNum": number, - "sunNum": number, - "moonNum": number, - "starNum": number + crownNum: number + sunNum: number + moonNum: number + starNum: number } export interface User { - uid: string; // 加密的字符串 - uin: string; // QQ号 - nick: string; - avatarUrl?: string; - longNick?: string; // 签名 - remark?: string; - sex?: Sex; - qqLevel?: QQLevel, - qid?: string - "birthday_year"?: number, - "birthday_month"?: number, - "birthday_day"?: number, - "topTime"?: string, - "constellation"?: number, - "shengXiao"?: number, - "kBloodType"?: number, - "homeTown"?: string, //"0-0-0", - "makeFriendCareer"?: number, - "pos"?: string, - "eMail"?: string - "phoneNum"?: string, - "college"?: string, - "country"?: string, - "province"?: string, - "city"?: string, - "postCode"?: string, - "address"?: string, - "isBlock"?: boolean, - "isSpecialCareOpen"?: boolean, - "isSpecialCareZone"?: boolean, - "ringId"?: string, - "regTime"?: number, - interest?: string, - "labels"?: string[], - "isHideQQLevel"?: number, - "privilegeIcon"?: { - "jumpUrl": string, - "openIconList": unknown[], - "closeIconList": unknown[] - }, - "photoWall"?: { - "picList": unknown[] - }, - "vipFlag"?: boolean, - "yearVipFlag"?: boolean, - "svipFlag"?: boolean, - "vipLevel"?: number, - "status"?: number, - "qidianMasterFlag"?: number, - "qidianCrewFlag"?: number, - "qidianCrewFlag2"?: number, - "extStatus"?: number, - "recommendImgFlag"?: number, - "disableEmojiShortCuts"?: number, - "pendantId"?: string, + uid: string // 加密的字符串 + uin: string // QQ号 + nick: string + avatarUrl?: string + longNick?: string // 签名 + remark?: string + sex?: Sex + qqLevel?: QQLevel + qid?: string + birthday_year?: number + birthday_month?: number + birthday_day?: number + topTime?: string + constellation?: number + shengXiao?: number + kBloodType?: number + homeTown?: string //"0-0-0", + makeFriendCareer?: number + pos?: string + eMail?: string + phoneNum?: string + college?: string + country?: string + province?: string + city?: string + postCode?: string + address?: string + isBlock?: boolean + isSpecialCareOpen?: boolean + isSpecialCareZone?: boolean + ringId?: string + regTime?: number + interest?: string + labels?: string[] + isHideQQLevel?: number + privilegeIcon?: { + jumpUrl: string + openIconList: unknown[] + closeIconList: unknown[] + } + photoWall?: { + picList: unknown[] + } + vipFlag?: boolean + yearVipFlag?: boolean + svipFlag?: boolean + vipLevel?: number + status?: number + qidianMasterFlag?: number + qidianCrewFlag?: number + qidianCrewFlag2?: number + extStatus?: number + recommendImgFlag?: number + disableEmojiShortCuts?: number + pendantId?: string } export interface SelfInfo extends User { - online?: boolean; + online?: boolean } -export interface Friend extends User {} \ No newline at end of file +export interface Friend extends User {} diff --git a/src/onebot11/action/BaseAction.ts b/src/onebot11/action/BaseAction.ts index 46af0b5..2290693 100644 --- a/src/onebot11/action/BaseAction.ts +++ b/src/onebot11/action/BaseAction.ts @@ -1,49 +1,49 @@ -import {ActionName, BaseCheckResult} from "./types" -import {OB11Response} from "./OB11Response" -import {OB11Return} from "../types"; +import { ActionName, BaseCheckResult } from './types' +import { OB11Response } from './OB11Response' +import { OB11Return } from '../types' -import {log} from "../../common/utils/log"; +import { log } from '../../common/utils/log' class BaseAction { - actionName: ActionName + actionName: ActionName - protected async check(payload: PayloadType): Promise { - return { - valid: true, - } + protected async check(payload: PayloadType): Promise { + return { + valid: true, } + } - public async handle(payload: PayloadType): Promise> { - const result = await this.check(payload); - if (!result.valid) { - return OB11Response.error(result.message, 400); - } - try { - const resData = await this._handle(payload); - return OB11Response.ok(resData); - } catch (e) { - log("发生错误", e) - return OB11Response.error(e?.toString() || e?.stack?.toString() || "未知错误,可能操作超时", 200); - } + public async handle(payload: PayloadType): Promise> { + const result = await this.check(payload) + if (!result.valid) { + return OB11Response.error(result.message, 400) } + try { + const resData = await this._handle(payload) + return OB11Response.ok(resData) + } catch (e) { + log('发生错误', e) + return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200) + } + } - public async websocketHandle(payload: PayloadType, echo: any): Promise> { - const result = await this.check(payload) - if (!result.valid) { - return OB11Response.error(result.message, 1400) - } - try { - const resData = await this._handle(payload) - return OB11Response.ok(resData, echo); - } catch (e) { - log("发生错误", e) - return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo) - } + public async websocketHandle(payload: PayloadType, echo: any): Promise> { + const result = await this.check(payload) + if (!result.valid) { + return OB11Response.error(result.message, 1400) } + try { + const resData = await this._handle(payload) + return OB11Response.ok(resData, echo) + } catch (e) { + log('发生错误', e) + return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo) + } + } - protected async _handle(payload: PayloadType): Promise { - throw `pleas override ${this.actionName} _handle`; - } + protected async _handle(payload: PayloadType): Promise { + throw `pleas override ${this.actionName} _handle` + } } -export default BaseAction \ No newline at end of file +export default BaseAction diff --git a/src/onebot11/action/OB11Response.ts b/src/onebot11/action/OB11Response.ts index 0611829..25a394e 100644 --- a/src/onebot11/action/OB11Response.ts +++ b/src/onebot11/action/OB11Response.ts @@ -1,32 +1,32 @@ -import {OB11Return} from '../types'; +import { OB11Return } from '../types' -import {isNull} from "../../common/utils/helper"; +import { isNull } from '../../common/utils/helper' export class OB11Response { - static res(data: T, status: string, retcode: number, message: string = ""): OB11Return { - return { - status: status, - retcode: retcode, - data: data, - message: message, - wording: message, - echo: null - } + static res(data: T, status: string, retcode: number, message: string = ''): OB11Return { + return { + status: status, + retcode: retcode, + data: data, + message: message, + wording: message, + echo: null, } + } - static ok(data: T, echo: any = null) { - let res = OB11Response.res(data, "ok", 0) - if (!isNull(echo)) { - res.echo = echo; - } - return res; + static ok(data: T, echo: any = null) { + let res = OB11Response.res(data, 'ok', 0) + if (!isNull(echo)) { + res.echo = echo } + return res + } - static error(err: string, retcode: number, echo: any = null) { - let res = OB11Response.res(null, "failed", retcode, err) - if (!isNull(echo)) { - res.echo = echo; - } - return res; + static error(err: string, retcode: number, echo: any = null) { + let res = OB11Response.res(null, 'failed', retcode, err) + if (!isNull(echo)) { + res.echo = echo } + return res + } } diff --git a/src/onebot11/action/file/GetFile.ts b/src/onebot11/action/file/GetFile.ts index fc0ced2..28efa2d 100644 --- a/src/onebot11/action/file/GetFile.ts +++ b/src/onebot11/action/file/GetFile.ts @@ -1,114 +1,110 @@ -import BaseAction from "../BaseAction"; -import fs from "fs/promises"; -import {dbUtil} from "../../../common/db"; -import {getConfigUtil} from "../../../common/config"; -import {log, sleep, uri2local} from "../../../common/utils"; -import {NTQQFileApi} from "../../../ntqqapi/api/file"; -import {ActionName} from "../types"; -import {FileElement, RawMessage, VideoElement} from "../../../ntqqapi/types"; -import {FileCache} from "../../../common/types"; +import BaseAction from '../BaseAction' +import fs from 'fs/promises' +import { dbUtil } from '../../../common/db' +import { getConfigUtil } from '../../../common/config' +import { log, sleep, uri2local } from '../../../common/utils' +import { NTQQFileApi } from '../../../ntqqapi/api/file' +import { ActionName } from '../types' +import { FileElement, RawMessage, VideoElement } from '../../../ntqqapi/types' +import { FileCache } from '../../../common/types' export interface GetFilePayload { - file: string // 文件名或者fileUuid + file: string // 文件名或者fileUuid } export interface GetFileResponse { - file?: string // path - url?: string - file_size?: string - file_name?: string - base64?: string + file?: string // path + url?: string + file_size?: string + file_name?: string + base64?: string } - export class GetFileBase extends BaseAction { - private getElement(msg: RawMessage): {id: string, element: VideoElement | FileElement}{ - let element = msg.elements.find(e=>e.fileElement) - if (!element){ - element = msg.elements.find(e=>e.videoElement) - return {id: element.elementId, element: element.videoElement} - } - return {id: element.elementId, element: element.fileElement} + private getElement(msg: RawMessage): { id: string; element: VideoElement | FileElement } { + let element = msg.elements.find((e) => e.fileElement) + if (!element) { + element = msg.elements.find((e) => e.videoElement) + return { id: element.elementId, element: element.videoElement } } - private async download(cache: FileCache, file: string){ - log("需要调用 NTQQ 下载文件api") - if (cache.msgId) { - let msg = await dbUtil.getMsgByLongId(cache.msgId) - if (msg){ - log("找到了文件 msg", msg) - let element = this.getElement(msg); - log("找到了文件 element", element); - // 构建下载函数 - await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, - element.id, "", "", true) - await sleep(1000); - msg = await dbUtil.getMsgByLongId(cache.msgId) - log("下载完成后的msg", msg) - cache.filePath = this.getElement(msg).element.filePath - dbUtil.addFileCache(file, cache).then() - } - } + return { id: element.elementId, element: element.fileElement } + } + private async download(cache: FileCache, file: string) { + log('需要调用 NTQQ 下载文件api') + if (cache.msgId) { + let msg = await dbUtil.getMsgByLongId(cache.msgId) + if (msg) { + log('找到了文件 msg', msg) + let element = this.getElement(msg) + log('找到了文件 element', element) + // 构建下载函数 + await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, element.id, '', '', true) + await sleep(1000) + msg = await dbUtil.getMsgByLongId(cache.msgId) + log('下载完成后的msg', msg) + cache.filePath = this.getElement(msg).element.filePath + dbUtil.addFileCache(file, cache).then() + } } - protected async _handle(payload: GetFilePayload): Promise { - const cache = await dbUtil.getFileCache(payload.file) - const {autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond} = getConfigUtil().getConfig() - if (!cache) { - throw new Error('file not found') - } - if (cache.downloadFunc) { - await cache.downloadFunc() + } + protected async _handle(payload: GetFilePayload): Promise { + const cache = await dbUtil.getFileCache(payload.file) + const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig() + if (!cache) { + throw new Error('file not found') + } + if (cache.downloadFunc) { + await cache.downloadFunc() + } + try { + await fs.access(cache.filePath, fs.constants.F_OK) + } catch (e) { + // log("file not found", e) + if (cache.url) { + const downloadResult = await uri2local(cache.url) + if (downloadResult.success) { + cache.filePath = downloadResult.path + dbUtil.addFileCache(payload.file, cache).then() + } else { + await this.download(cache, payload.file) } + } else { + // 没有url的可能是私聊文件或者群文件,需要自己下载 + await this.download(cache, payload.file) + } + } + let res: GetFileResponse = { + file: cache.filePath, + url: cache.url, + file_size: cache.fileSize, + file_name: cache.fileName, + } + if (enableLocalFile2Url) { + if (!cache.url) { try { - await fs.access(cache.filePath, fs.constants.F_OK) + res.base64 = await fs.readFile(cache.filePath, 'base64') } catch (e) { - // log("file not found", e) - if (cache.url){ - const downloadResult = await uri2local(cache.url) - if (downloadResult.success) { - cache.filePath = downloadResult.path - dbUtil.addFileCache(payload.file, cache).then() - } else { - await this.download(cache, payload.file) - } - } - else{ - // 没有url的可能是私聊文件或者群文件,需要自己下载 - await this.download(cache, payload.file) - } - + throw new Error('文件下载失败. ' + e) } - let res: GetFileResponse = { - file: cache.filePath, - url: cache.url, - file_size: cache.fileSize, - file_name: cache.fileName - } - if (enableLocalFile2Url) { - if (!cache.url) { - try{ - res.base64 = await fs.readFile(cache.filePath, 'base64') - }catch (e) { - throw new Error("文件下载失败. " + e) - } - } - } - // if (autoDeleteFile) { - // setTimeout(() => { - // fs.unlink(cache.filePath) - // }, autoDeleteFileSecond * 1000) - // } - return res + } } + // if (autoDeleteFile) { + // setTimeout(() => { + // fs.unlink(cache.filePath) + // }, autoDeleteFileSecond * 1000) + // } + return res + } } export default class GetFile extends GetFileBase { - actionName = ActionName.GetFile + actionName = ActionName.GetFile - protected async _handle(payload: {file_id: string, file: string}): Promise { - if (!payload.file_id) { - throw new Error('file_id 不能为空') - } - payload.file = payload.file_id - return super._handle(payload); + protected async _handle(payload: { file_id: string; file: string }): Promise { + if (!payload.file_id) { + throw new Error('file_id 不能为空') } -} \ No newline at end of file + payload.file = payload.file_id + return super._handle(payload) + } +} diff --git a/src/onebot11/action/file/GetImage.ts b/src/onebot11/action/file/GetImage.ts index e55a4ef..d51ba67 100644 --- a/src/onebot11/action/file/GetImage.ts +++ b/src/onebot11/action/file/GetImage.ts @@ -1,7 +1,6 @@ -import {GetFileBase} from "./GetFile"; -import {ActionName} from "../types"; - +import { GetFileBase } from './GetFile' +import { ActionName } from '../types' export default class GetImage extends GetFileBase { - actionName = ActionName.GetImage -} \ No newline at end of file + actionName = ActionName.GetImage +} diff --git a/src/onebot11/action/file/GetRecord.ts b/src/onebot11/action/file/GetRecord.ts index 568c472..2e57624 100644 --- a/src/onebot11/action/file/GetRecord.ts +++ b/src/onebot11/action/file/GetRecord.ts @@ -1,15 +1,15 @@ -import {GetFileBase, GetFilePayload, GetFileResponse} from "./GetFile"; -import {ActionName} from "../types"; +import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile' +import { ActionName } from '../types' interface Payload extends GetFilePayload { - out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' + out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' } export default class GetRecord extends GetFileBase { - actionName = ActionName.GetRecord + actionName = ActionName.GetRecord - protected async _handle(payload: Payload): Promise { - let res = super._handle(payload); - return res; - } -} \ No newline at end of file + protected async _handle(payload: Payload): Promise { + let res = super._handle(payload) + return res + } +} diff --git a/src/onebot11/action/go-cqhttp/DownloadFile.ts b/src/onebot11/action/go-cqhttp/DownloadFile.ts index 4ca1204..5f7f758 100644 --- a/src/onebot11/action/go-cqhttp/DownloadFile.ts +++ b/src/onebot11/action/go-cqhttp/DownloadFile.ts @@ -1,73 +1,72 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import fs from "fs"; -import {join as joinPath} from "node:path"; -import {calculateFileMD5, httpDownload, TEMP_DIR} from "../../../common/utils"; -import {v4 as uuid4} from "uuid"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import fs from 'fs' +import { join as joinPath } from 'node:path' +import { calculateFileMD5, httpDownload, TEMP_DIR } from '../../../common/utils' +import { v4 as uuid4 } from 'uuid' interface Payload { - thread_count?: number - url?: string - base64?: string - name?: string - headers?: string | string[] + thread_count?: number + url?: string + base64?: string + name?: string + headers?: string | string[] } interface FileResponse { - file: string + file: string } export default class GoCQHTTPDownloadFile extends BaseAction { - actionName = ActionName.GoCQHTTP_DownloadFile + actionName = ActionName.GoCQHTTP_DownloadFile - protected async _handle(payload: Payload): Promise { - const isRandomName = !payload.name - let name = payload.name || uuid4(); - const filePath = joinPath(TEMP_DIR, name); + protected async _handle(payload: Payload): Promise { + const isRandomName = !payload.name + let name = payload.name || uuid4() + const filePath = joinPath(TEMP_DIR, name) - if (payload.base64) { - fs.writeFileSync(filePath, payload.base64, 'base64') - } else if (payload.url) { - const headers = this.getHeaders(payload.headers); - let buffer = await httpDownload({url: payload.url, headers: headers}) - fs.writeFileSync(filePath, Buffer.from(buffer), 'binary'); - } else { - throw new Error("不存在任何文件, 无法下载") - } - if (fs.existsSync(filePath)) { - - if (isRandomName) { - // 默认实现要名称未填写时文件名为文件 md5 - const md5 = await calculateFileMD5(filePath); - const newPath = joinPath(TEMP_DIR, md5); - fs.renameSync(filePath, newPath); - return { file: newPath } - } - return { file: filePath } - } else { - throw new Error("文件写入失败, 检查权限") - } + if (payload.base64) { + fs.writeFileSync(filePath, payload.base64, 'base64') + } else if (payload.url) { + const headers = this.getHeaders(payload.headers) + let buffer = await httpDownload({ url: payload.url, headers: headers }) + fs.writeFileSync(filePath, Buffer.from(buffer), 'binary') + } else { + throw new Error('不存在任何文件, 无法下载') } - - getHeaders(headersIn?: string | string[]): Record { - const headers = {}; - if (typeof headersIn == 'string') { - headersIn = headersIn.split('[\\r\\n]'); - } - if (Array.isArray(headersIn)) { - for (const headerItem of headersIn) { - const spilt = headerItem.indexOf('='); - if (spilt < 0) { - headers[headerItem] = ""; - } else { - const key = headerItem.substring(0, spilt); - headers[key] = headerItem.substring(0, spilt + 1); - } - } - } - if (!headers['Content-Type']) { - headers['Content-Type'] = 'application/octet-stream'; - } - return headers; + if (fs.existsSync(filePath)) { + if (isRandomName) { + // 默认实现要名称未填写时文件名为文件 md5 + const md5 = await calculateFileMD5(filePath) + const newPath = joinPath(TEMP_DIR, md5) + fs.renameSync(filePath, newPath) + return { file: newPath } + } + return { file: filePath } + } else { + throw new Error('文件写入失败, 检查权限') } -} \ No newline at end of file + } + + getHeaders(headersIn?: string | string[]): Record { + const headers = {} + if (typeof headersIn == 'string') { + headersIn = headersIn.split('[\\r\\n]') + } + if (Array.isArray(headersIn)) { + for (const headerItem of headersIn) { + const spilt = headerItem.indexOf('=') + if (spilt < 0) { + headers[headerItem] = '' + } else { + const key = headerItem.substring(0, spilt) + headers[key] = headerItem.substring(0, spilt + 1) + } + } + } + if (!headers['Content-Type']) { + headers['Content-Type'] = 'application/octet-stream' + } + return headers + } +} diff --git a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts index 04c89e4..affe4db 100644 --- a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts +++ b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts @@ -1,39 +1,45 @@ -import BaseAction from "../BaseAction"; -import {OB11ForwardMessage, OB11Message, OB11MessageData} from "../../types"; -import {NTQQMsgApi, Peer} from "../../../ntqqapi/api"; -import {dbUtil} from "../../../common/db"; -import {OB11Constructor} from "../../constructor"; -import {ActionName} from "../types"; +import BaseAction from '../BaseAction' +import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types' +import { NTQQMsgApi, Peer } from '../../../ntqqapi/api' +import { dbUtil } from '../../../common/db' +import { OB11Constructor } from '../../constructor' +import { ActionName } from '../types' interface Payload { - message_id: string; // long msg id + message_id: string // long msg id } -interface Response{ - messages: (OB11Message & {content: OB11MessageData})[] +interface Response { + messages: (OB11Message & { content: OB11MessageData })[] } -export class GoCQHTTGetForwardMsgAction extends BaseAction{ - actionName = ActionName.GoCQHTTP_GetForwardMsg - protected async _handle(payload: Payload): Promise { - const rootMsg = await dbUtil.getMsgByLongId(payload.message_id) - if (!rootMsg){ - throw Error("msg not found") - } - let data = await NTQQMsgApi.getMultiMsg({chatType: rootMsg.chatType, peerUid: rootMsg.peerUid}, rootMsg.msgId, rootMsg.msgId) - if (data.result !== 0){ - throw Error("找不到相关的聊天记录" + data.errMsg) - } - let msgList = data.msgList - let messages = await Promise.all(msgList.map(async msg => { - let resMsg = await OB11Constructor.message(msg) - resMsg.message_id = await dbUtil.addMsg(msg); - return resMsg - })) - messages.map(msg => { - (msg).content = msg.message; - delete msg.message; - }) - return {messages} +export class GoCQHTTGetForwardMsgAction extends BaseAction { + actionName = ActionName.GoCQHTTP_GetForwardMsg + protected async _handle(payload: Payload): Promise { + const rootMsg = await dbUtil.getMsgByLongId(payload.message_id) + if (!rootMsg) { + throw Error('msg not found') } -} \ No newline at end of file + let data = await NTQQMsgApi.getMultiMsg( + { chatType: rootMsg.chatType, peerUid: rootMsg.peerUid }, + rootMsg.msgId, + rootMsg.msgId, + ) + if (data.result !== 0) { + throw Error('找不到相关的聊天记录' + data.errMsg) + } + let msgList = data.msgList + let messages = await Promise.all( + msgList.map(async (msg) => { + let resMsg = await OB11Constructor.message(msg) + resMsg.message_id = await dbUtil.addMsg(msg) + return resMsg + }), + ) + messages.map((msg) => { + ;(msg).content = msg.message + delete msg.message + }) + return { messages } + } +} diff --git a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts index 3ac98ae..5c85074 100644 --- a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts +++ b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts @@ -1,39 +1,46 @@ -import BaseAction from "../BaseAction"; -import {OB11Message, OB11User} from "../../types"; -import {groups} from "../../../common/data"; -import {ActionName} from "../types"; -import {ChatType} from "../../../ntqqapi/types"; -import {dbUtil} from "../../../common/db"; -import {NTQQMsgApi} from "../../../ntqqapi/api/msg"; -import {OB11Constructor} from "../../constructor"; -import {log} from "../../../common/utils"; - +import BaseAction from '../BaseAction' +import { OB11Message, OB11User } from '../../types' +import { groups } from '../../../common/data' +import { ActionName } from '../types' +import { ChatType } from '../../../ntqqapi/types' +import { dbUtil } from '../../../common/db' +import { NTQQMsgApi } from '../../../ntqqapi/api/msg' +import { OB11Constructor } from '../../constructor' +import { log } from '../../../common/utils' interface Payload { - group_id: number - message_seq: number, - count: number + group_id: number + message_seq: number + count: number } -interface Response{ - messages: OB11Message[] +interface Response { + messages: OB11Message[] } export default class GoCQHTTPGetGroupMsgHistory extends BaseAction { - actionName = ActionName.GoCQHTTP_GetGroupMsgHistory + actionName = ActionName.GoCQHTTP_GetGroupMsgHistory - protected async _handle(payload: Payload): Promise { - const group = groups.find(group => group.groupCode === payload.group_id.toString()) - if (!group) { - throw `群${payload.group_id}不存在` - } - const startMsgId = (await dbUtil.getMsgByShortId(payload.message_seq))?.msgId || "0" - // log("startMsgId", startMsgId) - let msgList = (await NTQQMsgApi.getMsgHistory({chatType: ChatType.group, peerUid: group.groupCode}, startMsgId, parseInt(payload.count?.toString()) || 20)).msgList - await Promise.all(msgList.map(async msg => { - msg.msgShortId = await dbUtil.addMsg(msg) - })) - const ob11MsgList = await Promise.all(msgList.map(msg=>OB11Constructor.message(msg))) - return {"messages": ob11MsgList} + protected async _handle(payload: Payload): Promise { + const group = groups.find((group) => group.groupCode === payload.group_id.toString()) + if (!group) { + throw `群${payload.group_id}不存在` } -} \ No newline at end of file + const startMsgId = (await dbUtil.getMsgByShortId(payload.message_seq))?.msgId || '0' + // log("startMsgId", startMsgId) + let msgList = ( + await NTQQMsgApi.getMsgHistory( + { chatType: ChatType.group, peerUid: group.groupCode }, + startMsgId, + parseInt(payload.count?.toString()) || 20, + ) + ).msgList + await Promise.all( + msgList.map(async (msg) => { + msg.msgShortId = await dbUtil.addMsg(msg) + }), + ) + const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Constructor.message(msg))) + return { messages: ob11MsgList } + } +} diff --git a/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts b/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts index 41567ca..33451b7 100644 --- a/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts +++ b/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts @@ -1,20 +1,19 @@ -import BaseAction from "../BaseAction"; -import {OB11User} from "../../types"; -import {getUidByUin, uidMaps} from "../../../common/data"; -import {OB11Constructor} from "../../constructor"; -import {ActionName} from "../types"; -import {NTQQUserApi} from "../../../ntqqapi/api/user"; - +import BaseAction from '../BaseAction' +import { OB11User } from '../../types' +import { getUidByUin, uidMaps } from '../../../common/data' +import { OB11Constructor } from '../../constructor' +import { ActionName } from '../types' +import { NTQQUserApi } from '../../../ntqqapi/api/user' export default class GoCQHTTPGetStrangerInfo extends BaseAction<{ user_id: number }, OB11User> { - actionName = ActionName.GoCQHTTP_GetStrangerInfo + actionName = ActionName.GoCQHTTP_GetStrangerInfo - protected async _handle(payload: { user_id: number }): Promise { - const user_id = payload.user_id.toString() - const uid = getUidByUin(user_id) - if (!uid) { - throw new Error("查无此人") - } - return OB11Constructor.stranger(await NTQQUserApi.getUserDetailInfo(uid, true)) + protected async _handle(payload: { user_id: number }): Promise { + const user_id = payload.user_id.toString() + const uid = getUidByUin(user_id) + if (!uid) { + throw new Error('查无此人') } -} \ No newline at end of file + return OB11Constructor.stranger(await NTQQUserApi.getUserDetailInfo(uid, true)) + } +} diff --git a/src/onebot11/action/go-cqhttp/SendForwardMsg.ts b/src/onebot11/action/go-cqhttp/SendForwardMsg.ts index 7ee1c08..f484e87 100644 --- a/src/onebot11/action/go-cqhttp/SendForwardMsg.ts +++ b/src/onebot11/action/go-cqhttp/SendForwardMsg.ts @@ -1,20 +1,20 @@ -import SendMsg, {convertMessage2List} from "../msg/SendMsg"; -import {OB11PostSendMsg} from "../../types"; -import {ActionName} from "../types"; +import SendMsg, { convertMessage2List } from '../msg/SendMsg' +import { OB11PostSendMsg } from '../../types' +import { ActionName } from '../types' export class GoCQHTTPSendForwardMsg extends SendMsg { - actionName = ActionName.GoCQHTTP_SendForwardMsg; + actionName = ActionName.GoCQHTTP_SendForwardMsg - protected async check(payload: OB11PostSendMsg) { - if (payload.messages) payload.message = convertMessage2List(payload.messages); - return super.check(payload); - } + protected async check(payload: OB11PostSendMsg) { + if (payload.messages) payload.message = convertMessage2List(payload.messages) + return super.check(payload) + } } export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsg { - actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg; + actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg } export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsg { - actionName = ActionName.GoCQHTTP_SendGroupForwardMsg; -} \ No newline at end of file + actionName = ActionName.GoCQHTTP_SendGroupForwardMsg +} diff --git a/src/onebot11/action/go-cqhttp/UploadFile.ts b/src/onebot11/action/go-cqhttp/UploadFile.ts index 2dc49c9..4428c9b 100644 --- a/src/onebot11/action/go-cqhttp/UploadFile.ts +++ b/src/onebot11/action/go-cqhttp/UploadFile.ts @@ -1,51 +1,49 @@ -import BaseAction from "../BaseAction"; -import {getGroup, getUidByUin} from "../../../common/data"; -import {ActionName} from "../types"; -import {SendMsgElementConstructor} from "../../../ntqqapi/constructor"; -import {ChatType, SendFileElement} from "../../../ntqqapi/types"; -import fs from "fs"; -import {NTQQMsgApi, Peer} from "../../../ntqqapi/api/msg"; -import {uri2local} from "../../../common/utils"; +import BaseAction from '../BaseAction' +import { getGroup, getUidByUin } from '../../../common/data' +import { ActionName } from '../types' +import { SendMsgElementConstructor } from '../../../ntqqapi/constructor' +import { ChatType, SendFileElement } from '../../../ntqqapi/types' +import fs from 'fs' +import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg' +import { uri2local } from '../../../common/utils' -interface Payload{ - user_id: number - group_id?: number - file: string - name: string - folder: string +interface Payload { + user_id: number + group_id?: number + file: string + name: string + folder: string } class GoCQHTTPUploadFileBase extends BaseAction { - actionName = ActionName.GoCQHTTP_UploadGroupFile + actionName = ActionName.GoCQHTTP_UploadGroupFile - - getPeer(payload: Payload): Peer { - if (payload.user_id){ - return {chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString())} - } - return {chatType: ChatType.group, peerUid: payload.group_id.toString()} + getPeer(payload: Payload): Peer { + if (payload.user_id) { + return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) } } + return { chatType: ChatType.group, peerUid: payload.group_id.toString() } + } - protected async _handle(payload: Payload): Promise { - let file = payload.file; - if (fs.existsSync(file)){ - file = `file://${file}` - } - const downloadResult = await uri2local(file); - if (downloadResult.errMsg){ - throw new Error(downloadResult.errMsg) - } - let sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name); - await NTQQMsgApi.sendMsg(this.getPeer(payload), [sendFileEle]); - return null + protected async _handle(payload: Payload): Promise { + let file = payload.file + if (fs.existsSync(file)) { + file = `file://${file}` } + const downloadResult = await uri2local(file) + if (downloadResult.errMsg) { + throw new Error(downloadResult.errMsg) + } + let sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name) + await NTQQMsgApi.sendMsg(this.getPeer(payload), [sendFileEle]) + return null + } } - export class GoCQHTTPUploadGroupFile extends GoCQHTTPUploadFileBase { - actionName = ActionName.GoCQHTTP_UploadGroupFile + actionName = ActionName.GoCQHTTP_UploadGroupFile } export class GoCQHTTPUploadPrivateFile extends GoCQHTTPUploadFileBase { - actionName = ActionName.GoCQHTTP_UploadPrivateFile -} \ No newline at end of file + actionName = ActionName.GoCQHTTP_UploadPrivateFile +} diff --git a/src/onebot11/action/group/GetGroupInfo.ts b/src/onebot11/action/group/GetGroupInfo.ts index e36158d..8bfc219 100644 --- a/src/onebot11/action/group/GetGroupInfo.ts +++ b/src/onebot11/action/group/GetGroupInfo.ts @@ -1,24 +1,24 @@ -import {OB11Group} from '../../types'; -import {getGroup} from "../../../common/data"; -import {OB11Constructor} from "../../constructor"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; +import { OB11Group } from '../../types' +import { getGroup } from '../../../common/data' +import { OB11Constructor } from '../../constructor' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' interface PayloadType { - group_id: number + group_id: number } class GetGroupInfo extends BaseAction { - actionName = ActionName.GetGroupInfo + actionName = ActionName.GetGroupInfo - protected async _handle(payload: PayloadType) { - const group = await getGroup(payload.group_id.toString()) - if (group) { - return OB11Constructor.group(group) - } else { - throw `群${payload.group_id}不存在` - } + protected async _handle(payload: PayloadType) { + const group = await getGroup(payload.group_id.toString()) + if (group) { + return OB11Constructor.group(group) + } else { + throw `群${payload.group_id}不存在` } + } } export default GetGroupInfo diff --git a/src/onebot11/action/group/GetGroupList.ts b/src/onebot11/action/group/GetGroupList.ts index 69cdbd3..f27ef0c 100644 --- a/src/onebot11/action/group/GetGroupList.ts +++ b/src/onebot11/action/group/GetGroupList.ts @@ -1,10 +1,10 @@ -import {OB11Group} from '../../types'; -import {OB11Constructor} from "../../constructor"; -import {groups} from "../../../common/data"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api"; -import {log} from "../../../common/utils"; +import { OB11Group } from '../../types' +import { OB11Constructor } from '../../constructor' +import { groups } from '../../../common/data' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api' +import { log } from '../../../common/utils' interface Payload { no_cache: boolean @@ -14,19 +14,18 @@ class GetGroupList extends BaseAction { actionName = ActionName.GetGroupList protected async _handle(payload: Payload) { - if (groups.length === 0 + if ( + groups.length === 0 // || payload.no_cache === true ) { try { const groups = await NTQQGroupApi.getGroups(true) // log("get groups", groups) - return OB11Constructor.groups(groups); - } catch (e) { - - } + return OB11Constructor.groups(groups) + } catch (e) {} } - return OB11Constructor.groups(groups); + return OB11Constructor.groups(groups) } } -export default GetGroupList \ No newline at end of file +export default GetGroupList diff --git a/src/onebot11/action/group/GetGroupMemberInfo.ts b/src/onebot11/action/group/GetGroupMemberInfo.ts index e98d10e..cdd4a7d 100644 --- a/src/onebot11/action/group/GetGroupMemberInfo.ts +++ b/src/onebot11/action/group/GetGroupMemberInfo.ts @@ -1,35 +1,34 @@ -import {OB11GroupMember} from '../../types'; -import {getGroupMember} from "../../../common/data"; -import {OB11Constructor} from "../../constructor"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQUserApi} from "../../../ntqqapi/api/user"; -import {log} from "../../../common/utils/log"; -import {isNull} from "../../../common/utils/helper"; - +import { OB11GroupMember } from '../../types' +import { getGroupMember } from '../../../common/data' +import { OB11Constructor } from '../../constructor' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQUserApi } from '../../../ntqqapi/api/user' +import { log } from '../../../common/utils/log' +import { isNull } from '../../../common/utils/helper' export interface PayloadType { - group_id: number - user_id: number + group_id: number + user_id: number } class GetGroupMemberInfo extends BaseAction { - actionName = ActionName.GetGroupMemberInfo + actionName = ActionName.GetGroupMemberInfo - protected async _handle(payload: PayloadType) { - const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) - if (member) { - if (isNull(member.sex)){ - log("获取群成员详细信息") - let info = (await NTQQUserApi.getUserDetailInfo(member.uid, true)) - log("群成员详细信息结果", info) - Object.assign(member, info); - } - return OB11Constructor.groupMember(payload.group_id.toString(), member) - } else { - throw (`群成员${payload.user_id}不存在`) - } + protected async _handle(payload: PayloadType) { + const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) + if (member) { + if (isNull(member.sex)) { + log('获取群成员详细信息') + let info = await NTQQUserApi.getUserDetailInfo(member.uid, true) + log('群成员详细信息结果', info) + Object.assign(member, info) + } + return OB11Constructor.groupMember(payload.group_id.toString(), member) + } else { + throw `群成员${payload.user_id}不存在` } + } } -export default GetGroupMemberInfo \ No newline at end of file +export default GetGroupMemberInfo diff --git a/src/onebot11/action/group/GetGroupMemberList.ts b/src/onebot11/action/group/GetGroupMemberList.ts index 085eff2..da7555c 100644 --- a/src/onebot11/action/group/GetGroupMemberList.ts +++ b/src/onebot11/action/group/GetGroupMemberList.ts @@ -1,29 +1,28 @@ -import {OB11GroupMember} from '../../types'; -import {getGroup} from "../../../common/data"; -import {OB11Constructor} from "../../constructor"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import { OB11GroupMember } from '../../types' +import { getGroup } from '../../../common/data' +import { OB11Constructor } from '../../constructor' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' export interface PayloadType { - group_id: number + group_id: number } - class GetGroupMemberList extends BaseAction { - actionName = ActionName.GetGroupMemberList + actionName = ActionName.GetGroupMemberList - protected async _handle(payload: PayloadType) { - const group = await getGroup(payload.group_id.toString()); - if (group) { - if (!group.members?.length) { - group.members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) - } - return OB11Constructor.groupMembers(group); - } else { - throw (`群${payload.group_id}不存在`) - } + protected async _handle(payload: PayloadType) { + const group = await getGroup(payload.group_id.toString()) + if (group) { + if (!group.members?.length) { + group.members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) + } + return OB11Constructor.groupMembers(group) + } else { + throw `群${payload.group_id}不存在` } + } } -export default GetGroupMemberList \ No newline at end of file +export default GetGroupMemberList diff --git a/src/onebot11/action/group/GetGuildList.ts b/src/onebot11/action/group/GetGuildList.ts index 9824362..b9d2e9d 100644 --- a/src/onebot11/action/group/GetGuildList.ts +++ b/src/onebot11/action/group/GetGuildList.ts @@ -1,10 +1,10 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' export default class GetGuildList extends BaseAction { - actionName = ActionName.GetGuildList + actionName = ActionName.GetGuildList - protected async _handle(payload: null): Promise { - return null; - } -} \ No newline at end of file + protected async _handle(payload: null): Promise { + return null + } +} diff --git a/src/onebot11/action/group/SendGroupMsg.ts b/src/onebot11/action/group/SendGroupMsg.ts index d0b52d4..954c005 100644 --- a/src/onebot11/action/group/SendGroupMsg.ts +++ b/src/onebot11/action/group/SendGroupMsg.ts @@ -1,18 +1,17 @@ -import SendMsg from "../msg/SendMsg"; -import {ActionName, BaseCheckResult} from "../types"; -import {OB11PostSendMsg} from "../../types"; - -import {log} from "../../../common/utils/log"; +import SendMsg from '../msg/SendMsg' +import { ActionName, BaseCheckResult } from '../types' +import { OB11PostSendMsg } from '../../types' +import { log } from '../../../common/utils/log' class SendGroupMsg extends SendMsg { - actionName = ActionName.SendGroupMsg + actionName = ActionName.SendGroupMsg - protected async check(payload: OB11PostSendMsg): Promise { - delete payload.user_id; - payload.message_type = "group" - return super.check(payload); - } + protected async check(payload: OB11PostSendMsg): Promise { + delete payload.user_id + payload.message_type = 'group' + return super.check(payload) + } } -export default SendGroupMsg \ No newline at end of file +export default SendGroupMsg diff --git a/src/onebot11/action/group/SetGroupAddRequest.ts b/src/onebot11/action/group/SetGroupAddRequest.ts index c08bc6b..966c453 100644 --- a/src/onebot11/action/group/SetGroupAddRequest.ts +++ b/src/onebot11/action/group/SetGroupAddRequest.ts @@ -1,26 +1,27 @@ -import BaseAction from "../BaseAction"; -import {GroupRequestOperateTypes} from "../../../ntqqapi/types"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { GroupRequestOperateTypes } from '../../../ntqqapi/types' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - flag: string, - // sub_type: "add" | "invite", - // type: "add" | "invite" - approve: boolean, - reason: string + flag: string + // sub_type: "add" | "invite", + // type: "add" | "invite" + approve: boolean + reason: string } export default class SetGroupAddRequest extends BaseAction { - actionName = ActionName.SetGroupAddRequest + actionName = ActionName.SetGroupAddRequest - protected async _handle(payload: Payload): Promise { - const seq = payload.flag.toString(); - const approve = payload.approve.toString() === "true"; - await NTQQGroupApi.handleGroupRequest(seq, - approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, - payload.reason - ) - return null - } -} \ No newline at end of file + protected async _handle(payload: Payload): Promise { + const seq = payload.flag.toString() + const approve = payload.approve.toString() === 'true' + await NTQQGroupApi.handleGroupRequest( + seq, + approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, + payload.reason, + ) + return null + } +} diff --git a/src/onebot11/action/group/SetGroupAdmin.ts b/src/onebot11/action/group/SetGroupAdmin.ts index 5ead5ec..b56f88e 100644 --- a/src/onebot11/action/group/SetGroupAdmin.ts +++ b/src/onebot11/action/group/SetGroupAdmin.ts @@ -1,25 +1,29 @@ -import BaseAction from "../BaseAction"; -import {getGroupMember} from "../../../common/data"; -import {GroupMemberRole} from "../../../ntqqapi/types"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { getGroupMember } from '../../../common/data' +import { GroupMemberRole } from '../../../ntqqapi/types' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - group_id: number, - user_id: number, - enable: boolean + group_id: number + user_id: number + enable: boolean } export default class SetGroupAdmin extends BaseAction { - actionName = ActionName.SetGroupAdmin + actionName = ActionName.SetGroupAdmin - protected async _handle(payload: Payload): Promise { - const member = await getGroupMember(payload.group_id, payload.user_id) - const enable = payload.enable.toString() === "true" - if (!member) { - throw `群成员${payload.user_id}不存在` - } - await NTQQGroupApi.setMemberRole(payload.group_id.toString(), member.uid, enable ? GroupMemberRole.admin : GroupMemberRole.normal) - return null + protected async _handle(payload: Payload): Promise { + const member = await getGroupMember(payload.group_id, payload.user_id) + const enable = payload.enable.toString() === 'true' + if (!member) { + throw `群成员${payload.user_id}不存在` } -} \ No newline at end of file + await NTQQGroupApi.setMemberRole( + payload.group_id.toString(), + member.uid, + enable ? GroupMemberRole.admin : GroupMemberRole.normal, + ) + return null + } +} diff --git a/src/onebot11/action/group/SetGroupBan.ts b/src/onebot11/action/group/SetGroupBan.ts index 01d9c22..e0a01cd 100644 --- a/src/onebot11/action/group/SetGroupBan.ts +++ b/src/onebot11/action/group/SetGroupBan.ts @@ -1,24 +1,25 @@ -import BaseAction from "../BaseAction"; -import {getGroupMember} from "../../../common/data"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { getGroupMember } from '../../../common/data' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - group_id: number, - user_id: number, - duration: number + group_id: number + user_id: number + duration: number } export default class SetGroupBan extends BaseAction { - actionName = ActionName.SetGroupBan + actionName = ActionName.SetGroupBan - protected async _handle(payload: Payload): Promise { - const member = await getGroupMember(payload.group_id, payload.user_id) - if (!member) { - throw `群成员${payload.user_id}不存在` - } - await NTQQGroupApi.banMember(payload.group_id.toString(), - [{uid: member.uid, timeStamp: parseInt(payload.duration.toString())}]) - return null + protected async _handle(payload: Payload): Promise { + const member = await getGroupMember(payload.group_id, payload.user_id) + if (!member) { + throw `群成员${payload.user_id}不存在` } -} \ No newline at end of file + await NTQQGroupApi.banMember(payload.group_id.toString(), [ + { uid: member.uid, timeStamp: parseInt(payload.duration.toString()) }, + ]) + return null + } +} diff --git a/src/onebot11/action/group/SetGroupCard.ts b/src/onebot11/action/group/SetGroupCard.ts index 2c21d34..abbbfa8 100644 --- a/src/onebot11/action/group/SetGroupCard.ts +++ b/src/onebot11/action/group/SetGroupCard.ts @@ -1,23 +1,23 @@ -import BaseAction from "../BaseAction"; -import {getGroupMember} from "../../../common/data"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { getGroupMember } from '../../../common/data' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - group_id: number, - user_id: number, - card: string + group_id: number + user_id: number + card: string } export default class SetGroupCard extends BaseAction { - actionName = ActionName.SetGroupCard + actionName = ActionName.SetGroupCard - protected async _handle(payload: Payload): Promise { - const member = await getGroupMember(payload.group_id, payload.user_id) - if (!member) { - throw `群成员${payload.user_id}不存在` - } - await NTQQGroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || "") - return null + protected async _handle(payload: Payload): Promise { + const member = await getGroupMember(payload.group_id, payload.user_id) + if (!member) { + throw `群成员${payload.user_id}不存在` } -} \ No newline at end of file + await NTQQGroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || '') + return null + } +} diff --git a/src/onebot11/action/group/SetGroupKick.ts b/src/onebot11/action/group/SetGroupKick.ts index f4cc23a..93f82b6 100644 --- a/src/onebot11/action/group/SetGroupKick.ts +++ b/src/onebot11/action/group/SetGroupKick.ts @@ -1,23 +1,23 @@ -import BaseAction from "../BaseAction"; -import {getGroupMember} from "../../../common/data"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { getGroupMember } from '../../../common/data' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - group_id: number, - user_id: number, - reject_add_request: boolean + group_id: number + user_id: number + reject_add_request: boolean } export default class SetGroupKick extends BaseAction { - actionName = ActionName.SetGroupKick + actionName = ActionName.SetGroupKick - protected async _handle(payload: Payload): Promise { - const member = await getGroupMember(payload.group_id, payload.user_id) - if (!member) { - throw `群成员${payload.user_id}不存在` - } - await NTQQGroupApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request); - return null + protected async _handle(payload: Payload): Promise { + const member = await getGroupMember(payload.group_id, payload.user_id) + if (!member) { + throw `群成员${payload.user_id}不存在` } -} \ No newline at end of file + await NTQQGroupApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request) + return null + } +} diff --git a/src/onebot11/action/group/SetGroupLeave.ts b/src/onebot11/action/group/SetGroupLeave.ts index 5e60979..c8337f2 100644 --- a/src/onebot11/action/group/SetGroupLeave.ts +++ b/src/onebot11/action/group/SetGroupLeave.ts @@ -1,22 +1,22 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; -import {log} from "../../../common/utils/log"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' +import { log } from '../../../common/utils/log' interface Payload { - group_id: number, - is_dismiss: boolean + group_id: number + is_dismiss: boolean } export default class SetGroupLeave extends BaseAction { - actionName = ActionName.SetGroupLeave + actionName = ActionName.SetGroupLeave - protected async _handle(payload: Payload): Promise { - try { - await NTQQGroupApi.quitGroup(payload.group_id.toString()) - } catch (e) { - log("退群失败", e) - throw e - } + protected async _handle(payload: Payload): Promise { + try { + await NTQQGroupApi.quitGroup(payload.group_id.toString()) + } catch (e) { + log('退群失败', e) + throw e } -} \ No newline at end of file + } +} diff --git a/src/onebot11/action/group/SetGroupName.ts b/src/onebot11/action/group/SetGroupName.ts index 508b44f..a32bed6 100644 --- a/src/onebot11/action/group/SetGroupName.ts +++ b/src/onebot11/action/group/SetGroupName.ts @@ -1,18 +1,17 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - group_id: number, - group_name: string + group_id: number + group_name: string } export default class SetGroupName extends BaseAction { - actionName = ActionName.SetGroupName + actionName = ActionName.SetGroupName - protected async _handle(payload: Payload): Promise { - - await NTQQGroupApi.setGroupName(payload.group_id.toString(), payload.group_name) - return null - } -} \ No newline at end of file + protected async _handle(payload: Payload): Promise { + await NTQQGroupApi.setGroupName(payload.group_id.toString(), payload.group_name) + return null + } +} diff --git a/src/onebot11/action/group/SetGroupWholeBan.ts b/src/onebot11/action/group/SetGroupWholeBan.ts index aa0f3a3..fdf9a03 100644 --- a/src/onebot11/action/group/SetGroupWholeBan.ts +++ b/src/onebot11/action/group/SetGroupWholeBan.ts @@ -1,18 +1,18 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' interface Payload { - group_id: number, - enable: boolean + group_id: number + enable: boolean } export default class SetGroupWholeBan extends BaseAction { - actionName = ActionName.SetGroupWholeBan + actionName = ActionName.SetGroupWholeBan - protected async _handle(payload: Payload): Promise { - const enable = payload.enable.toString() === "true" - await NTQQGroupApi.banGroup(payload.group_id.toString(), enable) - return null - } -} \ No newline at end of file + protected async _handle(payload: Payload): Promise { + const enable = payload.enable.toString() === 'true' + await NTQQGroupApi.banGroup(payload.group_id.toString(), enable) + return null + } +} diff --git a/src/onebot11/action/index.ts b/src/onebot11/action/index.ts index 76ef78d..a5a4539 100644 --- a/src/onebot11/action/index.ts +++ b/src/onebot11/action/index.ts @@ -8,45 +8,45 @@ import GetGroupMemberInfo from './group/GetGroupMemberInfo' import SendGroupMsg from './group/SendGroupMsg' import SendPrivateMsg from './msg/SendPrivateMsg' import SendMsg from './msg/SendMsg' -import DeleteMsg from "./msg/DeleteMsg"; -import BaseAction from "./BaseAction"; -import GetVersionInfo from "./system/GetVersionInfo"; -import CanSendRecord from "./system/CanSendRecord"; -import CanSendImage from "./system/CanSendImage"; -import GetStatus from "./system/GetStatus"; +import DeleteMsg from './msg/DeleteMsg' +import BaseAction from './BaseAction' +import GetVersionInfo from './system/GetVersionInfo' +import CanSendRecord from './system/CanSendRecord' +import CanSendImage from './system/CanSendImage' +import GetStatus from './system/GetStatus' import { GoCQHTTPSendForwardMsg, GoCQHTTPSendGroupForwardMsg, - GoCQHTTPSendPrivateForwardMsg -} from "./go-cqhttp/SendForwardMsg"; -import GoCQHTTPGetStrangerInfo from "./go-cqhttp/GetStrangerInfo"; -import SendLike from "./user/SendLike"; -import SetGroupAddRequest from "./group/SetGroupAddRequest"; -import SetGroupLeave from "./group/SetGroupLeave"; -import GetGuildList from "./group/GetGuildList"; -import Debug from "./llonebot/Debug"; -import SetFriendAddRequest from "./user/SetFriendAddRequest"; -import SetGroupWholeBan from "./group/SetGroupWholeBan"; -import SetGroupName from "./group/SetGroupName"; -import SetGroupBan from "./group/SetGroupBan"; -import SetGroupKick from "./group/SetGroupKick"; -import SetGroupAdmin from "./group/SetGroupAdmin"; -import SetGroupCard from "./group/SetGroupCard"; -import GetImage from "./file/GetImage"; -import GetRecord from "./file/GetRecord"; -import GoCQHTTPMarkMsgAsRead from "./msg/MarkMsgAsRead"; -import CleanCache from "./system/CleanCache"; -import {GoCQHTTPUploadGroupFile, GoCQHTTPUploadPrivateFile} from "./go-cqhttp/UploadFile"; -import {GetConfigAction, SetConfigAction} from "./llonebot/Config"; -import GetGroupAddRequest from "./llonebot/GetGroupAddRequest"; + GoCQHTTPSendPrivateForwardMsg, +} from './go-cqhttp/SendForwardMsg' +import GoCQHTTPGetStrangerInfo from './go-cqhttp/GetStrangerInfo' +import SendLike from './user/SendLike' +import SetGroupAddRequest from './group/SetGroupAddRequest' +import SetGroupLeave from './group/SetGroupLeave' +import GetGuildList from './group/GetGuildList' +import Debug from './llonebot/Debug' +import SetFriendAddRequest from './user/SetFriendAddRequest' +import SetGroupWholeBan from './group/SetGroupWholeBan' +import SetGroupName from './group/SetGroupName' +import SetGroupBan from './group/SetGroupBan' +import SetGroupKick from './group/SetGroupKick' +import SetGroupAdmin from './group/SetGroupAdmin' +import SetGroupCard from './group/SetGroupCard' +import GetImage from './file/GetImage' +import GetRecord from './file/GetRecord' +import GoCQHTTPMarkMsgAsRead from './msg/MarkMsgAsRead' +import CleanCache from './system/CleanCache' +import { GoCQHTTPUploadGroupFile, GoCQHTTPUploadPrivateFile } from './go-cqhttp/UploadFile' +import { GetConfigAction, SetConfigAction } from './llonebot/Config' +import GetGroupAddRequest from './llonebot/GetGroupAddRequest' import SetQQAvatar from './llonebot/SetQQAvatar' -import GoCQHTTPDownloadFile from "./go-cqhttp/DownloadFile"; -import GoCQHTTPGetGroupMsgHistory from "./go-cqhttp/GetGroupMsgHistory"; -import GetFile from "./file/GetFile"; -import {GoCQHTTGetForwardMsgAction} from "./go-cqhttp/GetForwardMsg"; -import {GetCookies} from "./user/GetCookie"; -import {SetMsgEmojiLike} from "./msg/SetMsgEmojiLike"; -import {ForwardFriendSingleMsg, ForwardSingleGroupMsg} from "./msg/ForwardSingleMsg"; +import GoCQHTTPDownloadFile from './go-cqhttp/DownloadFile' +import GoCQHTTPGetGroupMsgHistory from './go-cqhttp/GetGroupMsgHistory' +import GetFile from './file/GetFile' +import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg' +import { GetCookies } from './user/GetCookie' +import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike' +import { ForwardFriendSingleMsg, ForwardSingleGroupMsg } from './msg/ForwardSingleMsg' export const actionHandlers = [ new GetFile(), @@ -60,8 +60,13 @@ export const actionHandlers = [ new GetMsg(), new GetLoginInfo(), new GetFriendList(), - new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(), - new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(), + new GetGroupList(), + new GetGroupInfo(), + new GetGroupMemberList(), + new GetGroupMemberInfo(), + new SendGroupMsg(), + new SendPrivateMsg(), + new SendMsg(), new DeleteMsg(), new SetGroupAddRequest(), new SetFriendAddRequest(), @@ -98,14 +103,14 @@ export const actionHandlers = [ ] function initActionMap() { - const actionMap = new Map>(); + const actionMap = new Map>() for (const action of actionHandlers) { - actionMap.set(action.actionName, action); - actionMap.set(action.actionName + '_async', action); - actionMap.set(action.actionName + '_rate_limited', action); + actionMap.set(action.actionName, action) + actionMap.set(action.actionName + '_async', action) + actionMap.set(action.actionName + '_rate_limited', action) } return actionMap } -export const actionMap = initActionMap(); +export const actionMap = initActionMap() diff --git a/src/onebot11/action/llonebot/Config.ts b/src/onebot11/action/llonebot/Config.ts index 64bd7f9..d50fb31 100644 --- a/src/onebot11/action/llonebot/Config.ts +++ b/src/onebot11/action/llonebot/Config.ts @@ -1,20 +1,19 @@ -import BaseAction from "../BaseAction"; -import {Config} from "../../../common/types"; -import {ActionName} from "../types"; -import {setConfig} from "../../../main/setConfig"; -import {getConfigUtil} from "../../../common/config"; - +import BaseAction from '../BaseAction' +import { Config } from '../../../common/types' +import { ActionName } from '../types' +import { setConfig } from '../../../main/setConfig' +import { getConfigUtil } from '../../../common/config' export class GetConfigAction extends BaseAction { - actionName = ActionName.GetConfig - protected async _handle(payload: null): Promise { - return getConfigUtil().getConfig() - } + actionName = ActionName.GetConfig + protected async _handle(payload: null): Promise { + return getConfigUtil().getConfig() + } } export class SetConfigAction extends BaseAction { - actionName = ActionName.SetConfig - protected async _handle(payload: Config): Promise { - setConfig(payload).then(); - } -} \ No newline at end of file + actionName = ActionName.SetConfig + protected async _handle(payload: Config): Promise { + setConfig(payload).then() + } +} diff --git a/src/onebot11/action/llonebot/Debug.ts b/src/onebot11/action/llonebot/Debug.ts index 7ac1964..70a74c9 100644 --- a/src/onebot11/action/llonebot/Debug.ts +++ b/src/onebot11/action/llonebot/Debug.ts @@ -1,42 +1,42 @@ -import BaseAction from "../BaseAction"; +import BaseAction from '../BaseAction' // import * as ntqqApi from "../../../ntqqapi/api"; import { - NTQQMsgApi, - NTQQFriendApi, - NTQQGroupApi, - NTQQUserApi, - NTQQFileApi, - NTQQFileCacheApi, - NTQQWindowApi, -} from "../../../ntqqapi/api"; -import {ActionName} from "../types"; -import {log} from "../../../common/utils/log"; + NTQQMsgApi, + NTQQFriendApi, + NTQQGroupApi, + NTQQUserApi, + NTQQFileApi, + NTQQFileCacheApi, + NTQQWindowApi, +} from '../../../ntqqapi/api' +import { ActionName } from '../types' +import { log } from '../../../common/utils/log' interface Payload { - method: string, - args: any[], + method: string + args: any[] } export default class Debug extends BaseAction { - actionName = ActionName.Debug + actionName = ActionName.Debug - protected async _handle(payload: Payload): Promise { - log("debug call ntqq api", payload); - const ntqqApi = [NTQQMsgApi, NTQQFriendApi, NTQQGroupApi, NTQQUserApi, NTQQFileApi, NTQQFileCacheApi, NTQQWindowApi] - for (const ntqqApiClass of ntqqApi) { - log("ntqqApiClass", ntqqApiClass) - const method = ntqqApiClass[payload.method] - if (method) { - const result = method(...payload.args); - if (method.constructor.name === "AsyncFunction") { - return await result - } - return result - } + protected async _handle(payload: Payload): Promise { + log('debug call ntqq api', payload) + const ntqqApi = [NTQQMsgApi, NTQQFriendApi, NTQQGroupApi, NTQQUserApi, NTQQFileApi, NTQQFileCacheApi, NTQQWindowApi] + for (const ntqqApiClass of ntqqApi) { + log('ntqqApiClass', ntqqApiClass) + const method = ntqqApiClass[payload.method] + if (method) { + const result = method(...payload.args) + if (method.constructor.name === 'AsyncFunction') { + return await result } - throw `${payload.method}方法 不存在` - - // const info = await NTQQApi.getUserDetailInfo(friends[0].uid); - // return info + return result + } } -} \ No newline at end of file + throw `${payload.method}方法 不存在` + + // const info = await NTQQApi.getUserDetailInfo(friends[0].uid); + // return info + } +} diff --git a/src/onebot11/action/llonebot/GetGroupAddRequest.ts b/src/onebot11/action/llonebot/GetGroupAddRequest.ts index 219e5c5..6f986aa 100644 --- a/src/onebot11/action/llonebot/GetGroupAddRequest.ts +++ b/src/onebot11/action/llonebot/GetGroupAddRequest.ts @@ -1,33 +1,33 @@ -import {GroupNotify, GroupNotifyStatus} from "../../../ntqqapi/types"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {uidMaps} from "../../../common/data"; -import {NTQQUserApi} from "../../../ntqqapi/api/user"; -import {NTQQGroupApi} from "../../../ntqqapi/api/group"; -import {log} from "../../../common/utils/log"; +import { GroupNotify, GroupNotifyStatus } from '../../../ntqqapi/types' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { uidMaps } from '../../../common/data' +import { NTQQUserApi } from '../../../ntqqapi/api/user' +import { NTQQGroupApi } from '../../../ntqqapi/api/group' +import { log } from '../../../common/utils/log' interface OB11GroupRequestNotify { - group_id: number, - user_id: number, - flag: string + group_id: number + user_id: number + flag: string } export default class GetGroupAddRequest extends BaseAction { - actionName = ActionName.GetGroupIgnoreAddRequest + actionName = ActionName.GetGroupIgnoreAddRequest - protected async _handle(payload: null): Promise { - const data = await NTQQGroupApi.getGroupIgnoreNotifies() - log(data); - let notifies: GroupNotify[] = data.notifies.filter(notify => notify.status === GroupNotifyStatus.WAIT_HANDLE); - let returnData: OB11GroupRequestNotify[] = [] - for (const notify of notifies) { - const uin = uidMaps[notify.user1.uid] || (await NTQQUserApi.getUserDetailInfo(notify.user1.uid))?.uin - returnData.push({ - group_id: parseInt(notify.group.groupCode), - user_id: parseInt(uin), - flag: notify.seq - }) - } - return returnData; + protected async _handle(payload: null): Promise { + const data = await NTQQGroupApi.getGroupIgnoreNotifies() + log(data) + let notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.WAIT_HANDLE) + let returnData: OB11GroupRequestNotify[] = [] + for (const notify of notifies) { + const uin = uidMaps[notify.user1.uid] || (await NTQQUserApi.getUserDetailInfo(notify.user1.uid))?.uin + returnData.push({ + group_id: parseInt(notify.group.groupCode), + user_id: parseInt(uin), + flag: notify.seq, + }) } -} \ No newline at end of file + return returnData + } +} diff --git a/src/onebot11/action/llonebot/SetQQAvatar.ts b/src/onebot11/action/llonebot/SetQQAvatar.ts index 04c90f6..3d6d041 100644 --- a/src/onebot11/action/llonebot/SetQQAvatar.ts +++ b/src/onebot11/action/llonebot/SetQQAvatar.ts @@ -1,43 +1,43 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import * as fs from "node:fs"; -import {NTQQUserApi} from "../../../ntqqapi/api/user"; -import {checkFileReceived, uri2local} from "../../../common/utils/file"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import * as fs from 'node:fs' +import { NTQQUserApi } from '../../../ntqqapi/api/user' +import { checkFileReceived, uri2local } from '../../../common/utils/file' // import { log } from "../../../common/utils"; interface Payload { - file: string + file: string } export default class SetAvatar extends BaseAction { - actionName = ActionName.SetQQAvatar + actionName = ActionName.SetQQAvatar - protected async _handle(payload: Payload): Promise { - const {path, isLocal, errMsg} = (await uri2local(payload.file)) - if (errMsg){ - throw `头像${payload.file}设置失败,file字段可能格式不正确` - } - if (path) { - await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃,需要提前判断 - const ret = await NTQQUserApi.setQQAvatar(path) - if (!isLocal){ - fs.unlink(path, () => {}) - } - if (!ret) { - throw `头像${payload.file}设置失败,api无返回` - } - // log(`头像设置返回:${JSON.stringify(ret)}`) - if (ret['result'] == 1004022) { - throw `头像${payload.file}设置失败,文件可能不是图片格式` - } else if(ret['result'] != 0) { - throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}` - } - } else { - if (!isLocal){ - fs.unlink(path, () => {}) - } - throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在` - } - return null + protected async _handle(payload: Payload): Promise { + const { path, isLocal, errMsg } = await uri2local(payload.file) + if (errMsg) { + throw `头像${payload.file}设置失败,file字段可能格式不正确` } -} \ No newline at end of file + if (path) { + await checkFileReceived(path, 5000) // 文件不存在QQ会崩溃,需要提前判断 + const ret = await NTQQUserApi.setQQAvatar(path) + if (!isLocal) { + fs.unlink(path, () => {}) + } + if (!ret) { + throw `头像${payload.file}设置失败,api无返回` + } + // log(`头像设置返回:${JSON.stringify(ret)}`) + if (ret['result'] == 1004022) { + throw `头像${payload.file}设置失败,文件可能不是图片格式` + } else if (ret['result'] != 0) { + throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}` + } + } else { + if (!isLocal) { + fs.unlink(path, () => {}) + } + throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在` + } + return null + } +} diff --git a/src/onebot11/action/msg/DeleteMsg.ts b/src/onebot11/action/msg/DeleteMsg.ts index c67620c..c85d6d5 100644 --- a/src/onebot11/action/msg/DeleteMsg.ts +++ b/src/onebot11/action/msg/DeleteMsg.ts @@ -1,22 +1,25 @@ -import {ActionName} from "../types"; -import BaseAction from "../BaseAction"; -import {dbUtil} from "../../../common/db"; -import {NTQQMsgApi} from "../../../ntqqapi/api/msg"; +import { ActionName } from '../types' +import BaseAction from '../BaseAction' +import { dbUtil } from '../../../common/db' +import { NTQQMsgApi } from '../../../ntqqapi/api/msg' interface Payload { - message_id: number + message_id: number } class DeleteMsg extends BaseAction { - actionName = ActionName.DeleteMsg + actionName = ActionName.DeleteMsg - protected async _handle(payload: Payload) { - let msg = await dbUtil.getMsgByShortId(payload.message_id) - await NTQQMsgApi.recallMsg({ - chatType: msg.chatType, - peerUid: msg.peerUid - }, [msg.msgId]) - } + protected async _handle(payload: Payload) { + let msg = await dbUtil.getMsgByShortId(payload.message_id) + await NTQQMsgApi.recallMsg( + { + chatType: msg.chatType, + peerUid: msg.peerUid, + }, + [msg.msgId], + ) + } } -export default DeleteMsg \ No newline at end of file +export default DeleteMsg diff --git a/src/onebot11/action/msg/ForwardSingleMsg.ts b/src/onebot11/action/msg/ForwardSingleMsg.ts index a138aab..b08a093 100644 --- a/src/onebot11/action/msg/ForwardSingleMsg.ts +++ b/src/onebot11/action/msg/ForwardSingleMsg.ts @@ -1,9 +1,9 @@ -import BaseAction from "../BaseAction"; -import {NTQQMsgApi, Peer} from "../../../ntqqapi/api"; -import {ChatType, RawMessage} from "../../../ntqqapi/types"; -import {dbUtil} from "../../../common/db"; -import {getUidByUin} from "../../../common/data"; -import {ActionName} from "../types"; +import BaseAction from '../BaseAction' +import { NTQQMsgApi, Peer } from '../../../ntqqapi/api' +import { ChatType, RawMessage } from '../../../ntqqapi/types' +import { dbUtil } from '../../../common/db' +import { getUidByUin } from '../../../common/data' +import { ActionName } from '../types' interface Payload { message_id: number @@ -11,27 +11,26 @@ interface Payload { user_id?: number } - class ForwardSingleMsg extends BaseAction { - protected async getTargetPeer(payload: Payload): Promise { - if (payload.user_id){ - return {chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString())} - } - return {chatType: ChatType.group, peerUid: payload.group_id.toString()} + if (payload.user_id) { + return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) } + } + return { chatType: ChatType.group, peerUid: payload.group_id.toString() } } protected async _handle(payload: Payload): Promise { const msg = await dbUtil.getMsgByShortId(payload.message_id) - const peer = await this.getTargetPeer(payload); - await NTQQMsgApi.forwardMsg({ + const peer = await this.getTargetPeer(payload) + await NTQQMsgApi.forwardMsg( + { chatType: msg.chatType, - peerUid: msg.peerUid + peerUid: msg.peerUid, }, peer, - [msg.msgId] + [msg.msgId], ) - return null; + return null } } @@ -41,4 +40,4 @@ export class ForwardFriendSingleMsg extends ForwardSingleMsg { export class ForwardSingleGroupMsg extends ForwardSingleMsg { actionName = ActionName.ForwardGroupSingleMsg -} \ No newline at end of file +} diff --git a/src/onebot11/action/msg/GetMsg.ts b/src/onebot11/action/msg/GetMsg.ts index 72e21d5..543ef05 100644 --- a/src/onebot11/action/msg/GetMsg.ts +++ b/src/onebot11/action/msg/GetMsg.ts @@ -1,33 +1,32 @@ -import {OB11Message} from '../../types'; -import {OB11Constructor} from "../../constructor"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {dbUtil} from "../../../common/db"; - +import { OB11Message } from '../../types' +import { OB11Constructor } from '../../constructor' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { dbUtil } from '../../../common/db' export interface PayloadType { - message_id: number + message_id: number } export type ReturnDataType = OB11Message class GetMsg extends BaseAction { - actionName = ActionName.GetMsg + actionName = ActionName.GetMsg - protected async _handle(payload: PayloadType) { - // log("history msg ids", Object.keys(msgHistory)); - if (!payload.message_id) { - throw ("参数message_id不能为空") - } - let msg = await dbUtil.getMsgByShortId(payload.message_id) - if(!msg) { - msg = await dbUtil.getMsgByLongId(payload.message_id.toString()) - } - if (!msg){ - throw ("消息不存在") - } - return await OB11Constructor.message(msg) + protected async _handle(payload: PayloadType) { + // log("history msg ids", Object.keys(msgHistory)); + if (!payload.message_id) { + throw '参数message_id不能为空' } + let msg = await dbUtil.getMsgByShortId(payload.message_id) + if (!msg) { + msg = await dbUtil.getMsgByLongId(payload.message_id.toString()) + } + if (!msg) { + throw '消息不存在' + } + return await OB11Constructor.message(msg) + } } -export default GetMsg \ No newline at end of file +export default GetMsg diff --git a/src/onebot11/action/msg/MarkMsgAsRead.ts b/src/onebot11/action/msg/MarkMsgAsRead.ts index 6d6e097..f3796f5 100644 --- a/src/onebot11/action/msg/MarkMsgAsRead.ts +++ b/src/onebot11/action/msg/MarkMsgAsRead.ts @@ -1,14 +1,14 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' -interface Payload{ - message_id: number +interface Payload { + message_id: number } -export default class GoCQHTTPMarkMsgAsRead extends BaseAction{ - actionName = ActionName.GoCQHTTP_MarkMsgAsRead +export default class GoCQHTTPMarkMsgAsRead extends BaseAction { + actionName = ActionName.GoCQHTTP_MarkMsgAsRead - protected async _handle(payload: Payload): Promise { - return null - } -} \ No newline at end of file + protected async _handle(payload: Payload): Promise { + return null + } +} diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 092e787..69bbca8 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -1,563 +1,599 @@ import { - AtType, - ChatType, - ElementType, - Friend, - Group, - GroupMemberRole, - PicSubType, - RawMessage, - SendArkElement, - SendMessageElement -} from "../../../ntqqapi/types"; -import {friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo,} from "../../../common/data"; + AtType, + ChatType, + ElementType, + Friend, + Group, + GroupMemberRole, + PicSubType, + RawMessage, + SendArkElement, + SendMessageElement, +} from '../../../ntqqapi/types' +import { friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo } from '../../../common/data' import { - OB11MessageCustomMusic, - OB11MessageData, - OB11MessageDataType, - OB11MessageMixType, - OB11MessageNode, - OB11PostSendMsg -} from '../../types'; -import {NTQQMsgApi, Peer} from "../../../ntqqapi/api/msg"; -import {SendMsgElementConstructor} from "../../../ntqqapi/constructor"; -import BaseAction from "../BaseAction"; -import {ActionName, BaseCheckResult} from "../types"; -import * as fs from "node:fs"; -import {decodeCQCode} from "../../cqcode"; -import {dbUtil} from "../../../common/db"; -import {ALLOW_SEND_TEMP_MSG} from "../../../common/config"; -import {log} from "../../../common/utils/log"; -import {sleep} from "../../../common/utils/helper"; -import {uri2local} from "../../../common/utils"; -import {crychic} from "../../../ntqqapi/external/crychic"; -import {NTQQGroupApi} from "../../../ntqqapi/api"; + OB11MessageCustomMusic, + OB11MessageData, + OB11MessageDataType, + OB11MessageMixType, + OB11MessageNode, + OB11PostSendMsg, +} from '../../types' +import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg' +import { SendMsgElementConstructor } from '../../../ntqqapi/constructor' +import BaseAction from '../BaseAction' +import { ActionName, BaseCheckResult } from '../types' +import * as fs from 'node:fs' +import { decodeCQCode } from '../../cqcode' +import { dbUtil } from '../../../common/db' +import { ALLOW_SEND_TEMP_MSG } from '../../../common/config' +import { log } from '../../../common/utils/log' +import { sleep } from '../../../common/utils/helper' +import { uri2local } from '../../../common/utils' +import { crychic } from '../../../ntqqapi/external/crychic' +import { NTQQGroupApi } from '../../../ntqqapi/api' function checkSendMessage(sendMsgList: OB11MessageData[]) { - function checkUri(uri: string): boolean { - const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/; - return pattern.test(uri); - } + function checkUri(uri: string): boolean { + const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/ + return pattern.test(uri) + } - for (let msg of sendMsgList) { - if (msg["type"] && msg["data"]) { - let type = msg["type"]; - let data = msg["data"]; - if (type === "text" && !data["text"]) { - return 400; - } else if (["image", "voice", "record"].includes(type)) { - if (!data["file"]) { - return 400; - } else { - if (checkUri(data["file"])) { - return 200; - } else { - return 400; - } - } - - } else if (type === "at" && !data["qq"]) { - return 400; - } else if (type === "reply" && !data["id"]) { - return 400; - } + for (let msg of sendMsgList) { + if (msg['type'] && msg['data']) { + let type = msg['type'] + let data = msg['data'] + if (type === 'text' && !data['text']) { + return 400 + } else if (['image', 'voice', 'record'].includes(type)) { + if (!data['file']) { + return 400 } else { + if (checkUri(data['file'])) { + return 200 + } else { return 400 + } } + } else if (type === 'at' && !data['qq']) { + return 400 + } else if (type === 'reply' && !data['id']) { + return 400 + } + } else { + return 400 } - return 200; + } + return 200 } export interface ReturnDataType { - message_id: number + message_id: number } export function convertMessage2List(message: OB11MessageMixType, autoEscape = false) { - if (typeof message === "string") { - if (autoEscape === true) { - message = [{ - type: OB11MessageDataType.text, - data: { - text: message - } - }] - } else { - message = decodeCQCode(message.toString()) - } - } else if (!Array.isArray(message)) { - message = [message] + if (typeof message === 'string') { + if (autoEscape === true) { + message = [ + { + type: OB11MessageDataType.text, + data: { + text: message, + }, + }, + ] + } else { + message = decodeCQCode(message.toString()) } - return message; + } else if (!Array.isArray(message)) { + message = [message] + } + return message } -export async function createSendElements(messageData: OB11MessageData[], target: Group | Friend | undefined, ignoreTypes: OB11MessageDataType[] = []) { - let sendElements: SendMessageElement[] = [] - let deleteAfterSentFiles: string[] = [] - for (let sendMsg of messageData) { - if (ignoreTypes.includes(sendMsg.type)) { +export async function createSendElements( + messageData: OB11MessageData[], + target: Group | Friend | undefined, + ignoreTypes: OB11MessageDataType[] = [], +) { + let sendElements: SendMessageElement[] = [] + let deleteAfterSentFiles: string[] = [] + for (let sendMsg of messageData) { + if (ignoreTypes.includes(sendMsg.type)) { + continue + } + switch (sendMsg.type) { + case OB11MessageDataType.text: + { + const text = sendMsg.data?.text + if (text) { + sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) + } + } + break + case OB11MessageDataType.at: + { + if (!target) { continue + } + let atQQ = sendMsg.data?.qq + if (atQQ) { + atQQ = atQQ.toString() + if (atQQ === 'all') { + // todo:查询剩余的at全体次数 + const groupCode = (target as Group)?.groupCode + let remainAtAllCount = 1 + let isAdmin: boolean = true + if (groupCode) { + try { + remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo + .RemainAtAllCountForUin + log(`群${groupCode}剩余at全体次数`, remainAtAllCount) + const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin) + isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner + } catch (e) {} + } + if (isAdmin && remainAtAllCount > 0) { + sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员')) + } + } else { + // const atMember = group?.members.find(m => m.uin == atQQ) + const atMember = await getGroupMember((target as Group)?.groupCode, atQQ) + if (atMember) { + sendElements.push( + SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick), + ) + } + } + } } - switch (sendMsg.type) { - case OB11MessageDataType.text: { - const text = sendMsg.data?.text; - if (text) { - sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) - } + break + case OB11MessageDataType.reply: + { + let replyMsgId = sendMsg.data.id + if (replyMsgId) { + const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId)) + if (replyMsg) { + sendElements.push( + SendMsgElementConstructor.reply( + replyMsg.msgSeq, + replyMsg.msgId, + replyMsg.senderUin, + replyMsg.senderUin, + ), + ) } - break; - case OB11MessageDataType.at: { - if (!target) { - continue - } - let atQQ = sendMsg.data?.qq; - if (atQQ) { - atQQ = atQQ.toString() - if (atQQ === "all") { - // todo:查询剩余的at全体次数 - const groupCode = (target as Group)?.groupCode; - let remainAtAllCount = 1 - let isAdmin: boolean = true; - if (groupCode) { - try { - remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo.RemainAtAllCountForUin - log(`群${groupCode}剩余at全体次数`, remainAtAllCount); - const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin); - isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner; - } catch (e) {} - } - if(isAdmin && remainAtAllCount > 0) { - sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) - } - } else { - // const atMember = group?.members.find(m => m.uin == atQQ) - const atMember = await getGroupMember((target as Group)?.groupCode, atQQ); - if (atMember) { - sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) - } - } - } - } - break; - case OB11MessageDataType.reply: { - let replyMsgId = sendMsg.data.id; - if (replyMsgId) { - const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId)) - if (replyMsg) { - sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin, replyMsg.senderUin)) - } - } - } - break; - case OB11MessageDataType.face: { - const faceId = sendMsg.data?.id - if (faceId) { - sendElements.push(SendMsgElementConstructor.face(parseInt(faceId))) - } - } - break; - case OB11MessageDataType.mface: { - sendElements.push(SendMsgElementConstructor.mface(sendMsg.data.emojiPackageId, sendMsg.data.emojiId, sendMsg.data.key)) - } - case OB11MessageDataType.image: - case OB11MessageDataType.file: - case OB11MessageDataType.video: - case OB11MessageDataType.voice: { - let file = sendMsg.data?.file - const payloadFileName = sendMsg.data?.name - if (file) { - const cache = await dbUtil.getFileCache(file) - if (cache) { - if (fs.existsSync(cache.filePath)) { - file = "file://" + cache.filePath - } else if (cache.downloadFunc) { - await cache.downloadFunc() - file = cache.filePath; - } else if (cache.url) { - file = cache.url - } - log("找到文件缓存", file); - } - const {path, isLocal, fileName, errMsg} = (await uri2local(file)) - if (errMsg) { - throw errMsg - } - if (path) { - if (!isLocal) { // 只删除http和base64转过来的文件 - deleteAfterSentFiles.push(path) - } - if (sendMsg.type === OB11MessageDataType.file) { - log("发送文件", path, payloadFileName || fileName) - sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName)); - } else if (sendMsg.type === OB11MessageDataType.video) { - log("发送视频", path, payloadFileName || fileName) - let thumb = sendMsg.data?.thumb; - if (thumb) { - let uri2LocalRes = await uri2local(thumb) - if (uri2LocalRes.success) { - thumb = uri2LocalRes.path; - } - } - sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb)); - } else if (sendMsg.type === OB11MessageDataType.voice) { - sendElements.push(await SendMsgElementConstructor.ptt(path)); - } else if (sendMsg.type === OB11MessageDataType.image) { - sendElements.push(await SendMsgElementConstructor.pic(path, sendMsg.data.summary || "", parseInt(sendMsg.data?.subType?.toString()) || 0)); - } - } - } - } - break; - case OB11MessageDataType.json: { - sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data)) - } - break; - case OB11MessageDataType.poke: { - let qq = sendMsg.data?.qq || sendMsg.data?.id - if (qq) { - if ("groupCode" in target) { - crychic.sendGroupPoke(target.groupCode, qq.toString()) - } else { - if (!qq) { - qq = parseInt(target.uin) - } - crychic.sendFriendPoke(qq.toString()) - } - sendElements.push(SendMsgElementConstructor.poke("", "")) - } - } - break; - case OB11MessageDataType.dice:{ - const resultId = sendMsg.data?.result - sendElements.push(SendMsgElementConstructor.dice(resultId)); - }break; - case OB11MessageDataType.RPS:{ - const resultId = sendMsg.data?.result - sendElements.push(SendMsgElementConstructor.rps(resultId)); - }break; + } } - + break + case OB11MessageDataType.face: + { + const faceId = sendMsg.data?.id + if (faceId) { + sendElements.push(SendMsgElementConstructor.face(parseInt(faceId))) + } + } + break + case OB11MessageDataType.mface: { + sendElements.push( + SendMsgElementConstructor.mface(sendMsg.data.emojiPackageId, sendMsg.data.emojiId, sendMsg.data.key), + ) + } + case OB11MessageDataType.image: + case OB11MessageDataType.file: + case OB11MessageDataType.video: + case OB11MessageDataType.voice: + { + let file = sendMsg.data?.file + const payloadFileName = sendMsg.data?.name + if (file) { + const cache = await dbUtil.getFileCache(file) + if (cache) { + if (fs.existsSync(cache.filePath)) { + file = 'file://' + cache.filePath + } else if (cache.downloadFunc) { + await cache.downloadFunc() + file = cache.filePath + } else if (cache.url) { + file = cache.url + } + log('找到文件缓存', file) + } + const { path, isLocal, fileName, errMsg } = await uri2local(file) + if (errMsg) { + throw errMsg + } + if (path) { + if (!isLocal) { + // 只删除http和base64转过来的文件 + deleteAfterSentFiles.push(path) + } + if (sendMsg.type === OB11MessageDataType.file) { + log('发送文件', path, payloadFileName || fileName) + sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName)) + } else if (sendMsg.type === OB11MessageDataType.video) { + log('发送视频', path, payloadFileName || fileName) + let thumb = sendMsg.data?.thumb + if (thumb) { + let uri2LocalRes = await uri2local(thumb) + if (uri2LocalRes.success) { + thumb = uri2LocalRes.path + } + } + sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb)) + } else if (sendMsg.type === OB11MessageDataType.voice) { + sendElements.push(await SendMsgElementConstructor.ptt(path)) + } else if (sendMsg.type === OB11MessageDataType.image) { + sendElements.push( + await SendMsgElementConstructor.pic( + path, + sendMsg.data.summary || '', + parseInt(sendMsg.data?.subType?.toString()) || 0, + ), + ) + } + } + } + } + break + case OB11MessageDataType.json: + { + sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data)) + } + break + case OB11MessageDataType.poke: + { + let qq = sendMsg.data?.qq || sendMsg.data?.id + if (qq) { + if ('groupCode' in target) { + crychic.sendGroupPoke(target.groupCode, qq.toString()) + } else { + if (!qq) { + qq = parseInt(target.uin) + } + crychic.sendFriendPoke(qq.toString()) + } + sendElements.push(SendMsgElementConstructor.poke('', '')) + } + } + break + case OB11MessageDataType.dice: + { + const resultId = sendMsg.data?.result + sendElements.push(SendMsgElementConstructor.dice(resultId)) + } + break + case OB11MessageDataType.RPS: + { + const resultId = sendMsg.data?.result + sendElements.push(SendMsgElementConstructor.rps(resultId)) + } + break } + } - return { - sendElements, - deleteAfterSentFiles - } + return { + sendElements, + deleteAfterSentFiles, + } } -export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete = true) { - if (!sendElements.length) { - throw ("消息体无法解析") - } - const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, 20000); - log("消息发送结果", returnMsg) - returnMsg.msgShortId = await dbUtil.addMsg(returnMsg) - deleteAfterSentFiles.map(f => fs.unlink(f, () => { - })) - return returnMsg +export async function sendMsg( + peer: Peer, + sendElements: SendMessageElement[], + deleteAfterSentFiles: string[], + waitComplete = true, +) { + if (!sendElements.length) { + throw '消息体无法解析' + } + const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, 20000) + log('消息发送结果', returnMsg) + returnMsg.msgShortId = await dbUtil.addMsg(returnMsg) + deleteAfterSentFiles.map((f) => fs.unlink(f, () => {})) + return returnMsg } export class SendMsg extends BaseAction { - actionName = ActionName.SendMsg + actionName = ActionName.SendMsg - protected async check(payload: OB11PostSendMsg): Promise { - const messages = convertMessage2List(payload.message); - const fmNum = this.getSpecialMsgNum(payload, OB11MessageDataType.node) - if (fmNum && fmNum != messages.length) { - return { - valid: false, - message: "转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素" - } - } - if (payload.message_type !== "private" && payload.group_id && !(await getGroup(payload.group_id))) { - return { - valid: false, - message: `群${payload.group_id}不存在` - } - } - if (payload.user_id && payload.message_type !== "group") { - if (!(await getFriend(payload.user_id))) { - if (!ALLOW_SEND_TEMP_MSG && !(await dbUtil.getReceivedTempUinMap())[payload.user_id.toString()]) { - return { - valid: false, - message: `不能发送临时消息` - } - } - } - } - return { - valid: true, + protected async check(payload: OB11PostSendMsg): Promise { + const messages = convertMessage2List(payload.message) + const fmNum = this.getSpecialMsgNum(payload, OB11MessageDataType.node) + if (fmNum && fmNum != messages.length) { + return { + valid: false, + message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', + } + } + if (payload.message_type !== 'private' && payload.group_id && !(await getGroup(payload.group_id))) { + return { + valid: false, + message: `群${payload.group_id}不存在`, + } + } + if (payload.user_id && payload.message_type !== 'group') { + if (!(await getFriend(payload.user_id))) { + if (!ALLOW_SEND_TEMP_MSG && !(await dbUtil.getReceivedTempUinMap())[payload.user_id.toString()]) { + return { + valid: false, + message: `不能发送临时消息`, + } } + } + } + return { + valid: true, + } + } + + protected async _handle(payload: OB11PostSendMsg) { + const peer: Peer = { + chatType: ChatType.friend, + peerUid: '', + } + let isTempMsg = false + let group: Group | undefined = undefined + let friend: Friend | undefined = undefined + const genGroupPeer = async () => { + group = await getGroup(payload.group_id.toString()) + peer.chatType = ChatType.group + // peer.name = group.name + peer.peerUid = group.groupCode } - protected async _handle(payload: OB11PostSendMsg) { - - const peer: Peer = { - chatType: ChatType.friend, - peerUid: "" + const genFriendPeer = () => { + friend = friends.find((f) => f.uin == payload.user_id.toString()) + if (friend) { + // peer.name = friend.nickName + peer.peerUid = friend.uid + } else { + peer.chatType = ChatType.temp + const tempUserUid = getUidByUin(payload.user_id.toString()) + if (!tempUserUid) { + throw `找不到私聊对象${payload.user_id}` } - let isTempMsg = false; - let group: Group | undefined = undefined; - let friend: Friend | undefined = undefined; - const genGroupPeer = async () => { - group = await getGroup(payload.group_id.toString()) - peer.chatType = ChatType.group - // peer.name = group.name - peer.peerUid = group.groupCode + // peer.name = tempUser.nickName + isTempMsg = true + peer.peerUid = tempUserUid + } + } + if (payload?.group_id && payload.message_type === 'group') { + await genGroupPeer() + } else if (payload?.user_id) { + genFriendPeer() + } else if (payload.group_id) { + await genGroupPeer() + } else { + throw '发送消息参数错误, 请指定group_id或user_id' + } + const messages = convertMessage2List( + payload.message, + payload.auto_escape === true || payload.auto_escape === 'true', + ) + if (this.getSpecialMsgNum(payload, OB11MessageDataType.node)) { + try { + const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group) + return { message_id: returnMsg.msgShortId } + } catch (e) { + throw '发送转发消息失败 ' + e.toString() + } + } else { + if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) { + const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic + if (music) { + const { url, audio, title, content, image } = music.data + const selfPeer: Peer = { peerUid: selfInfo.uid, chatType: ChatType.friend } + // 搞不定! + // const musicMsg = await this.send(selfPeer, [this.genMusicElement(url, audio, title, content, image)], [], false) + // 转发 + // const res = await NTQQApi.forwardMsg(selfPeer, peer, [musicMsg.msgId]) + // log("转发音乐消息成功", res); + // return {message_id: musicMsg.msgShortId} } + } + } + // log("send msg:", peer, sendElements) + const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group || friend) + if (sendElements.length === 1) { + if (sendElements[0] === null) { + return { message_id: 0 } + } + } + const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles) + deleteAfterSentFiles.map((f) => fs.unlink(f, () => {})) + return { message_id: returnMsg.msgShortId } + } - const genFriendPeer = () => { - friend = friends.find(f => f.uin == payload.user_id.toString()) - if (friend) { - // peer.name = friend.nickName - peer.peerUid = friend.uid - } else { - peer.chatType = ChatType.temp - const tempUserUid = getUidByUin(payload.user_id.toString()) - if (!tempUserUid) { - throw (`找不到私聊对象${payload.user_id}`) - } - // peer.name = tempUser.nickName - isTempMsg = true; - peer.peerUid = tempUserUid; - } - } - if (payload?.group_id && payload.message_type === "group") { - await genGroupPeer() + private getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number { + if (Array.isArray(payload.message)) { + return payload.message.filter((msg) => msg.type == msgType).length + } + return 0 + } - } else if (payload?.user_id) { - genFriendPeer() - } else if (payload.group_id) { - await genGroupPeer() + private async cloneMsg(msg: RawMessage): Promise { + log('克隆的目标消息', msg) + let sendElements: SendMessageElement[] = [] + for (const ele of msg.elements) { + sendElements.push(ele as SendMessageElement) + // Object.keys(ele).forEach((eleKey) => { + // if (eleKey.endsWith("Element")) { + // } + } + if (sendElements.length === 0) { + log('需要clone的消息无法解析,将会忽略掉', msg) + } + log('克隆消息', sendElements) + try { + const nodeMsg = await NTQQMsgApi.sendMsg( + { + chatType: ChatType.friend, + peerUid: selfInfo.uid, + }, + sendElements, + true, + ) + await sleep(500) + return nodeMsg + } catch (e) { + log(e, '克隆转发消息失败,将忽略本条消息', msg) + } + } + + // 返回一个合并转发的消息id + private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) { + const selfPeer = { + chatType: ChatType.friend, + peerUid: selfInfo.uid, + } + let nodeMsgIds: string[] = [] + // 先判断一遍是不是id和自定义混用 + let needClone = + messageNodes.filter((node) => node.data.id).length && messageNodes.filter((node) => !node.data.id).length + for (const messageNode of messageNodes) { + // 一个node表示一个人的消息 + let nodeId = messageNode.data.id + // 有nodeId表示一个子转发消息卡片 + if (nodeId) { + let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId)) + if (!needClone) { + nodeMsgIds.push(nodeMsg.msgId) } else { - throw ("发送消息参数错误, 请指定group_id或user_id") - } - const messages = convertMessage2List(payload.message, payload.auto_escape === true || payload.auto_escape === 'true'); - if (this.getSpecialMsgNum(payload, OB11MessageDataType.node)) { - try { - const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group) - return {message_id: returnMsg.msgShortId} - } catch (e) { - throw ("发送转发消息失败 " + e.toString()) - } - } else { - if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) { - const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic - if (music) { - const {url, audio, title, content, image} = music.data; - const selfPeer: Peer = {peerUid: selfInfo.uid, chatType: ChatType.friend} - // 搞不定! - // const musicMsg = await this.send(selfPeer, [this.genMusicElement(url, audio, title, content, image)], [], false) - // 转发 - // const res = await NTQQApi.forwardMsg(selfPeer, peer, [musicMsg.msgId]) - // log("转发音乐消息成功", res); - // return {message_id: musicMsg.msgShortId} - } + if (nodeMsg.peerUid !== selfInfo.uid) { + const cloneMsg = await this.cloneMsg(nodeMsg) + if (cloneMsg) { + nodeMsgIds.push(cloneMsg.msgId) } + } } - // log("send msg:", peer, sendElements) - const {sendElements, deleteAfterSentFiles} = await createSendElements(messages, group || friend) - if (sendElements.length === 1){ - if (sendElements[0] === null){ - return {message_id: 0} - } - } - const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles) - deleteAfterSentFiles.map(f => fs.unlink(f, () => { - })); - return {message_id: returnMsg.msgShortId} - } - - - private getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number { - if (Array.isArray(payload.message)) { - return payload.message.filter(msg => msg.type == msgType).length - } - return 0 - } - - private async cloneMsg(msg: RawMessage): Promise { - log("克隆的目标消息", msg) - let sendElements: SendMessageElement[] = []; - for (const ele of msg.elements) { - sendElements.push(ele as SendMessageElement) - // Object.keys(ele).forEach((eleKey) => { - // if (eleKey.endsWith("Element")) { - // } - - } - if (sendElements.length === 0) { - log("需要clone的消息无法解析,将会忽略掉", msg) - } - log("克隆消息", sendElements) + } else { + // 自定义的消息 + // 提取消息段,发给自己生成消息id try { - const nodeMsg = await NTQQMsgApi.sendMsg({ - chatType: ChatType.friend, - peerUid: selfInfo.uid - }, sendElements, true); - await sleep(500); - return nodeMsg - } catch (e) { - log(e, "克隆转发消息失败,将忽略本条消息", msg); - } + const { sendElements, deleteAfterSentFiles } = await createSendElements( + convertMessage2List(messageNode.data.content), + group, + ) + log('开始生成转发节点', sendElements) + let sendElementsSplit: SendMessageElement[][] = [] + let splitIndex = 0 + for (const ele of sendElements) { + if (!sendElementsSplit[splitIndex]) { + sendElementsSplit[splitIndex] = [] + } - } - - // 返回一个合并转发的消息id - private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) { - - const selfPeer = { - chatType: ChatType.friend, - peerUid: selfInfo.uid - } - let nodeMsgIds: string[] = [] - // 先判断一遍是不是id和自定义混用 - let needClone = messageNodes.filter(node => node.data.id).length && messageNodes.filter(node => !node.data.id).length - for (const messageNode of messageNodes) { - // 一个node表示一个人的消息 - let nodeId = messageNode.data.id; - // 有nodeId表示一个子转发消息卡片 - if (nodeId) { - let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId)); - if (!needClone) { - nodeMsgIds.push(nodeMsg.msgId) - } else { - if (nodeMsg.peerUid !== selfInfo.uid) { - const cloneMsg = await this.cloneMsg(nodeMsg) - if (cloneMsg) { - nodeMsgIds.push(cloneMsg.msgId) - } - } - } + if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) { + if (sendElementsSplit[splitIndex].length > 0) { + splitIndex++ + } + sendElementsSplit[splitIndex] = [ele] + splitIndex++ } else { - // 自定义的消息 - // 提取消息段,发给自己生成消息id - try { - const { - sendElements, - deleteAfterSentFiles - } = await createSendElements(convertMessage2List(messageNode.data.content), group); - log("开始生成转发节点", sendElements); - let sendElementsSplit: SendMessageElement[][] = [] - let splitIndex = 0; - for (const ele of sendElements) { - if (!sendElementsSplit[splitIndex]) { - sendElementsSplit[splitIndex] = [] - } - - if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) { - if (sendElementsSplit[splitIndex].length > 0) { - splitIndex++; - } - sendElementsSplit[splitIndex] = [ele] - splitIndex++; - } else { - sendElementsSplit[splitIndex].push(ele) - } - log(sendElementsSplit) - } - // log("分割后的转发节点", sendElementsSplit) - for (const eles of sendElementsSplit) { - const nodeMsg = await sendMsg(selfPeer, eles, [], true); - nodeMsgIds.push(nodeMsg.msgId) - await sleep(500); - log("转发节点生成成功", nodeMsg.msgId); - } - deleteAfterSentFiles.map(f => fs.unlink(f, () => { - })); - - } catch (e) { - log("生成转发消息节点失败", e) - } + sendElementsSplit[splitIndex].push(ele) } - } - - // 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发 - let nodeMsgArray: Array = [] - let srcPeer: Peer = null; - let needSendSelf = false; - for (const [index, msgId] of nodeMsgIds.entries()) { - const nodeMsg = await dbUtil.getMsgByLongId(msgId) - if (nodeMsg) { - nodeMsgArray.push(nodeMsg) - if (!srcPeer) { - srcPeer = {chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid} - } else if (srcPeer.peerUid !== nodeMsg.peerUid) { - needSendSelf = true - srcPeer = selfPeer - } - } - } - log("nodeMsgArray", nodeMsgArray); - nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); - if (needSendSelf) { - log("需要克隆转发消息"); - for (const [index, msg] of nodeMsgArray.entries()) { - if (msg.peerUid !== selfInfo.uid) { - const cloneMsg = await this.cloneMsg(msg) - if (cloneMsg) { - nodeMsgIds[index] = cloneMsg.msgId - } - } - } - } - // elements之间用换行符分隔 - // let _sendForwardElements: SendMessageElement[] = [] - // for(let i = 0; i < sendForwardElements.length; i++){ - // _sendForwardElements.push(sendForwardElements[i]) - // _sendForwardElements.push(SendMsgElementConstructor.text("\n\n")) - // } - // const nodeMsg = await NTQQApi.sendMsg(selfPeer, _sendForwardElements, true); - // nodeIds.push(nodeMsg.msgId) - // await sleep(500); - // 开发转发 - if (nodeMsgIds.length === 0) { - throw Error("转发消息失败,节点为空") - } - try { - log("开发转发", nodeMsgIds) - return await NTQQMsgApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds) + log(sendElementsSplit) + } + // log("分割后的转发节点", sendElementsSplit) + for (const eles of sendElementsSplit) { + const nodeMsg = await sendMsg(selfPeer, eles, [], true) + nodeMsgIds.push(nodeMsg.msgId) + await sleep(500) + log('转发节点生成成功', nodeMsg.msgId) + } + deleteAfterSentFiles.map((f) => fs.unlink(f, () => {})) } catch (e) { - log("forward failed", e) - return null; + log('生成转发消息节点失败', e) } + } } - - private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement { - const musicJson = { - app: 'com.tencent.structmsg', - config: { - ctime: 1709689928, - forward: 1, - token: '5c1e4905f926dd3a64a4bd3841460351', - type: 'normal' - }, - extra: {app_type: 1, appid: 100497308, uin: selfInfo.uin}, - meta: { - news: { - action: '', - android_pkg_name: '', - app_type: 1, - appid: 100497308, - ctime: 1709689928, - desc: content || title, - jumpUrl: url, - musicUrl: audio, - preview: image, - source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0', - source_url: '', - tag: 'QQ音乐', - title: title, - uin: selfInfo.uin, - } - }, - prompt: content || title, - ver: '0.0.0.1', - view: 'news' + // 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发 + let nodeMsgArray: Array = [] + let srcPeer: Peer = null + let needSendSelf = false + for (const [index, msgId] of nodeMsgIds.entries()) { + const nodeMsg = await dbUtil.getMsgByLongId(msgId) + if (nodeMsg) { + nodeMsgArray.push(nodeMsg) + if (!srcPeer) { + srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid } + } else if (srcPeer.peerUid !== nodeMsg.peerUid) { + needSendSelf = true + srcPeer = selfPeer } - - return SendMsgElementConstructor.ark(musicJson) + } } + log('nodeMsgArray', nodeMsgArray) + nodeMsgIds = nodeMsgArray.map((msg) => msg.msgId) + if (needSendSelf) { + log('需要克隆转发消息') + for (const [index, msg] of nodeMsgArray.entries()) { + if (msg.peerUid !== selfInfo.uid) { + const cloneMsg = await this.cloneMsg(msg) + if (cloneMsg) { + nodeMsgIds[index] = cloneMsg.msgId + } + } + } + } + // elements之间用换行符分隔 + // let _sendForwardElements: SendMessageElement[] = [] + // for(let i = 0; i < sendForwardElements.length; i++){ + // _sendForwardElements.push(sendForwardElements[i]) + // _sendForwardElements.push(SendMsgElementConstructor.text("\n\n")) + // } + // const nodeMsg = await NTQQApi.sendMsg(selfPeer, _sendForwardElements, true); + // nodeIds.push(nodeMsg.msgId) + // await sleep(500); + // 开发转发 + if (nodeMsgIds.length === 0) { + throw Error('转发消息失败,节点为空') + } + try { + log('开发转发', nodeMsgIds) + return await NTQQMsgApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds) + } catch (e) { + log('forward failed', e) + return null + } + } + + private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement { + const musicJson = { + app: 'com.tencent.structmsg', + config: { + ctime: 1709689928, + forward: 1, + token: '5c1e4905f926dd3a64a4bd3841460351', + type: 'normal', + }, + extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin }, + meta: { + news: { + action: '', + android_pkg_name: '', + app_type: 1, + appid: 100497308, + ctime: 1709689928, + desc: content || title, + jumpUrl: url, + musicUrl: audio, + preview: image, + source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0', + source_url: '', + tag: 'QQ音乐', + title: title, + uin: selfInfo.uin, + }, + }, + prompt: content || title, + ver: '0.0.0.1', + view: 'news', + } + + return SendMsgElementConstructor.ark(musicJson) + } } -export default SendMsg \ No newline at end of file +export default SendMsg diff --git a/src/onebot11/action/msg/SendPrivateMsg.ts b/src/onebot11/action/msg/SendPrivateMsg.ts index 8304b3d..e24aa53 100644 --- a/src/onebot11/action/msg/SendPrivateMsg.ts +++ b/src/onebot11/action/msg/SendPrivateMsg.ts @@ -1,14 +1,14 @@ -import SendMsg from "./SendMsg"; -import {ActionName, BaseCheckResult} from "../types"; -import {OB11PostSendMsg} from "../../types"; +import SendMsg from './SendMsg' +import { ActionName, BaseCheckResult } from '../types' +import { OB11PostSendMsg } from '../../types' class SendPrivateMsg extends SendMsg { - actionName = ActionName.SendPrivateMsg + actionName = ActionName.SendPrivateMsg - protected async check(payload: OB11PostSendMsg): Promise { - payload.message_type = "private" - return super.check(payload); - } + protected async check(payload: OB11PostSendMsg): Promise { + payload.message_type = 'private' + return super.check(payload) + } } -export default SendPrivateMsg \ No newline at end of file +export default SendPrivateMsg diff --git a/src/onebot11/action/msg/SetMsgEmojiLike.ts b/src/onebot11/action/msg/SetMsgEmojiLike.ts index 31ac092..eaa5ffb 100644 --- a/src/onebot11/action/msg/SetMsgEmojiLike.ts +++ b/src/onebot11/action/msg/SetMsgEmojiLike.ts @@ -1,27 +1,32 @@ -import {ActionName} from "../types"; -import BaseAction from "../BaseAction"; -import {dbUtil} from "../../../common/db"; -import {NTQQMsgApi} from "../../../ntqqapi/api/msg"; +import { ActionName } from '../types' +import BaseAction from '../BaseAction' +import { dbUtil } from '../../../common/db' +import { NTQQMsgApi } from '../../../ntqqapi/api/msg' interface Payload { - message_id: number, - emoji_id: string + message_id: number + emoji_id: string } export class SetMsgEmojiLike extends BaseAction { - actionName = ActionName.SetMsgEmojiLike + actionName = ActionName.SetMsgEmojiLike - protected async _handle(payload: Payload) { - let msg = await dbUtil.getMsgByShortId(payload.message_id) - if (!msg) { - throw new Error('msg not found') - } - if (!payload.emoji_id){ - throw new Error('emojiId not found') - } - return await NTQQMsgApi.setEmojiLike({ - chatType: msg.chatType, - peerUid: msg.peerUid - }, msg.msgSeq, payload.emoji_id, true) + protected async _handle(payload: Payload) { + let msg = await dbUtil.getMsgByShortId(payload.message_id) + if (!msg) { + throw new Error('msg not found') } + if (!payload.emoji_id) { + throw new Error('emojiId not found') + } + return await NTQQMsgApi.setEmojiLike( + { + chatType: msg.chatType, + peerUid: msg.peerUid, + }, + msg.msgSeq, + payload.emoji_id, + true, + ) + } } diff --git a/src/onebot11/action/system/CanSendImage.ts b/src/onebot11/action/system/CanSendImage.ts index 48354ac..6afb0bd 100644 --- a/src/onebot11/action/system/CanSendImage.ts +++ b/src/onebot11/action/system/CanSendImage.ts @@ -1,10 +1,10 @@ -import {ActionName} from "../types"; -import CanSendRecord from "./CanSendRecord"; +import { ActionName } from '../types' +import CanSendRecord from './CanSendRecord' interface ReturnType { - yes: boolean + yes: boolean } export default class CanSendImage extends CanSendRecord { - actionName = ActionName.CanSendImage -} \ No newline at end of file + actionName = ActionName.CanSendImage +} diff --git a/src/onebot11/action/system/CanSendRecord.ts b/src/onebot11/action/system/CanSendRecord.ts index 3464f49..be08da8 100644 --- a/src/onebot11/action/system/CanSendRecord.ts +++ b/src/onebot11/action/system/CanSendRecord.ts @@ -1,16 +1,16 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' interface ReturnType { - yes: boolean + yes: boolean } export default class CanSendRecord extends BaseAction { - actionName = ActionName.CanSendRecord + actionName = ActionName.CanSendRecord - protected async _handle(payload): Promise { - return { - yes: true - } + protected async _handle(payload): Promise { + return { + yes: true, } -} \ No newline at end of file + } +} diff --git a/src/onebot11/action/system/CleanCache.ts b/src/onebot11/action/system/CleanCache.ts index f6547e7..9d5d95f 100644 --- a/src/onebot11/action/system/CleanCache.ts +++ b/src/onebot11/action/system/CleanCache.ts @@ -1,105 +1,103 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import fs from "fs"; -import Path from "path"; -import { - ChatType, - ChatCacheListItemBasic, - CacheFileType -} from '../../../ntqqapi/types'; -import {dbUtil} from "../../../common/db"; -import {NTQQFileApi, NTQQFileCacheApi} from "../../../ntqqapi/api/file"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import fs from 'fs' +import Path from 'path' +import { ChatType, ChatCacheListItemBasic, CacheFileType } from '../../../ntqqapi/types' +import { dbUtil } from '../../../common/db' +import { NTQQFileApi, NTQQFileCacheApi } from '../../../ntqqapi/api/file' export default class CleanCache extends BaseAction { - actionName = ActionName.CleanCache + actionName = ActionName.CleanCache - protected _handle(): Promise { - return new Promise(async (res, rej) => { - try { - // dbUtil.clearCache(); - const cacheFilePaths: string[] = []; + protected _handle(): Promise { + return new Promise(async (res, rej) => { + try { + // dbUtil.clearCache(); + const cacheFilePaths: string[] = [] - await NTQQFileCacheApi.setCacheSilentScan(false); + await NTQQFileCacheApi.setCacheSilentScan(false) - cacheFilePaths.push((await NTQQFileCacheApi.getHotUpdateCachePath())); - cacheFilePaths.push((await NTQQFileCacheApi.getDesktopTmpPath())); - (await NTQQFileCacheApi.getCacheSessionPathList()).forEach(e => cacheFilePaths.push(e.value)); + cacheFilePaths.push(await NTQQFileCacheApi.getHotUpdateCachePath()) + cacheFilePaths.push(await NTQQFileCacheApi.getDesktopTmpPath()) + ;(await NTQQFileCacheApi.getCacheSessionPathList()).forEach((e) => cacheFilePaths.push(e.value)) - // await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知 - const cacheScanResult = await NTQQFileCacheApi.scanCache(); - const cacheSize = parseInt(cacheScanResult.size[6]); + // await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知 + const cacheScanResult = await NTQQFileCacheApi.scanCache() + const cacheSize = parseInt(cacheScanResult.size[6]) - if (cacheScanResult.result !== 0) { - throw('Something went wrong while scanning cache. Code: ' + cacheScanResult.result); - } + if (cacheScanResult.result !== 0) { + throw 'Something went wrong while scanning cache. Code: ' + cacheScanResult.result + } - await NTQQFileCacheApi.setCacheSilentScan(true); - if (cacheSize > 0 && cacheFilePaths.length > 2) { // 存在缓存文件且大小不为 0 时执行清理动作 - // await NTQQApi.clearCache([ 'tmp', 'hotUpdate', ...cacheScanResult ]) // XXX: 也是调用就崩溃,调用 fs 删除得了 - deleteCachePath(cacheFilePaths); - } + await NTQQFileCacheApi.setCacheSilentScan(true) + if (cacheSize > 0 && cacheFilePaths.length > 2) { + // 存在缓存文件且大小不为 0 时执行清理动作 + // await NTQQApi.clearCache([ 'tmp', 'hotUpdate', ...cacheScanResult ]) // XXX: 也是调用就崩溃,调用 fs 删除得了 + deleteCachePath(cacheFilePaths) + } - // 获取聊天记录列表 - // NOTE: 以防有人不需要删除聊天记录,暂时先注释掉,日后加个开关 - // const privateChatCache = await getCacheList(ChatType.friend); // 私聊消息 - // const groupChatCache = await getCacheList(ChatType.group); // 群聊消息 - // const chatCacheList = [ ...privateChatCache, ...groupChatCache ]; - const chatCacheList: ChatCacheListItemBasic[] = []; + // 获取聊天记录列表 + // NOTE: 以防有人不需要删除聊天记录,暂时先注释掉,日后加个开关 + // const privateChatCache = await getCacheList(ChatType.friend); // 私聊消息 + // const groupChatCache = await getCacheList(ChatType.group); // 群聊消息 + // const chatCacheList = [ ...privateChatCache, ...groupChatCache ]; + const chatCacheList: ChatCacheListItemBasic[] = [] - // 获取聊天缓存文件列表 - const cacheFileList: string[] = []; - - for (const name in CacheFileType) { - if (!isNaN(parseInt(name))) continue; + // 获取聊天缓存文件列表 + const cacheFileList: string[] = [] - const fileTypeAny: any = CacheFileType[name]; - const fileType: CacheFileType = fileTypeAny; + for (const name in CacheFileType) { + if (!isNaN(parseInt(name))) continue - cacheFileList.push(...(await NTQQFileCacheApi.getFileCacheInfo(fileType)).infos.map(file => file.fileKey)); - } + const fileTypeAny: any = CacheFileType[name] + const fileType: CacheFileType = fileTypeAny - // 一并清除 - await NTQQFileCacheApi.clearChatCache(chatCacheList, cacheFileList); - res(); - } catch(e) { - console.error('清理缓存时发生了错误'); - rej(e); - } - }); - } + cacheFileList.push(...(await NTQQFileCacheApi.getFileCacheInfo(fileType)).infos.map((file) => file.fileKey)) + } + + // 一并清除 + await NTQQFileCacheApi.clearChatCache(chatCacheList, cacheFileList) + res() + } catch (e) { + console.error('清理缓存时发生了错误') + rej(e) + } + }) + } } function deleteCachePath(pathList: string[]) { - const emptyPath = (path: string) => { - if (!fs.existsSync(path)) return; - const files = fs.readdirSync(path); - files.forEach(file => { - const filePath = Path.resolve(path, file); - const stats = fs.statSync(filePath); - if (stats.isDirectory()) emptyPath(filePath); - else fs.unlinkSync(filePath); - }); - fs.rmdirSync(path); - } + const emptyPath = (path: string) => { + if (!fs.existsSync(path)) return + const files = fs.readdirSync(path) + files.forEach((file) => { + const filePath = Path.resolve(path, file) + const stats = fs.statSync(filePath) + if (stats.isDirectory()) emptyPath(filePath) + else fs.unlinkSync(filePath) + }) + fs.rmdirSync(path) + } - for (const path of pathList) { - emptyPath(path); - } + for (const path of pathList) { + emptyPath(path) + } } -function getCacheList(type: ChatType) { // NOTE: 做这个方法主要是因为目前还不支持针对频道消息的清理 - return new Promise>((res, rej) => { - NTQQFileCacheApi.getChatCacheList(type, 1000, 0) - .then(data => { - const list = data.infos.filter(e => e.chatType === type && parseInt(e.basicChatCacheInfo.chatSize) > 0); - const result = list.map(e => { - const result = { ...e.basicChatCacheInfo }; - result.chatType = type; - result.isChecked = true; - return result; - }); - res(result); - }) - .catch(e => rej(e)); - }); -} \ No newline at end of file +function getCacheList(type: ChatType) { + // NOTE: 做这个方法主要是因为目前还不支持针对频道消息的清理 + return new Promise>((res, rej) => { + NTQQFileCacheApi.getChatCacheList(type, 1000, 0) + .then((data) => { + const list = data.infos.filter((e) => e.chatType === type && parseInt(e.basicChatCacheInfo.chatSize) > 0) + const result = list.map((e) => { + const result = { ...e.basicChatCacheInfo } + result.chatType = type + result.isChecked = true + return result + }) + res(result) + }) + .catch((e) => rej(e)) + }) +} diff --git a/src/onebot11/action/system/GetLoginInfo.ts b/src/onebot11/action/system/GetLoginInfo.ts index 3d8b5f1..a247a76 100644 --- a/src/onebot11/action/system/GetLoginInfo.ts +++ b/src/onebot11/action/system/GetLoginInfo.ts @@ -1,16 +1,15 @@ -import {OB11User} from '../../types'; -import {OB11Constructor} from "../../constructor"; -import {selfInfo} from "../../../common/data"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; - +import { OB11User } from '../../types' +import { OB11Constructor } from '../../constructor' +import { selfInfo } from '../../../common/data' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' class GetLoginInfo extends BaseAction { - actionName = ActionName.GetLoginInfo + actionName = ActionName.GetLoginInfo - protected async _handle(payload: null) { - return OB11Constructor.selfInfo(selfInfo); - } + protected async _handle(payload: null) { + return OB11Constructor.selfInfo(selfInfo) + } } -export default GetLoginInfo \ No newline at end of file +export default GetLoginInfo diff --git a/src/onebot11/action/system/GetStatus.ts b/src/onebot11/action/system/GetStatus.ts index 3decd2b..dd7d77d 100644 --- a/src/onebot11/action/system/GetStatus.ts +++ b/src/onebot11/action/system/GetStatus.ts @@ -1,16 +1,15 @@ -import BaseAction from "../BaseAction"; -import {OB11Status} from "../../types"; -import {ActionName} from "../types"; -import {selfInfo} from "../../../common/data"; - +import BaseAction from '../BaseAction' +import { OB11Status } from '../../types' +import { ActionName } from '../types' +import { selfInfo } from '../../../common/data' export default class GetStatus extends BaseAction { - actionName = ActionName.GetStatus + actionName = ActionName.GetStatus - protected async _handle(payload: any): Promise { - return { - online: selfInfo.online, - good: true - } + protected async _handle(payload: any): Promise { + return { + online: selfInfo.online, + good: true, } -} \ No newline at end of file + } +} diff --git a/src/onebot11/action/system/GetVersionInfo.ts b/src/onebot11/action/system/GetVersionInfo.ts index 9dfab55..88754a8 100644 --- a/src/onebot11/action/system/GetVersionInfo.ts +++ b/src/onebot11/action/system/GetVersionInfo.ts @@ -1,16 +1,16 @@ -import BaseAction from "../BaseAction"; -import {OB11Version} from "../../types"; -import {ActionName} from "../types"; -import {version} from "../../../version"; +import BaseAction from '../BaseAction' +import { OB11Version } from '../../types' +import { ActionName } from '../types' +import { version } from '../../../version' export default class GetVersionInfo extends BaseAction { - actionName = ActionName.GetVersionInfo + actionName = ActionName.GetVersionInfo - protected async _handle(payload: any): Promise { - return { - app_name: "LLOneBot", - protocol_version: "v11", - app_version: version - } + protected async _handle(payload: any): Promise { + return { + app_name: 'LLOneBot', + protocol_version: 'v11', + app_version: version, } -} \ No newline at end of file + } +} diff --git a/src/onebot11/action/types.ts b/src/onebot11/action/types.ts index f301a54..039aa15 100644 --- a/src/onebot11/action/types.ts +++ b/src/onebot11/action/types.ts @@ -1,69 +1,69 @@ export type BaseCheckResult = ValidCheckResult | InvalidCheckResult export interface ValidCheckResult { - valid: true + valid: true - [k: string | number]: any + [k: string | number]: any } export interface InvalidCheckResult { - valid: false - message: string + valid: false + message: string - [k: string | number]: any + [k: string | number]: any } export enum ActionName { - // llonebot - GetGroupIgnoreAddRequest = "get_group_ignore_add_request", - SetQQAvatar = "set_qq_avatar", - GetConfig = "get_config", - SetConfig = "set_config", - Debug = "llonebot_debug", - GetFile = "get_file", - // onebot 11 - SendLike = "send_like", - GetLoginInfo = "get_login_info", - GetFriendList = "get_friend_list", - GetGroupInfo = "get_group_info", - GetGroupList = "get_group_list", - GetGroupMemberInfo = "get_group_member_info", - GetGroupMemberList = "get_group_member_list", - GetMsg = "get_msg", - SendMsg = "send_msg", - SendGroupMsg = "send_group_msg", - SendPrivateMsg = "send_private_msg", - DeleteMsg = "delete_msg", - SetMsgEmojiLike = "set_msg_emoji_like", - SetGroupAddRequest = "set_group_add_request", - SetFriendAddRequest = "set_friend_add_request", - SetGroupLeave = "set_group_leave", - GetVersionInfo = "get_version_info", - GetStatus = "get_status", - CanSendRecord = "can_send_record", - CanSendImage = "can_send_image", - SetGroupKick = "set_group_kick", - SetGroupBan = "set_group_ban", - SetGroupWholeBan = "set_group_whole_ban", - SetGroupAdmin = "set_group_admin", - SetGroupCard = "set_group_card", - SetGroupName = "set_group_name", - GetImage = "get_image", - GetRecord = "get_record", - CleanCache = "clean_cache", - GetCookies = "get_cookies", - ForwardFriendSingleMsg = "forward_friend_single_msg", - ForwardGroupSingleMsg = "forward_group_single_msg", - // 以下为go-cqhttp api - GoCQHTTP_SendForwardMsg = "send_forward_msg", - GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg", - GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg", - GoCQHTTP_GetStrangerInfo = "get_stranger_info", - GetGuildList = "get_guild_list", - GoCQHTTP_MarkMsgAsRead = "mark_msg_as_read", - GoCQHTTP_UploadGroupFile = "upload_group_file", - GoCQHTTP_UploadPrivateFile = "upload_private_file", - GoCQHTTP_DownloadFile = "download_file", - GoCQHTTP_GetGroupMsgHistory = "get_group_msg_history", - GoCQHTTP_GetForwardMsg = "get_forward_msg", -} \ No newline at end of file + // llonebot + GetGroupIgnoreAddRequest = 'get_group_ignore_add_request', + SetQQAvatar = 'set_qq_avatar', + GetConfig = 'get_config', + SetConfig = 'set_config', + Debug = 'llonebot_debug', + GetFile = 'get_file', + // onebot 11 + SendLike = 'send_like', + GetLoginInfo = 'get_login_info', + GetFriendList = 'get_friend_list', + GetGroupInfo = 'get_group_info', + GetGroupList = 'get_group_list', + GetGroupMemberInfo = 'get_group_member_info', + GetGroupMemberList = 'get_group_member_list', + GetMsg = 'get_msg', + SendMsg = 'send_msg', + SendGroupMsg = 'send_group_msg', + SendPrivateMsg = 'send_private_msg', + DeleteMsg = 'delete_msg', + SetMsgEmojiLike = 'set_msg_emoji_like', + SetGroupAddRequest = 'set_group_add_request', + SetFriendAddRequest = 'set_friend_add_request', + SetGroupLeave = 'set_group_leave', + GetVersionInfo = 'get_version_info', + GetStatus = 'get_status', + CanSendRecord = 'can_send_record', + CanSendImage = 'can_send_image', + SetGroupKick = 'set_group_kick', + SetGroupBan = 'set_group_ban', + SetGroupWholeBan = 'set_group_whole_ban', + SetGroupAdmin = 'set_group_admin', + SetGroupCard = 'set_group_card', + SetGroupName = 'set_group_name', + GetImage = 'get_image', + GetRecord = 'get_record', + CleanCache = 'clean_cache', + GetCookies = 'get_cookies', + ForwardFriendSingleMsg = 'forward_friend_single_msg', + ForwardGroupSingleMsg = 'forward_group_single_msg', + // 以下为go-cqhttp api + GoCQHTTP_SendForwardMsg = 'send_forward_msg', + GoCQHTTP_SendGroupForwardMsg = 'send_group_forward_msg', + GoCQHTTP_SendPrivateForwardMsg = 'send_private_forward_msg', + GoCQHTTP_GetStrangerInfo = 'get_stranger_info', + GetGuildList = 'get_guild_list', + GoCQHTTP_MarkMsgAsRead = 'mark_msg_as_read', + GoCQHTTP_UploadGroupFile = 'upload_group_file', + GoCQHTTP_UploadPrivateFile = 'upload_private_file', + GoCQHTTP_DownloadFile = 'download_file', + GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history', + GoCQHTTP_GetForwardMsg = 'get_forward_msg', +} diff --git a/src/onebot11/action/user/GetCookie.ts b/src/onebot11/action/user/GetCookie.ts index 9383543..6f060eb 100644 --- a/src/onebot11/action/user/GetCookie.ts +++ b/src/onebot11/action/user/GetCookie.ts @@ -1,12 +1,12 @@ -import BaseAction from "../BaseAction"; -import {NTQQUserApi} from "../../../ntqqapi/api"; -import {groups} from "../../../common/data"; -import {ActionName} from "../types"; +import BaseAction from '../BaseAction' +import { NTQQUserApi } from '../../../ntqqapi/api' +import { groups } from '../../../common/data' +import { ActionName } from '../types' -export class GetCookies extends BaseAction{ - actionName = ActionName.GetCookies; +export class GetCookies extends BaseAction { + actionName = ActionName.GetCookies - protected async _handle() { - return NTQQUserApi.getCookie(groups[0]) - } -} \ No newline at end of file + protected async _handle() { + return NTQQUserApi.getCookie(groups[0]) + } +} diff --git a/src/onebot11/action/user/GetFriendList.ts b/src/onebot11/action/user/GetFriendList.ts index 3680231..19b0b9c 100644 --- a/src/onebot11/action/user/GetFriendList.ts +++ b/src/onebot11/action/user/GetFriendList.ts @@ -1,16 +1,15 @@ -import {OB11User} from '../../types'; -import {OB11Constructor} from "../../constructor"; -import {friends} from "../../../common/data"; -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; - +import { OB11User } from '../../types' +import { OB11Constructor } from '../../constructor' +import { friends } from '../../../common/data' +import BaseAction from '../BaseAction' +import { ActionName } from '../types' class GetFriendList extends BaseAction { - actionName = ActionName.GetFriendList + actionName = ActionName.GetFriendList - protected async _handle(payload: null) { - return OB11Constructor.friends(friends); - } + protected async _handle(payload: null) { + return OB11Constructor.friends(friends) + } } -export default GetFriendList \ No newline at end of file +export default GetFriendList diff --git a/src/onebot11/action/user/SendLike.ts b/src/onebot11/action/user/SendLike.ts index 217f397..9bfa2a4 100644 --- a/src/onebot11/action/user/SendLike.ts +++ b/src/onebot11/action/user/SendLike.ts @@ -1,35 +1,35 @@ -import BaseAction from "../BaseAction"; -import {getFriend, getUidByUin, uidMaps} from "../../../common/data"; -import {ActionName} from "../types"; -import {NTQQFriendApi} from "../../../ntqqapi/api/friend"; -import {log} from "../../../common/utils/log"; +import BaseAction from '../BaseAction' +import { getFriend, getUidByUin, uidMaps } from '../../../common/data' +import { ActionName } from '../types' +import { NTQQFriendApi } from '../../../ntqqapi/api/friend' +import { log } from '../../../common/utils/log' interface Payload { - user_id: number, - times: number + user_id: number + times: number } export default class SendLike extends BaseAction { - actionName = ActionName.SendLike + actionName = ActionName.SendLike - protected async _handle(payload: Payload): Promise { - log("点赞参数", payload) - try { - const qq = payload.user_id.toString(); - const friend = await getFriend(qq) - let uid: string; - if (!friend) { - uid = getUidByUin(qq) - } else { - uid = friend.uid - } - let result = await NTQQFriendApi.likeFriend(uid, parseInt(payload.times?.toString()) || 1); - if (result.result !== 0) { - throw result.errMsg - } - } catch (e) { - throw `点赞失败 ${e}` - } - return null + protected async _handle(payload: Payload): Promise { + log('点赞参数', payload) + try { + const qq = payload.user_id.toString() + const friend = await getFriend(qq) + let uid: string + if (!friend) { + uid = getUidByUin(qq) + } else { + uid = friend.uid + } + let result = await NTQQFriendApi.likeFriend(uid, parseInt(payload.times?.toString()) || 1) + if (result.result !== 0) { + throw result.errMsg + } + } catch (e) { + throw `点赞失败 ${e}` } -} \ No newline at end of file + return null + } +} diff --git a/src/onebot11/action/user/SetFriendAddRequest.ts b/src/onebot11/action/user/SetFriendAddRequest.ts index 383ffce..2905fd7 100644 --- a/src/onebot11/action/user/SetFriendAddRequest.ts +++ b/src/onebot11/action/user/SetFriendAddRequest.ts @@ -1,19 +1,19 @@ -import BaseAction from "../BaseAction"; -import {ActionName} from "../types"; -import {NTQQFriendApi} from "../../../ntqqapi/api/friend"; +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQFriendApi } from '../../../ntqqapi/api/friend' interface Payload { - flag: string, - approve: boolean, - remark?: string, + flag: string + approve: boolean + remark?: string } export default class SetFriendAddRequest extends BaseAction { - actionName = ActionName.SetFriendAddRequest; + actionName = ActionName.SetFriendAddRequest - protected async _handle(payload: Payload): Promise { - const approve = payload.approve.toString() === "true"; - await NTQQFriendApi.handleFriendRequest(payload.flag, approve) - return null; - } -} \ No newline at end of file + protected async _handle(payload: Payload): Promise { + const approve = payload.approve.toString() === 'true' + await NTQQFriendApi.handleFriendRequest(payload.flag, approve) + return null + } +} diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index 3cc88bc..506e9f6 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -1,4 +1,4 @@ -import fastXmlParser, {XMLParser} from 'fast-xml-parser'; +import fastXmlParser, { XMLParser } from 'fast-xml-parser' import { OB11Group, OB11GroupMember, @@ -7,84 +7,89 @@ import { OB11MessageData, OB11MessageDataType, OB11User, - OB11UserSex -} from "./types"; + OB11UserSex, +} from './types' import { AtType, - ChatType, FaceIndex, + ChatType, + FaceIndex, GrayTipElementSubType, Group, GroupMember, - IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, + IMAGE_HTTP_HOST, + IMAGE_HTTP_HOST_NT, RawMessage, SelfInfo, Sex, TipGroupElementType, User, - VideoElement -} from '../ntqqapi/types'; -import {deleteGroup, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap} from '../common/data'; -import {EventType} from "./event/OB11BaseEvent"; -import {encodeCQCode} from "./cqcode"; -import {dbUtil} from "../common/db"; -import {OB11GroupIncreaseEvent} from "./event/notice/OB11GroupIncreaseEvent"; -import {OB11GroupBanEvent} from "./event/notice/OB11GroupBanEvent"; -import {OB11GroupUploadNoticeEvent} from "./event/notice/OB11GroupUploadNoticeEvent"; -import {OB11GroupNoticeEvent} from "./event/notice/OB11GroupNoticeEvent"; -import {NTQQUserApi} from "../ntqqapi/api/user"; -import {NTQQFileApi} from "../ntqqapi/api/file"; -import {calcQQLevel} from "../common/utils/qqlevel"; -import {log} from "../common/utils/log"; -import {sleep} from "../common/utils/helper"; -import {getConfigUtil} from "../common/config"; -import {OB11GroupTitleEvent} from "./event/notice/OB11GroupTitleEvent"; -import {OB11GroupCardEvent} from "./event/notice/OB11GroupCardEvent"; -import {OB11GroupDecreaseEvent} from "./event/notice/OB11GroupDecreaseEvent"; -import {NTQQGroupApi} from "../ntqqapi/api"; -import {OB11GroupMsgEmojiLikeEvent} from "./event/notice/OB11MsgEmojiLikeEvent"; + VideoElement, +} from '../ntqqapi/types' +import { deleteGroup, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap } from '../common/data' +import { EventType } from './event/OB11BaseEvent' +import { encodeCQCode } from './cqcode' +import { dbUtil } from '../common/db' +import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent' +import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent' +import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent' +import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent' +import { NTQQUserApi } from '../ntqqapi/api/user' +import { NTQQFileApi } from '../ntqqapi/api/file' +import { calcQQLevel } from '../common/utils/qqlevel' +import { log } from '../common/utils/log' +import { sleep } from '../common/utils/helper' +import { getConfigUtil } from '../common/config' +import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent' +import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent' +import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent' +import { NTQQGroupApi } from '../ntqqapi/api' +import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent' -let lastRKeyUpdateTime = 0; +let lastRKeyUpdateTime = 0 export class OB11Constructor { static async message(msg: RawMessage): Promise { - let config = getConfigUtil().getConfig(); - const {enableLocalFile2Url, ob11: {messagePostFormat}} = config; - const message_type = msg.chatType == ChatType.group ? "group" : "private"; + let config = getConfigUtil().getConfig() + const { + enableLocalFile2Url, + ob11: { messagePostFormat }, + } = config + const message_type = msg.chatType == ChatType.group ? 'group' : 'private' const resMsg: OB11Message = { self_id: parseInt(selfInfo.uin), user_id: parseInt(msg.senderUin), time: parseInt(msg.msgTime) || Date.now(), message_id: msg.msgShortId, real_id: msg.msgShortId, - message_type: msg.chatType == ChatType.group ? "group" : "private", + message_type: msg.chatType == ChatType.group ? 'group' : 'private', sender: { user_id: parseInt(msg.senderUin), nickname: msg.sendNickName, - card: msg.sendMemberName || "", + card: msg.sendMemberName || '', }, - raw_message: "", + raw_message: '', font: 14, - sub_type: "friend", + sub_type: 'friend', message: messagePostFormat === 'string' ? '' : [], message_format: messagePostFormat === 'string' ? 'string' : 'array', post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, } if (msg.chatType == ChatType.group) { - resMsg.sub_type = "normal" // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼 + resMsg.sub_type = 'normal' // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼 resMsg.group_id = parseInt(msg.peerUin) - const member = await getGroupMember(msg.peerUin, msg.senderUin); + const member = await getGroupMember(msg.peerUin, msg.senderUin) if (member) { - resMsg.sender.role = OB11Constructor.groupMemberRole(member.role); + resMsg.sender.role = OB11Constructor.groupMemberRole(member.role) resMsg.sender.nickname = member.nick } } else if (msg.chatType == ChatType.friend) { - resMsg.sub_type = "friend" - const friend = await getFriend(msg.senderUin); + resMsg.sub_type = 'friend' + const friend = await getFriend(msg.senderUin) if (friend) { - resMsg.sender.nickname = friend.nick; + resMsg.sender.nickname = friend.nick } } else if (msg.chatType == ChatType.temp) { - resMsg.sub_type = "group" + resMsg.sub_type = 'group' const tempGroupCode = tempGroupCodeMap[msg.peerUin] if (tempGroupCode) { resMsg.group_id = parseInt(tempGroupCode) @@ -94,17 +99,17 @@ export class OB11Constructor { for (let element of msg.elements) { let message_data: OB11MessageData | any = { data: {}, - type: "unknown" + type: 'unknown', } if (element.textElement && element.textElement?.atType !== AtType.notAt) { - message_data["type"] = OB11MessageDataType.at + message_data['type'] = OB11MessageDataType.at if (element.textElement.atType == AtType.atAll) { // message_data["data"]["mention"] = "all" - message_data["data"]["qq"] = "all" + message_data['data']['qq'] = 'all' } else { let atUid = element.textElement.atNtUid let atQQ = element.textElement.atUid - if (!atQQ || atQQ === "0") { + if (!atQQ || atQQ === '0') { const atMember = await getGroupMember(msg.peerUin, atUid) if (atMember) { atQQ = atMember.uin @@ -112,86 +117,101 @@ export class OB11Constructor { } if (atQQ) { // message_data["data"]["mention"] = atQQ - message_data["data"]["qq"] = atQQ + message_data['data']['qq'] = atQQ } } } else if (element.textElement) { - message_data["type"] = "text" + message_data['type'] = 'text' let text = element.textElement.content if (!text.trim()) { - continue; + continue } - message_data["data"]["text"] = text + message_data['data']['text'] = text } else if (element.replyElement) { - message_data["type"] = "reply" + message_data['type'] = 'reply' // log("收到回复消息", element.replyElement.replayMsgSeq) try { const replyMsg = await dbUtil.getMsgBySeqId(element.replyElement.replayMsgSeq) // log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId) if (replyMsg) { - message_data["data"]["id"] = replyMsg.msgShortId.toString() + message_data['data']['id'] = replyMsg.msgShortId.toString() } else { continue } } catch (e) { - log("获取不到引用的消息", e.stack, element.replyElement.replayMsgSeq) + log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq) } - } else if (element.picElement) { - message_data["type"] = "image" + message_data['type'] = 'image' // message_data["data"]["file"] = element.picElement.sourcePath - message_data["data"]["file"] = element.picElement.fileName + message_data['data']['file'] = element.picElement.fileName // message_data["data"]["path"] = element.picElement.sourcePath // let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64" - message_data["data"]["url"] = await NTQQFileApi.getImageUrl(msg); + message_data['data']['url'] = await NTQQFileApi.getImageUrl(msg) // message_data["data"]["file_id"] = element.picElement.fileUuid - message_data["data"]["file_size"] = element.picElement.fileSize - dbUtil.addFileCache(element.picElement.fileName, { - fileName: element.picElement.fileName, - filePath: element.picElement.sourcePath, - fileSize: element.picElement.fileSize.toString(), - url: message_data["data"]["url"], - downloadFunc: async () => { - await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, - element.elementId, element.picElement.thumbPath?.get(0) || "", element.picElement.sourcePath) - } - }).then() + message_data['data']['file_size'] = element.picElement.fileSize + dbUtil + .addFileCache(element.picElement.fileName, { + fileName: element.picElement.fileName, + filePath: element.picElement.sourcePath, + fileSize: element.picElement.fileSize.toString(), + url: message_data['data']['url'], + downloadFunc: async () => { + await NTQQFileApi.downloadMedia( + msg.msgId, + msg.chatType, + msg.peerUid, + element.elementId, + element.picElement.thumbPath?.get(0) || '', + element.picElement.sourcePath, + ) + }, + }) + .then() // 不在自动下载图片 - } else if (element.videoElement || element.fileElement) { const videoOrFileElement = element.videoElement || element.fileElement const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file - message_data["type"] = ob11MessageDataType; - message_data["data"]["file"] = videoOrFileElement.fileName - message_data["data"]["path"] = videoOrFileElement.filePath - message_data["data"]["file_id"] = videoOrFileElement.fileUuid - message_data["data"]["file_size"] = videoOrFileElement.fileSize - dbUtil.addFileCache(videoOrFileElement.fileUuid, { - msgId: msg.msgId, - fileName: videoOrFileElement.fileName, - filePath: videoOrFileElement.filePath, - fileSize: videoOrFileElement.fileSize, - downloadFunc: async () => { - await NTQQFileApi.downloadMedia( - msg.msgId, msg.chatType, msg.peerUid, - element.elementId, - ob11MessageDataType == OB11MessageDataType.video ? (videoOrFileElement as VideoElement).thumbPath.get(0) : null, - videoOrFileElement.filePath) - } - }).then() + message_data['type'] = ob11MessageDataType + message_data['data']['file'] = videoOrFileElement.fileName + message_data['data']['path'] = videoOrFileElement.filePath + message_data['data']['file_id'] = videoOrFileElement.fileUuid + message_data['data']['file_size'] = videoOrFileElement.fileSize + dbUtil + .addFileCache(videoOrFileElement.fileUuid, { + msgId: msg.msgId, + fileName: videoOrFileElement.fileName, + filePath: videoOrFileElement.filePath, + fileSize: videoOrFileElement.fileSize, + downloadFunc: async () => { + await NTQQFileApi.downloadMedia( + msg.msgId, + msg.chatType, + msg.peerUid, + element.elementId, + ob11MessageDataType == OB11MessageDataType.video + ? (videoOrFileElement as VideoElement).thumbPath.get(0) + : null, + videoOrFileElement.filePath, + ) + }, + }) + .then() // 怎么拿到url呢 } else if (element.pttElement) { - message_data["type"] = OB11MessageDataType.voice; - message_data["data"]["file"] = element.pttElement.fileName - message_data["data"]["path"] = element.pttElement.filePath + message_data['type'] = OB11MessageDataType.voice + message_data['data']['file'] = element.pttElement.fileName + message_data['data']['path'] = element.pttElement.filePath // message_data["data"]["file_id"] = element.pttElement.fileUuid - message_data["data"]["file_size"] = element.pttElement.fileSize - dbUtil.addFileCache(element.pttElement.fileName, { - fileName: element.pttElement.fileName, - filePath: element.pttElement.filePath, - fileSize: element.pttElement.fileSize, - }).then() + message_data['data']['file_size'] = element.pttElement.fileSize + dbUtil + .addFileCache(element.pttElement.fileName, { + fileName: element.pttElement.fileName, + filePath: element.pttElement.filePath, + fileSize: element.pttElement.fileSize, + }) + .then() // log("收到语音消息", msg) // window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => { @@ -200,63 +220,67 @@ export class OB11Constructor { // console.log("语音转文字失败", err); // }) } else if (element.arkElement) { - message_data["type"] = OB11MessageDataType.json; - message_data["data"]["data"] = element.arkElement.bytesData; + message_data['type'] = OB11MessageDataType.json + message_data['data']['data'] = element.arkElement.bytesData } else if (element.faceElement) { - const faceId = element.faceElement.faceIndex; + const faceId = element.faceElement.faceIndex if (faceId === FaceIndex.dice) { - message_data["type"] = OB11MessageDataType.dice - message_data["data"]["result"] = element.faceElement.resultId; + message_data['type'] = OB11MessageDataType.dice + message_data['data']['result'] = element.faceElement.resultId } else if (faceId === FaceIndex.RPS) { - message_data["type"] = OB11MessageDataType.RPS - message_data["data"]["result"] = element.faceElement.resultId; + message_data['type'] = OB11MessageDataType.RPS + message_data['data']['result'] = element.faceElement.resultId } else { - message_data["type"] = OB11MessageDataType.face; - message_data["data"]["id"] = element.faceElement.faceIndex.toString(); + message_data['type'] = OB11MessageDataType.face + message_data['data']['id'] = element.faceElement.faceIndex.toString() } } else if (element.marketFaceElement) { - message_data["type"] = OB11MessageDataType.mface; - message_data["data"]["text"] = element.marketFaceElement.faceName; - const md5 = element.marketFaceElement.emojiId; + message_data['type'] = OB11MessageDataType.mface + message_data['data']['text'] = element.marketFaceElement.faceName + const md5 = element.marketFaceElement.emojiId // 取md5的前两位 - const dir = md5.substring(0, 2); + const dir = md5.substring(0, 2) // 获取组装url // const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000` - const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${md5}/raw300.gif`; - message_data["data"]["url"] = url; - message_data["data"]["emoji_id"] = element.marketFaceElement.emojiId - message_data["data"]["emoji_package_id"] = String(element.marketFaceElement.emojiPackageId) - message_data["data"]["key"] = element.marketFaceElement.key - + const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${md5}/raw300.gif` + message_data['data']['url'] = url + message_data['data']['emoji_id'] = element.marketFaceElement.emojiId + message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId) + message_data['data']['key'] = element.marketFaceElement.key } else if (element.markdownElement) { - message_data["type"] = OB11MessageDataType.markdown; - message_data["data"]["data"] = element.markdownElement.content; + message_data['type'] = OB11MessageDataType.markdown + message_data['data']['data'] = element.markdownElement.content } else if (element.multiForwardMsgElement) { - message_data["type"] = OB11MessageDataType.forward; - message_data["data"]["id"] = msg.msgId + message_data['type'] = OB11MessageDataType.forward + message_data['data']['id'] = msg.msgId } - if (message_data.type !== "unknown" && message_data.data) { - const cqCode = encodeCQCode(message_data); + if (message_data.type !== 'unknown' && message_data.data) { + const cqCode = encodeCQCode(message_data) if (messagePostFormat === 'string') { - (resMsg.message as string) += cqCode; - } else (resMsg.message as OB11MessageData[]).push(message_data); + ;(resMsg.message as string) += cqCode + } else (resMsg.message as OB11MessageData[]).push(message_data) - resMsg.raw_message += cqCode; + resMsg.raw_message += cqCode } } - resMsg.raw_message = resMsg.raw_message.trim(); - return resMsg; + resMsg.raw_message = resMsg.raw_message.trim() + return resMsg } static async GroupEvent(msg: RawMessage): Promise { if (msg.chatType !== ChatType.group) { - return; + return } if (msg.senderUin) { - let member = await getGroupMember(msg.peerUid, msg.senderUin); + let member = await getGroupMember(msg.peerUid, msg.senderUin) if (member && member.cardName !== msg.sendMemberName) { - const event = new OB11GroupCardEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), msg.sendMemberName, member.cardName) - member.cardName = msg.sendMemberName; + const event = new OB11GroupCardEvent( + parseInt(msg.peerUid), + parseInt(msg.senderUin), + msg.sendMemberName, + member.cardName, + ) + member.cardName = msg.sendMemberName return event } } @@ -267,52 +291,68 @@ export class OB11Constructor { if (groupElement) { // log("收到群提示消息", groupElement) if (groupElement.type == TipGroupElementType.memberIncrease) { - log("收到群成员增加消息", groupElement) - await sleep(1000); - const member = await getGroupMember(msg.peerUid, groupElement.memberUid); - let memberUin = member?.uin; + log('收到群成员增加消息', groupElement) + await sleep(1000) + const member = await getGroupMember(msg.peerUid, groupElement.memberUid) + let memberUin = member?.uin if (!memberUin) { memberUin = (await NTQQUserApi.getUserDetailInfo(groupElement.memberUid)).uin } // log("获取新群成员QQ", memberUin) - const adminMember = await getGroupMember(msg.peerUid, groupElement.adminUid); + const adminMember = await getGroupMember(msg.peerUid, groupElement.adminUid) // log("获取同意新成员入群的管理员", adminMember) if (memberUin) { const operatorUin = adminMember?.uin || memberUin - let event = new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin)); + let event = new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin)) // log("构造群增加事件", event) - return event; + return event } } else if (groupElement.type === TipGroupElementType.ban) { - log("收到群群员禁言提示", groupElement) + log('收到群群员禁言提示', groupElement) const memberUid = groupElement.shutUp.member.uid const adminUid = groupElement.shutUp.admin.uid - let memberUin: string = "" + let memberUin: string = '' let duration = parseInt(groupElement.shutUp.duration) - let sub_type: "ban" | "lift_ban" = duration > 0 ? "ban" : "lift_ban" + let sub_type: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban' if (memberUid) { - memberUin = (await getGroupMember(msg.peerUid, memberUid))?.uin || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin + memberUin = + (await getGroupMember(msg.peerUid, memberUid))?.uin || + (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin } else { - memberUin = "0"; // 0表示全员禁言 + memberUin = '0' // 0表示全员禁言 if (duration > 0) { duration = -1 } } - const adminUin = (await getGroupMember(msg.peerUid, adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin + const adminUin = + (await getGroupMember(msg.peerUid, adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin if (memberUin && adminUin) { - return new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type); + return new OB11GroupBanEvent( + parseInt(msg.peerUid), + parseInt(memberUin), + parseInt(adminUin), + duration, + sub_type, + ) } } else if (groupElement.type == TipGroupElementType.kicked) { log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement) - deleteGroup(msg.peerUid); + deleteGroup(msg.peerUid) NTQQGroupApi.quitGroup(msg.peerUid).then() try { - const adminUin = (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(groupElement.adminUid))?.uin + const adminUin = + (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || + (await NTQQUserApi.getUserDetailInfo(groupElement.adminUid))?.uin if (adminUin) { - return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), parseInt(adminUin), "kick_me"); + return new OB11GroupDecreaseEvent( + parseInt(msg.peerUid), + parseInt(selfInfo.uin), + parseInt(adminUin), + 'kick_me', + ) } } catch (e) { - return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, "leave"); + return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave') } } } else if (element.fileElement) { @@ -320,14 +360,14 @@ export class OB11Constructor { id: element.fileElement.fileUuid, name: element.fileElement.fileName, size: parseInt(element.fileElement.fileSize), - busid: element.fileElement.fileBizId || 0 + busid: element.fileElement.fileBizId || 0, }) } if (grayTipElement) { const xmlElement = grayTipElement.xmlElement - if (xmlElement?.templId === "10382") { + if (xmlElement?.templId === '10382') { // 表情回应消息 // "content": // " @@ -338,42 +378,46 @@ export class OB11Constructor { // ", const emojiLikeData = new fastXmlParser.XMLParser({ ignoreAttributes: false, - attributeNamePrefix: "" + attributeNamePrefix: '', }).parse(xmlElement.content) - log("收到表情回应我的消息", emojiLikeData) + log('收到表情回应我的消息', emojiLikeData) try { - const senderUin = emojiLikeData.gtip.qq.jp; - const msgSeq = emojiLikeData.gtip.url.msgseq; - const emojiId = emojiLikeData.gtip.face.id; - const msg = await dbUtil.getMsgBySeqId(msgSeq); + const senderUin = emojiLikeData.gtip.qq.jp + const msgSeq = emojiLikeData.gtip.url.msgseq + const emojiId = emojiLikeData.gtip.face.id + const msg = await dbUtil.getMsgBySeqId(msgSeq) if (!msg) { - return; + return } - return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), msg.msgShortId, [{ - emoji_id: emojiId, - count: 1 - }]); + return new OB11GroupMsgEmojiLikeEvent(parseInt(msg.peerUid), parseInt(senderUin), msg.msgShortId, [ + { + emoji_id: emojiId, + count: 1, + }, + ]) } catch (e) { - log("解析表情回应消息失败", e.stack); + log('解析表情回应消息失败', e.stack) } } - if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER - && xmlElement?.templId == "10179") { - log("收到新人被邀请进群消息", grayTipElement) + if ( + grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER && + xmlElement?.templId == '10179' + ) { + log('收到新人被邀请进群消息', grayTipElement) if (xmlElement?.content) { - const regex = /jp="(\d+)"/g; + const regex = /jp="(\d+)"/g - let matches = []; + let matches = [] let match = null while ((match = regex.exec(xmlElement.content)) !== null) { - matches.push(match[1]); + matches.push(match[1]) } // log("新人进群匹配到的QQ号", matches) if (matches.length === 2) { - const [inviter, invitee] = matches; - return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), "invite"); + const [inviter, invitee] = matches + return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite') } } } else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) { @@ -404,8 +448,8 @@ export class OB11Constructor { * */ const memberUin = json.items[1].param[0] const title = json.items[3].txt - log("收到群成员新头衔消息", json) - getGroupMember(msg.peerUid, memberUin).then(member => { + log('收到群成员新头衔消息', json) + getGroupMember(msg.peerUid, memberUin).then((member) => { member.memberSpecialTitle = title }) return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title) @@ -420,7 +464,7 @@ export class OB11Constructor { nickname: friend.nick, remark: friend.remark, sex: OB11Constructor.sex(friend.sex), - level: friend.qqLevel && calcQQLevel(friend.qqLevel) || 0 + level: (friend.qqLevel && calcQQLevel(friend.qqLevel)) || 0, } } @@ -439,7 +483,7 @@ export class OB11Constructor { return { 4: OB11GroupMemberRole.owner, 3: OB11GroupMemberRole.admin, - 2: OB11GroupMemberRole.member + 2: OB11GroupMemberRole.member, }[role] } @@ -447,7 +491,7 @@ export class OB11Constructor { const sexMap = { [Sex.male]: OB11UserSex.male, [Sex.female]: OB11UserSex.female, - [Sex.unknown]: OB11UserSex.unknown + [Sex.unknown]: OB11UserSex.unknown, } return sexMap[sex] || OB11UserSex.unknown } @@ -460,18 +504,18 @@ export class OB11Constructor { card: member.cardName, sex: OB11Constructor.sex(member.sex), age: 0, - area: "", + area: '', level: 0, - qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0, - join_time: 0, // 暂时没法获取 - last_sent_time: 0, // 暂时没法获取 + qq_level: (member.qqLevel && calcQQLevel(member.qqLevel)) || 0, + join_time: 0, // 暂时没法获取 + last_sent_time: 0, // 暂时没法获取 title_expire_time: 0, unfriendly: false, card_changeable: true, is_robot: member.isRobot, shut_up_timestamp: member.shutUpTime, role: OB11Constructor.groupMemberRole(member.role), - title: member.memberSpecialTitle || "", + title: member.memberSpecialTitle || '', } } @@ -484,13 +528,13 @@ export class OB11Constructor { age: 0, qid: user.qid, login_days: 0, - level: user.qqLevel && calcQQLevel(user.qqLevel) || 0, + level: (user.qqLevel && calcQQLevel(user.qqLevel)) || 0, } } static groupMembers(group: Group): OB11GroupMember[] { - log("construct ob11 group members", group) - return group.members.map(m => OB11Constructor.groupMember(group.groupCode, m)) + log('construct ob11 group members', group) + return group.members.map((m) => OB11Constructor.groupMember(group.groupCode, m)) } static group(group: Group): OB11Group { @@ -498,7 +542,7 @@ export class OB11Constructor { group_id: parseInt(group.groupCode), group_name: group.groupName, member_count: group.memberCount, - max_member_count: group.maxMember + max_member_count: group.maxMember, } } diff --git a/src/onebot11/cqcode.ts b/src/onebot11/cqcode.ts index d343925..83ebdbf 100644 --- a/src/onebot11/cqcode.ts +++ b/src/onebot11/cqcode.ts @@ -1,76 +1,69 @@ -import {OB11MessageData} from "./types"; +import { OB11MessageData } from './types' const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)\]/ function unescape(source: string) { - return String(source) - .replace(/[/g, '[') - .replace(/]/g, ']') - .replace(/,/g, ',') - .replace(/&/g, '&') + return String(source).replace(/[/g, '[').replace(/]/g, ']').replace(/,/g, ',').replace(/&/g, '&') } function from(source: string) { - const capture = pattern.exec(source) - if (!capture) return null - const [, type, attrs] = capture - const data: Record = {} - attrs && attrs.slice(1).split(',').forEach((str) => { + const capture = pattern.exec(source) + if (!capture) return null + const [, type, attrs] = capture + const data: Record = {} + attrs && + attrs + .slice(1) + .split(',') + .forEach((str) => { const index = str.indexOf('=') data[str.slice(0, index)] = unescape(str.slice(index + 1)) - }) - return {type, data, capture} + }) + return { type, data, capture } } function h(type: string, data: any) { - return { - type, - data, - } + return { + type, + data, + } } export function decodeCQCode(source: string): OB11MessageData[] { - const elements: any[] = [] - let result: ReturnType - while ((result = from(source))) { - const {type, data, capture} = result - if (capture.index) { - elements.push(h('text', {text: unescape(source.slice(0, capture.index))})) - } - elements.push(h(type, data)) - source = source.slice(capture.index + capture[0].length) + const elements: any[] = [] + let result: ReturnType + while ((result = from(source))) { + const { type, data, capture } = result + if (capture.index) { + elements.push(h('text', { text: unescape(source.slice(0, capture.index)) })) } - if (source) elements.push(h('text', {text: unescape(source)})) - return elements + elements.push(h(type, data)) + source = source.slice(capture.index + capture[0].length) + } + if (source) elements.push(h('text', { text: unescape(source) })) + return elements } - export function encodeCQCode(data: OB11MessageData) { - const CQCodeEscapeText = (text: string) => { - return text.replace(/\&/g, '&') - .replace(/\[/g, '[') - .replace(/\]/g, ']') - - }; + const CQCodeEscapeText = (text: string) => { + return text.replace(/\&/g, '&').replace(/\[/g, '[').replace(/\]/g, ']') + } - const CQCodeEscape = (text: string) => { - return text.replace(/\&/g, '&') - .replace(/\[/g, '[') - .replace(/\]/g, ']') - .replace(/,/g, ','); - }; + const CQCodeEscape = (text: string) => { + return text.replace(/\&/g, '&').replace(/\[/g, '[').replace(/\]/g, ']').replace(/,/g, ',') + } - if (data.type === 'text') { - return CQCodeEscapeText(data.data.text); - } + if (data.type === 'text') { + return CQCodeEscapeText(data.data.text) + } - let result = '[CQ:' + data.type; - for (const name in data.data) { - const value = data.data[name]; - result += `,${name}=${CQCodeEscape(value)}`; - } - result += ']'; - return result; + let result = '[CQ:' + data.type + for (const name in data.data) { + const value = data.data[name] + result += `,${name}=${CQCodeEscape(value)}` + } + result += ']' + return result } // const result = parseCQCode("[CQ:at,qq=114514]早上好啊[CQ:image,file=http://baidu.com/1.jpg,type=show,id=40004]") diff --git a/src/onebot11/event/OB11BaseEvent.ts b/src/onebot11/event/OB11BaseEvent.ts index a707930..27cb8bc 100644 --- a/src/onebot11/event/OB11BaseEvent.ts +++ b/src/onebot11/event/OB11BaseEvent.ts @@ -1,16 +1,15 @@ -import {selfInfo} from "../../common/data"; +import { selfInfo } from '../../common/data' export enum EventType { - META = "meta_event", - REQUEST = "request", - NOTICE = "notice", - MESSAGE = "message", - MESSAGE_SENT = "message_sent", + META = 'meta_event', + REQUEST = 'request', + NOTICE = 'notice', + MESSAGE = 'message', + MESSAGE_SENT = 'message_sent', } - export abstract class OB11BaseEvent { - time = Math.floor(Date.now() / 1000); - self_id = parseInt(selfInfo.uin); - post_type: EventType; -} \ No newline at end of file + time = Math.floor(Date.now() / 1000) + self_id = parseInt(selfInfo.uin) + post_type: EventType +} diff --git a/src/onebot11/event/message/OB11BaseMessageEvent.ts b/src/onebot11/event/message/OB11BaseMessageEvent.ts index 6018aeb..4b2693c 100644 --- a/src/onebot11/event/message/OB11BaseMessageEvent.ts +++ b/src/onebot11/event/message/OB11BaseMessageEvent.ts @@ -1,5 +1,5 @@ -import {EventType, OB11BaseEvent} from "../OB11BaseEvent"; +import { EventType, OB11BaseEvent } from '../OB11BaseEvent' export abstract class OB11BaseMessageEvent extends OB11BaseEvent { - post_type = EventType.MESSAGE; -} \ No newline at end of file + post_type = EventType.MESSAGE +} diff --git a/src/onebot11/event/meta/OB11BaseMetaEvent.ts b/src/onebot11/event/meta/OB11BaseMetaEvent.ts index d3946e6..c2460a9 100644 --- a/src/onebot11/event/meta/OB11BaseMetaEvent.ts +++ b/src/onebot11/event/meta/OB11BaseMetaEvent.ts @@ -1,6 +1,6 @@ -import {EventType, OB11BaseEvent} from "../OB11BaseEvent"; +import { EventType, OB11BaseEvent } from '../OB11BaseEvent' export abstract class OB11BaseMetaEvent extends OB11BaseEvent { - post_type = EventType.META; - meta_event_type: string; -} \ No newline at end of file + post_type = EventType.META + meta_event_type: string +} diff --git a/src/onebot11/event/meta/OB11HeartbeatEvent.ts b/src/onebot11/event/meta/OB11HeartbeatEvent.ts index 42fa024..dc2120c 100644 --- a/src/onebot11/event/meta/OB11HeartbeatEvent.ts +++ b/src/onebot11/event/meta/OB11HeartbeatEvent.ts @@ -1,21 +1,21 @@ -import {OB11BaseMetaEvent} from "./OB11BaseMetaEvent"; +import { OB11BaseMetaEvent } from './OB11BaseMetaEvent' interface HeartbeatStatus { - online: boolean | null, - good: boolean + online: boolean | null + good: boolean } export class OB11HeartbeatEvent extends OB11BaseMetaEvent { - meta_event_type = "heartbeat"; - status: HeartbeatStatus; - interval: number; + meta_event_type = 'heartbeat' + status: HeartbeatStatus + interval: number - public constructor(isOnline: boolean | null, isGood: boolean, interval: number) { - super(); - this.interval = interval; - this.status = { - online: isOnline, - good: isGood - } + public constructor(isOnline: boolean | null, isGood: boolean, interval: number) { + super() + this.interval = interval + this.status = { + online: isOnline, + good: isGood, } -} \ No newline at end of file + } +} diff --git a/src/onebot11/event/meta/OB11LifeCycleEvent.ts b/src/onebot11/event/meta/OB11LifeCycleEvent.ts index e9243ca..36aa3d1 100644 --- a/src/onebot11/event/meta/OB11LifeCycleEvent.ts +++ b/src/onebot11/event/meta/OB11LifeCycleEvent.ts @@ -1,17 +1,17 @@ -import {OB11BaseMetaEvent} from "./OB11BaseMetaEvent"; +import { OB11BaseMetaEvent } from './OB11BaseMetaEvent' export enum LifeCycleSubType { - ENABLE = "enable", - DISABLE = "disable", - CONNECT = "connect" + ENABLE = 'enable', + DISABLE = 'disable', + CONNECT = 'connect', } export class OB11LifeCycleEvent extends OB11BaseMetaEvent { - meta_event_type = "lifecycle"; - sub_type: LifeCycleSubType; + meta_event_type = 'lifecycle' + sub_type: LifeCycleSubType - public constructor(subType: LifeCycleSubType) { - super(); - this.sub_type = subType; - } -} \ No newline at end of file + public constructor(subType: LifeCycleSubType) { + super() + this.sub_type = subType + } +} diff --git a/src/onebot11/event/notice/OB11BaseNoticeEvent.ts b/src/onebot11/event/notice/OB11BaseNoticeEvent.ts index 9429d2c..04c15c8 100644 --- a/src/onebot11/event/notice/OB11BaseNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11BaseNoticeEvent.ts @@ -1,5 +1,5 @@ -import {EventType, OB11BaseEvent} from "../OB11BaseEvent"; +import { EventType, OB11BaseEvent } from '../OB11BaseEvent' export abstract class OB11BaseNoticeEvent extends OB11BaseEvent { - post_type = EventType.NOTICE; -} \ No newline at end of file + post_type = EventType.NOTICE +} diff --git a/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts index 9344b44..f2e8187 100644 --- a/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts @@ -1,13 +1,13 @@ -import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent"; +import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent' export class OB11FriendRecallNoticeEvent extends OB11BaseNoticeEvent { - notice_type = "friend_recall" - user_id: number - message_id: number + notice_type = 'friend_recall' + user_id: number + message_id: number - public constructor(userId: number, messageId: number) { - super(); - this.user_id = userId; - this.message_id = messageId; - } -} \ No newline at end of file + public constructor(userId: number, messageId: number) { + super() + this.user_id = userId + this.message_id = messageId + } +} diff --git a/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts index 9018bfe..024e818 100644 --- a/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts @@ -1,6 +1,6 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent { - notice_type = "group_admin" - sub_type: "set" | "unset" // "set" | "unset" -} \ No newline at end of file + notice_type = 'group_admin' + sub_type: 'set' | 'unset' // "set" | "unset" +} diff --git a/src/onebot11/event/notice/OB11GroupBanEvent.ts b/src/onebot11/event/notice/OB11GroupBanEvent.ts index 1939539..1dec45b 100644 --- a/src/onebot11/event/notice/OB11GroupBanEvent.ts +++ b/src/onebot11/event/notice/OB11GroupBanEvent.ts @@ -1,17 +1,17 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export class OB11GroupBanEvent extends OB11GroupNoticeEvent { - notice_type = "group_ban"; - operator_id: number; - duration: number; - sub_type: "ban" | "lift_ban"; + notice_type = 'group_ban' + operator_id: number + duration: number + sub_type: 'ban' | 'lift_ban' - constructor(groupId: number, userId: number, operatorId: number, duration: number, sub_type: "ban" | "lift_ban") { - super(); - this.group_id = groupId; - this.operator_id = operatorId; - this.user_id = userId; - this.duration = duration; - this.sub_type = sub_type; - } + constructor(groupId: number, userId: number, operatorId: number, duration: number, sub_type: 'ban' | 'lift_ban') { + super() + this.group_id = groupId + this.operator_id = operatorId + this.user_id = userId + this.duration = duration + this.sub_type = sub_type + } } diff --git a/src/onebot11/event/notice/OB11GroupCardEvent.ts b/src/onebot11/event/notice/OB11GroupCardEvent.ts index dc258f9..1953741 100644 --- a/src/onebot11/event/notice/OB11GroupCardEvent.ts +++ b/src/onebot11/event/notice/OB11GroupCardEvent.ts @@ -1,16 +1,15 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export class OB11GroupCardEvent extends OB11GroupNoticeEvent { - notice_type = "group_card"; - card_new: string; - card_old: string; + notice_type = 'group_card' + card_new: string + card_old: string - - constructor(groupId: number, userId: number, cardNew: string, cardOld: string) { - super(); - this.group_id = groupId; - this.user_id = userId; - this.card_new = cardNew; - this.card_old = cardOld; - } + constructor(groupId: number, userId: number, cardNew: string, cardOld: string) { + super() + this.group_id = groupId + this.user_id = userId + this.card_new = cardNew + this.card_old = cardOld + } } diff --git a/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts index e63b170..66edb01 100644 --- a/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts +++ b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts @@ -1,17 +1,17 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' -export type GroupDecreaseSubType = "leave" | "kick" | "kick_me"; +export type GroupDecreaseSubType = 'leave' | 'kick' | 'kick_me' export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent { - notice_type = "group_decrease"; - sub_type: GroupDecreaseSubType = "leave"; // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me") - operator_id: number; + notice_type = 'group_decrease' + sub_type: GroupDecreaseSubType = 'leave' // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me") + operator_id: number - constructor(groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = "leave") { - super(); - this.group_id = groupId; - this.operator_id = operatorId; // 实际上不应该这么实现,但是现在还没有办法识别用户是被踢出的,还是自己主动退出的 - this.user_id = userId; - this.sub_type = subType; - } + constructor(groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') { + super() + this.group_id = groupId + this.operator_id = operatorId // 实际上不应该这么实现,但是现在还没有办法识别用户是被踢出的,还是自己主动退出的 + this.user_id = userId + this.sub_type = subType + } } diff --git a/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts b/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts index 3527820..12c76b4 100644 --- a/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts +++ b/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts @@ -1,15 +1,15 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' -type GroupIncreaseSubType = "approve" | "invite"; +type GroupIncreaseSubType = 'approve' | 'invite' export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent { - notice_type = "group_increase"; - operator_id: number; - sub_type: GroupIncreaseSubType; - constructor(groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = "approve") { - super(); - this.group_id = groupId; - this.operator_id = operatorId; - this.user_id = userId; - this.sub_type = subType - } + notice_type = 'group_increase' + operator_id: number + sub_type: GroupIncreaseSubType + constructor(groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = 'approve') { + super() + this.group_id = groupId + this.operator_id = operatorId + this.user_id = userId + this.sub_type = subType + } } diff --git a/src/onebot11/event/notice/OB11GroupNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupNoticeEvent.ts index 31c1d6c..2be3ae6 100644 --- a/src/onebot11/event/notice/OB11GroupNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupNoticeEvent.ts @@ -1,6 +1,6 @@ -import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent"; +import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent' export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent { - group_id: number; - user_id: number; -} \ No newline at end of file + group_id: number + user_id: number +} diff --git a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts index 11c180a..b396038 100644 --- a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts @@ -1,15 +1,15 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent { - notice_type = "group_recall" - operator_id: number - message_id: number + notice_type = 'group_recall' + operator_id: number + message_id: number - constructor(groupId: number, userId: number, operatorId: number, messageId: number) { - super(); - this.group_id = groupId; - this.user_id = userId; - this.operator_id = operatorId; - this.message_id = messageId; - } -} \ No newline at end of file + constructor(groupId: number, userId: number, operatorId: number, messageId: number) { + super() + this.group_id = groupId + this.user_id = userId + this.operator_id = operatorId + this.message_id = messageId + } +} diff --git a/src/onebot11/event/notice/OB11GroupTitleEvent.ts b/src/onebot11/event/notice/OB11GroupTitleEvent.ts index 0d3a97f..db83911 100644 --- a/src/onebot11/event/notice/OB11GroupTitleEvent.ts +++ b/src/onebot11/event/notice/OB11GroupTitleEvent.ts @@ -1,15 +1,14 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export class OB11GroupTitleEvent extends OB11GroupNoticeEvent { - notice_type = "notify"; - sub_type = "title"; - title: string + notice_type = 'notify' + sub_type = 'title' + title: string - - constructor(groupId: number, userId: number, title: string) { - super(); - this.group_id = groupId; - this.user_id = userId; - this.title = title; - } + constructor(groupId: number, userId: number, title: string) { + super() + this.group_id = groupId + this.user_id = userId + this.title = title + } } diff --git a/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts index e487efd..2163e80 100644 --- a/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupUploadNoticeEvent.ts @@ -1,20 +1,20 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' -export interface GroupUploadFile{ - id: string, - name: string, - size: number, - busid: number, +export interface GroupUploadFile { + id: string + name: string + size: number + busid: number } export class OB11GroupUploadNoticeEvent extends OB11GroupNoticeEvent { - notice_type = "group_upload" - file: GroupUploadFile + notice_type = 'group_upload' + file: GroupUploadFile - constructor(groupId: number, userId: number, file: GroupUploadFile) { - super(); - this.group_id = groupId; - this.user_id = userId; - this.file = file - } -} \ No newline at end of file + constructor(groupId: number, userId: number, file: GroupUploadFile) { + super() + this.group_id = groupId + this.user_id = userId + this.file = file + } +} diff --git a/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts b/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts index 0351f46..1a9a662 100644 --- a/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts +++ b/src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts @@ -1,21 +1,21 @@ -import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent"; +import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent' export interface MsgEmojiLike { - emoji_id: string, + emoji_id: string count: number } export class OB11GroupMsgEmojiLikeEvent extends OB11GroupNoticeEvent { - notice_type = "group_msg_emoji_like"; - message_id: number; - sub_type: "ban" | "lift_ban"; + notice_type = 'group_msg_emoji_like' + message_id: number + sub_type: 'ban' | 'lift_ban' likes: MsgEmojiLike[] constructor(groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[]) { - super(); - this.group_id = groupId; - this.user_id = userId; // 可为空,表示是对别人的消息操作,如果是对bot自己的消息则不为空 - this.message_id = messageId; - this.likes = likes; + super() + this.group_id = groupId + this.user_id = userId // 可为空,表示是对别人的消息操作,如果是对bot自己的消息则不为空 + this.message_id = messageId + this.likes = likes } } diff --git a/src/onebot11/event/notice/OB11PokeEvent.ts b/src/onebot11/event/notice/OB11PokeEvent.ts index 6359b3e..d66813e 100644 --- a/src/onebot11/event/notice/OB11PokeEvent.ts +++ b/src/onebot11/event/notice/OB11PokeEvent.ts @@ -1,30 +1,30 @@ -import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent"; -import {selfInfo} from "../../../common/data"; -import {OB11BaseEvent} from "../OB11BaseEvent"; +import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent' +import { selfInfo } from '../../../common/data' +import { OB11BaseEvent } from '../OB11BaseEvent' -class OB11PokeEvent extends OB11BaseNoticeEvent{ - notice_type = "notify" - sub_type = "poke" - target_id = parseInt(selfInfo.uin) - user_id: number +class OB11PokeEvent extends OB11BaseNoticeEvent { + notice_type = 'notify' + sub_type = 'poke' + target_id = parseInt(selfInfo.uin) + user_id: number } -export class OB11FriendPokeEvent extends OB11PokeEvent{ - sender_id: number - constructor(user_id: number) { - super(); - this.user_id = user_id; - this.sender_id = user_id; - } +export class OB11FriendPokeEvent extends OB11PokeEvent { + sender_id: number + constructor(user_id: number) { + super() + this.user_id = user_id + this.sender_id = user_id + } } -export class OB11GroupPokeEvent extends OB11PokeEvent{ - group_id: number +export class OB11GroupPokeEvent extends OB11PokeEvent { + group_id: number - constructor(group_id: number, user_id: number=0) { - super(); - this.group_id = group_id; - this.target_id = user_id; - this.user_id = user_id; - } + constructor(group_id: number, user_id: number = 0) { + super() + this.group_id = group_id + this.target_id = user_id + this.user_id = user_id + } } diff --git a/src/onebot11/event/request/OB11FriendRequest.ts b/src/onebot11/event/request/OB11FriendRequest.ts index 9c9bc8e..66724cf 100644 --- a/src/onebot11/event/request/OB11FriendRequest.ts +++ b/src/onebot11/event/request/OB11FriendRequest.ts @@ -1,11 +1,10 @@ -import {OB11BaseNoticeEvent} from "../notice/OB11BaseNoticeEvent"; -import {EventType} from "../OB11BaseEvent"; - +import { OB11BaseNoticeEvent } from '../notice/OB11BaseNoticeEvent' +import { EventType } from '../OB11BaseEvent' export class OB11FriendRequestEvent extends OB11BaseNoticeEvent { - post_type = EventType.REQUEST - user_id: number; - request_type: "friend" = "friend"; - comment: string; - flag: string; -} \ No newline at end of file + post_type = EventType.REQUEST + user_id: number + request_type: 'friend' = 'friend' + comment: string + flag: string +} diff --git a/src/onebot11/event/request/OB11GroupRequest.ts b/src/onebot11/event/request/OB11GroupRequest.ts index 37b36c7..07cd652 100644 --- a/src/onebot11/event/request/OB11GroupRequest.ts +++ b/src/onebot11/event/request/OB11GroupRequest.ts @@ -1,11 +1,10 @@ -import {OB11GroupNoticeEvent} from "../notice/OB11GroupNoticeEvent"; -import {EventType} from "../OB11BaseEvent"; - +import { OB11GroupNoticeEvent } from '../notice/OB11GroupNoticeEvent' +import { EventType } from '../OB11BaseEvent' export class OB11GroupRequestEvent extends OB11GroupNoticeEvent { - post_type = EventType.REQUEST; - request_type: "group" = "group"; - sub_type: "add" | "invite" = "add"; - comment: string; - flag: string; -} \ No newline at end of file + post_type = EventType.REQUEST + request_type: 'group' = 'group' + sub_type: 'add' | 'invite' = 'add' + comment: string + flag: string +} diff --git a/src/onebot11/server/http.ts b/src/onebot11/server/http.ts index e614f66..702fa06 100644 --- a/src/onebot11/server/http.ts +++ b/src/onebot11/server/http.ts @@ -1,55 +1,54 @@ -import {Response} from "express"; -import {OB11Response} from "../action/OB11Response"; -import {HttpServerBase} from "../../common/server/http"; -import {actionHandlers, actionMap} from "../action"; -import {getConfigUtil} from "../../common/config"; -import {postOB11Event} from "./postOB11Event"; -import {OB11HeartbeatEvent} from "../event/meta/OB11HeartbeatEvent"; -import {selfInfo} from "../../common/data"; +import { Response } from 'express' +import { OB11Response } from '../action/OB11Response' +import { HttpServerBase } from '../../common/server/http' +import { actionHandlers, actionMap } from '../action' +import { getConfigUtil } from '../../common/config' +import { postOB11Event } from './postOB11Event' +import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent' +import { selfInfo } from '../../common/data' class OB11HTTPServer extends HttpServerBase { - name = "OneBot V11 server" + name = 'OneBot V11 server' - handleFailed(res: Response, payload: any, e: any) { - res.send(OB11Response.error(e.stack.toString(), 200)) - } + handleFailed(res: Response, payload: any, e: any) { + res.send(OB11Response.error(e.stack.toString(), 200)) + } - protected listen(port: number) { - if (getConfigUtil().getConfig().ob11.enableHttp) { - super.listen(port); - } + protected listen(port: number) { + if (getConfigUtil().getConfig().ob11.enableHttp) { + super.listen(port) } + } } -export const ob11HTTPServer = new OB11HTTPServer(); +export const ob11HTTPServer = new OB11HTTPServer() setTimeout(() => { - for (const [actionName, action] of actionMap) { - for (const method of ["post", "get"]) { - ob11HTTPServer.registerRouter(method, actionName, (res, payload) => action.handle(payload)) - } + for (const [actionName, action] of actionMap) { + for (const method of ['post', 'get']) { + ob11HTTPServer.registerRouter(method, actionName, (res, payload) => action.handle(payload)) } + } }, 0) - -class HTTPHeart{ - intervalId: NodeJS.Timeout | null = null - start(){ - const {heartInterval} = getConfigUtil().getConfig(); - if (this.intervalId) { - clearInterval(this.intervalId); - } - this.intervalId = setInterval(() => { - // ws的心跳是ws自己维护的 - postOB11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false) - }, heartInterval) +class HTTPHeart { + intervalId: NodeJS.Timeout | null = null + start() { + const { heartInterval } = getConfigUtil().getConfig() + if (this.intervalId) { + clearInterval(this.intervalId) } + this.intervalId = setInterval(() => { + // ws的心跳是ws自己维护的 + postOB11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false) + }, heartInterval) + } - stop(){ - if (this.intervalId){ - clearInterval(this.intervalId) - } + stop() { + if (this.intervalId) { + clearInterval(this.intervalId) } + } } -export const httpHeart = new HTTPHeart(); \ No newline at end of file +export const httpHeart = new HTTPHeart() diff --git a/src/onebot11/server/postOB11Event.ts b/src/onebot11/server/postOB11Event.ts index 4c22920..eea11ab 100644 --- a/src/onebot11/server/postOB11Event.ts +++ b/src/onebot11/server/postOB11Event.ts @@ -1,178 +1,185 @@ -import {OB11Message, OB11MessageAt, OB11MessageData} from "../types"; -import {getFriend, getGroup, getUidByUin, selfInfo} from "../../common/data"; -import {OB11BaseMetaEvent} from "../event/meta/OB11BaseMetaEvent"; -import {OB11BaseNoticeEvent} from "../event/notice/OB11BaseNoticeEvent"; -import {WebSocket as WebSocketClass} from "ws"; -import {wsReply} from "./ws/reply"; -import {log} from "../../common/utils/log"; -import {getConfigUtil} from "../../common/config"; -import crypto from 'crypto'; -import {NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, Peer} from "../../ntqqapi/api"; -import {ChatType, Group, GroupRequestOperateTypes} from "../../ntqqapi/types"; -import {convertMessage2List, createSendElements, sendMsg} from "../action/msg/SendMsg"; -import {dbUtil} from "../../common/db"; -import {OB11FriendRequestEvent} from "../event/request/OB11FriendRequest"; -import {OB11GroupRequestEvent} from "../event/request/OB11GroupRequest"; -import {isNull} from "../../common/utils"; +import { OB11Message, OB11MessageAt, OB11MessageData } from '../types' +import { getFriend, getGroup, getUidByUin, selfInfo } from '../../common/data' +import { OB11BaseMetaEvent } from '../event/meta/OB11BaseMetaEvent' +import { OB11BaseNoticeEvent } from '../event/notice/OB11BaseNoticeEvent' +import { WebSocket as WebSocketClass } from 'ws' +import { wsReply } from './ws/reply' +import { log } from '../../common/utils/log' +import { getConfigUtil } from '../../common/config' +import crypto from 'crypto' +import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, Peer } from '../../ntqqapi/api' +import { ChatType, Group, GroupRequestOperateTypes } from '../../ntqqapi/types' +import { convertMessage2List, createSendElements, sendMsg } from '../action/msg/SendMsg' +import { dbUtil } from '../../common/db' +import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest' +import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest' +import { isNull } from '../../common/utils' export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent interface QuickActionPrivateMessage { - reply?: string; - auto_escape?: boolean; + reply?: string + auto_escape?: boolean } interface QuickActionGroupMessage extends QuickActionPrivateMessage { - // 回复群消息 - at_sender?: boolean - delete?: boolean - kick?: boolean - ban?: boolean - ban_duration?: number - // + // 回复群消息 + at_sender?: boolean + delete?: boolean + kick?: boolean + ban?: boolean + ban_duration?: number + // } interface QuickActionFriendRequest { - approve?: boolean - remark?: string + approve?: boolean + remark?: string } interface QuickActionGroupRequest { - approve?: boolean - reason?: string + approve?: boolean + reason?: string } -type QuickAction = - QuickActionPrivateMessage - & QuickActionGroupMessage - & QuickActionFriendRequest - & QuickActionGroupRequest +type QuickAction = QuickActionPrivateMessage & + QuickActionGroupMessage & + QuickActionFriendRequest & + QuickActionGroupRequest -const eventWSList: WebSocketClass[] = []; +const eventWSList: WebSocketClass[] = [] export function registerWsEventSender(ws: WebSocketClass) { - eventWSList.push(ws); + eventWSList.push(ws) } export function unregisterWsEventSender(ws: WebSocketClass) { - let index = eventWSList.indexOf(ws); - if (index !== -1) { - eventWSList.splice(index, 1); - } + let index = eventWSList.indexOf(ws) + if (index !== -1) { + eventWSList.splice(index, 1) + } } export function postWsEvent(event: PostEventType) { - for (const ws of eventWSList) { - new Promise(() => { - wsReply(ws, event); - }).then() - } + for (const ws of eventWSList) { + new Promise(() => { + wsReply(ws, event) + }).then() + } } export function postOB11Event(msg: PostEventType, reportSelf = false, postWs = true) { - const config = getConfigUtil().getConfig(); - // 判断msg是否是event - if (!config.reportSelfMessage && !reportSelf) { - if (msg.post_type === "message" && (msg as OB11Message).user_id.toString() == selfInfo.uin) { + const config = getConfigUtil().getConfig() + // 判断msg是否是event + if (!config.reportSelfMessage && !reportSelf) { + if (msg.post_type === 'message' && (msg as OB11Message).user_id.toString() == selfInfo.uin) { + return + } + } + if (config.ob11.enableHttpPost) { + const msgStr = JSON.stringify(msg) + const hmac = crypto.createHmac('sha1', config.ob11.httpSecret) + hmac.update(msgStr) + const sig = hmac.digest('hex') + let headers = { + 'Content-Type': 'application/json', + 'x-self-id': selfInfo.uin, + } + if (config.ob11.httpSecret) { + headers['x-signature'] = 'sha1=' + sig + } + for (const host of config.ob11.httpHosts) { + fetch(host, { + method: 'POST', + headers, + body: msgStr, + }).then( + async (res) => { + log(`新消息事件HTTP上报成功: ${host} `, msgStr) + // todo: 处理不够优雅,应该使用高级泛型进行QuickAction类型识别 + let resJson: QuickAction + try { + resJson = await res.json() + log(`新消息事件HTTP上报返回快速操作: `, JSON.stringify(resJson)) + } catch (e) { + log(`新消息事件HTTP上报没有返回快速操作,不需要处理`) return - } - } - if (config.ob11.enableHttpPost) { - const msgStr = JSON.stringify(msg); - const hmac = crypto.createHmac('sha1', config.ob11.httpSecret); - hmac.update(msgStr); - const sig = hmac.digest('hex'); - let headers = { - "Content-Type": "application/json", - "x-self-id": selfInfo.uin - } - if (config.ob11.httpSecret) { - headers["x-signature"] = "sha1=" + sig; - } - for (const host of config.ob11.httpHosts) { - fetch(host, { - method: "POST", - headers, - body: msgStr - }).then(async (res) => { - log(`新消息事件HTTP上报成功: ${host} `, msgStr); - // todo: 处理不够优雅,应该使用高级泛型进行QuickAction类型识别 - let resJson: QuickAction; - try { - resJson = await res.json(); - log(`新消息事件HTTP上报返回快速操作: `, JSON.stringify(resJson)) - } catch (e) { - log(`新消息事件HTTP上报没有返回快速操作,不需要处理`) - return - } - if (msg.post_type === "message") { - msg = msg as OB11Message; - const rawMessage = await dbUtil.getMsgByShortId(msg.message_id) - resJson = resJson as QuickActionPrivateMessage | QuickActionGroupMessage - const reply = resJson.reply - let peer: Peer = { - chatType: ChatType.friend, - peerUid: msg.user_id.toString() - } - if (msg.message_type == "private") { - peer.peerUid = getUidByUin(msg.user_id.toString()) - if (msg.sub_type === "group") { - peer.chatType = ChatType.temp - } - } else { - peer.chatType = ChatType.group - peer.peerUid = msg.group_id.toString() - } - if (reply) { - let group: Group = null - let replyMessage: OB11MessageData[] = [] + } + if (msg.post_type === 'message') { + msg = msg as OB11Message + const rawMessage = await dbUtil.getMsgByShortId(msg.message_id) + resJson = resJson as QuickActionPrivateMessage | QuickActionGroupMessage + const reply = resJson.reply + let peer: Peer = { + chatType: ChatType.friend, + peerUid: msg.user_id.toString(), + } + if (msg.message_type == 'private') { + peer.peerUid = getUidByUin(msg.user_id.toString()) + if (msg.sub_type === 'group') { + peer.chatType = ChatType.temp + } + } else { + peer.chatType = ChatType.group + peer.peerUid = msg.group_id.toString() + } + if (reply) { + let group: Group = null + let replyMessage: OB11MessageData[] = [] - if (msg.message_type == "group") { - group = await getGroup(msg.group_id.toString()) - if ((resJson as QuickActionGroupMessage).at_sender) { - replyMessage.push({ - type: "at", - data: { - qq: msg.user_id.toString() - } - } as OB11MessageAt) - } - } - replyMessage = replyMessage.concat(convertMessage2List(reply, resJson.auto_escape)) - const {sendElements, deleteAfterSentFiles} = await createSendElements(replyMessage, group) - log(`发送消息给`, peer, sendElements) - sendMsg(peer, sendElements, deleteAfterSentFiles, false).then() - } else if (resJson.delete) { - NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then() - } else if (resJson.kick) { - NTQQGroupApi.kickMember(peer.peerUid, [rawMessage.senderUid]).then() - } else if (resJson.ban) { - NTQQGroupApi.banMember(peer.peerUid, [{ - uid: rawMessage.senderUid, - timeStamp: resJson.ban_duration || 60 * 30 - }],).then() - } - - } else if (msg.post_type === "request") { - if ((msg as OB11FriendRequestEvent).request_type === "friend") { - resJson = resJson as QuickActionFriendRequest - if (!isNull(resJson.approve)) { - // todo: set remark - NTQQFriendApi.handleFriendRequest(((msg as OB11FriendRequestEvent).flag), resJson.approve).then() - } - } else if ((msg as OB11GroupRequestEvent).request_type === "group") { - resJson = resJson as QuickActionGroupRequest - if (!isNull(resJson.approve)) { - NTQQGroupApi.handleGroupRequest((msg as OB11FriendRequestEvent).flag, resJson.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, resJson.reason).then() - } - } + if (msg.message_type == 'group') { + group = await getGroup(msg.group_id.toString()) + if ((resJson as QuickActionGroupMessage).at_sender) { + replyMessage.push({ + type: 'at', + data: { + qq: msg.user_id.toString(), + }, + } as OB11MessageAt) } - }, (err: any) => { - log(`新消息事件HTTP上报失败: ${host} `, err, msg); - }); - } + } + replyMessage = replyMessage.concat(convertMessage2List(reply, resJson.auto_escape)) + const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group) + log(`发送消息给`, peer, sendElements) + sendMsg(peer, sendElements, deleteAfterSentFiles, false).then() + } else if (resJson.delete) { + NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then() + } else if (resJson.kick) { + NTQQGroupApi.kickMember(peer.peerUid, [rawMessage.senderUid]).then() + } else if (resJson.ban) { + NTQQGroupApi.banMember(peer.peerUid, [ + { + uid: rawMessage.senderUid, + timeStamp: resJson.ban_duration || 60 * 30, + }, + ]).then() + } + } else if (msg.post_type === 'request') { + if ((msg as OB11FriendRequestEvent).request_type === 'friend') { + resJson = resJson as QuickActionFriendRequest + if (!isNull(resJson.approve)) { + // todo: set remark + NTQQFriendApi.handleFriendRequest((msg as OB11FriendRequestEvent).flag, resJson.approve).then() + } + } else if ((msg as OB11GroupRequestEvent).request_type === 'group') { + resJson = resJson as QuickActionGroupRequest + if (!isNull(resJson.approve)) { + NTQQGroupApi.handleGroupRequest( + (msg as OB11FriendRequestEvent).flag, + resJson.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, + resJson.reason, + ).then() + } + } + } + }, + (err: any) => { + log(`新消息事件HTTP上报失败: ${host} `, err, msg) + }, + ) } - if (postWs){ - postWsEvent(msg); - } -} \ No newline at end of file + } + if (postWs) { + postWsEvent(msg) + } +} diff --git a/src/onebot11/server/ws/ReverseWebsocket.ts b/src/onebot11/server/ws/ReverseWebsocket.ts index 77e3e92..5bbcb1b 100644 --- a/src/onebot11/server/ws/ReverseWebsocket.ts +++ b/src/onebot11/server/ws/ReverseWebsocket.ts @@ -1,148 +1,146 @@ -import {selfInfo} from "../../../common/data"; -import {LifeCycleSubType, OB11LifeCycleEvent} from "../../event/meta/OB11LifeCycleEvent"; -import {ActionName} from "../../action/types"; -import {OB11Response} from "../../action/OB11Response"; -import BaseAction from "../../action/BaseAction"; -import {actionMap} from "../../action"; -import {postWsEvent, registerWsEventSender, unregisterWsEventSender} from "../postOB11Event"; -import {wsReply} from "./reply"; -import {WebSocket as WebSocketClass} from "ws"; -import {OB11HeartbeatEvent} from "../../event/meta/OB11HeartbeatEvent"; -import {log} from "../../../common/utils/log"; -import {getConfigUtil} from "../../../common/config"; +import { selfInfo } from '../../../common/data' +import { LifeCycleSubType, OB11LifeCycleEvent } from '../../event/meta/OB11LifeCycleEvent' +import { ActionName } from '../../action/types' +import { OB11Response } from '../../action/OB11Response' +import BaseAction from '../../action/BaseAction' +import { actionMap } from '../../action' +import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../postOB11Event' +import { wsReply } from './reply' +import { WebSocket as WebSocketClass } from 'ws' +import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent' +import { log } from '../../../common/utils/log' +import { getConfigUtil } from '../../../common/config' -export let rwsList: ReverseWebsocket[] = []; +export let rwsList: ReverseWebsocket[] = [] export class ReverseWebsocket { - public websocket: WebSocketClass; - public url: string; - private running: boolean = false; + public websocket: WebSocketClass + public url: string + private running: boolean = false - public constructor(url: string) { - this.url = url; - this.running = true; - this.connect(); + public constructor(url: string) { + this.url = url + this.running = true + this.connect() + } + + public stop() { + this.running = false + this.websocket.close() + } + + public onopen() { + wsReply(this.websocket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) + } + + public async onmessage(msg: string) { + let receiveData: { action: ActionName; params: any; echo?: any } = { action: null, params: {} } + let echo = null + try { + receiveData = JSON.parse(msg.toString()) + echo = receiveData.echo + log('收到反向Websocket消息', receiveData) + } catch (e) { + return wsReply(this.websocket, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)) } - - public stop() { - this.running = false; - this.websocket.close(); + const action: BaseAction = actionMap.get(receiveData.action) + if (!action) { + return wsReply(this.websocket, OB11Response.error('不支持的api ' + receiveData.action, 1404, echo)) } - - public onopen() { - wsReply(this.websocket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)); + try { + let handleResult = await action.websocketHandle(receiveData.params, echo) + wsReply(this.websocket, handleResult) + } catch (e) { + wsReply(this.websocket, OB11Response.error(`api处理出错:${e}`, 1200, echo)) } + } - public async onmessage(msg: string) { - let receiveData: { action: ActionName, params: any, echo?: any } = {action: null, params: {}} - let echo = null - try { - receiveData = JSON.parse(msg.toString()) - echo = receiveData.echo - log("收到反向Websocket消息", receiveData) - } catch (e) { - return wsReply(this.websocket, OB11Response.error("json解析失败,请检查数据格式", 1400, echo)) - } - const action: BaseAction = actionMap.get(receiveData.action); - if (!action) { - return wsReply(this.websocket, OB11Response.error("不支持的api " + receiveData.action, 1404, echo)) - } - try { - let handleResult = await action.websocketHandle(receiveData.params, echo); - wsReply(this.websocket, handleResult) - } catch (e) { - wsReply(this.websocket, OB11Response.error(`api处理出错:${e}`, 1200, echo)) - } + public onclose = function () { + log('反向ws断开', this.url) + unregisterWsEventSender(this.websocket) + if (this.running) { + this.reconnect() } + } - public onclose = function () { - log("反向ws断开", this.url); - unregisterWsEventSender(this.websocket); - if (this.running) { - this.reconnect(); - } + public send(msg: string) { + if (this.websocket && this.websocket.readyState == WebSocket.OPEN) { + this.websocket.send(msg) } + } - public send(msg: string) { - if (this.websocket && this.websocket.readyState == WebSocket.OPEN) { - this.websocket.send(msg); - } - } + private reconnect() { + setTimeout(() => { + this.connect() + }, 3000) // TODO: 重连间隔在配置文件中实现 + } - private reconnect() { - setTimeout(() => { - this.connect(); - }, 3000); // TODO: 重连间隔在配置文件中实现 - } + private connect() { + const { token, heartInterval } = getConfigUtil().getConfig() + this.websocket = new WebSocketClass(this.url, { + maxPayload: 1024 * 1024 * 1024, + handshakeTimeout: 2000, + perMessageDeflate: false, + headers: { + 'X-Self-ID': selfInfo.uin, + Authorization: `Bearer ${token}`, + 'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段 + }, + }) + registerWsEventSender(this.websocket) + log('Trying to connect to the websocket server: ' + this.url) - private connect() { - const {token, heartInterval} = getConfigUtil().getConfig() - this.websocket = new WebSocketClass(this.url, { - maxPayload: 1024 * 1024 * 1024, - handshakeTimeout: 2000, - perMessageDeflate: false, - headers: { - 'X-Self-ID': selfInfo.uin, - 'Authorization': `Bearer ${token}`, - 'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段 - } - }); - registerWsEventSender(this.websocket); - log("Trying to connect to the websocket server: " + this.url); + this.websocket.on('open', () => { + log('Connected to the websocket server: ' + this.url) + this.onopen() + }) + this.websocket.on('message', async (data) => { + await this.onmessage(data.toString()) + }) - this.websocket.on("open", () => { - log("Connected to the websocket server: " + this.url); - this.onopen(); - }); + this.websocket.on('error', log) - this.websocket.on("message", async (data) => { - await this.onmessage(data.toString()); - }); - - this.websocket.on("error", log); - - const wsClientInterval = setInterval(() => { - postWsEvent(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval)); - }, heartInterval); // 心跳包 - this.websocket.on("close", () => { - clearInterval(wsClientInterval); - log("The websocket connection: " + this.url + " closed, trying reconnecting..."); - this.onclose(); - }); - } + const wsClientInterval = setInterval(() => { + postWsEvent(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval)) + }, heartInterval) // 心跳包 + this.websocket.on('close', () => { + clearInterval(wsClientInterval) + log('The websocket connection: ' + this.url + ' closed, trying reconnecting...') + this.onclose() + }) + } } class OB11ReverseWebsockets { - start() { - for (const url of getConfigUtil().getConfig().ob11.wsHosts) { - log("开始连接反向ws", url) - new Promise(() => { - try { - rwsList.push(new ReverseWebsocket(url)); - } catch (e) { - log(e.stack); - } - }).then(); + start() { + for (const url of getConfigUtil().getConfig().ob11.wsHosts) { + log('开始连接反向ws', url) + new Promise(() => { + try { + rwsList.push(new ReverseWebsocket(url)) + } catch (e) { + log(e.stack) } + }).then() } + } - stop() { - for (let rws of rwsList) { - try { - rws.stop(); - }catch (e) { - log("反向ws关闭:", e.stack) - } - } - rwsList.length = 0; + stop() { + for (let rws of rwsList) { + try { + rws.stop() + } catch (e) { + log('反向ws关闭:', e.stack) + } } + rwsList.length = 0 + } - restart() { - this.stop(); - this.start(); - } + restart() { + this.stop() + this.start() + } } -export const ob11ReverseWebsockets = new OB11ReverseWebsockets(); - +export const ob11ReverseWebsockets = new OB11ReverseWebsockets() diff --git a/src/onebot11/server/ws/WebsocketServer.ts b/src/onebot11/server/ws/WebsocketServer.ts index 446b0df..2a5ccf4 100644 --- a/src/onebot11/server/ws/WebsocketServer.ts +++ b/src/onebot11/server/ws/WebsocketServer.ts @@ -1,75 +1,74 @@ -import {WebSocket} from "ws"; -import {actionMap} from "../../action"; -import {OB11Response} from "../../action/OB11Response"; -import {postWsEvent, registerWsEventSender, unregisterWsEventSender} from "../postOB11Event"; -import {ActionName} from "../../action/types"; -import BaseAction from "../../action/BaseAction"; -import {LifeCycleSubType, OB11LifeCycleEvent} from "../../event/meta/OB11LifeCycleEvent"; -import {OB11HeartbeatEvent} from "../../event/meta/OB11HeartbeatEvent"; -import {WebsocketServerBase} from "../../../common/server/websocket"; -import {IncomingMessage} from "node:http"; -import {wsReply} from "./reply"; -import {selfInfo} from "../../../common/data"; -import {log} from "../../../common/utils/log"; -import {getConfigUtil} from "../../../common/config"; +import { WebSocket } from 'ws' +import { actionMap } from '../../action' +import { OB11Response } from '../../action/OB11Response' +import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../postOB11Event' +import { ActionName } from '../../action/types' +import BaseAction from '../../action/BaseAction' +import { LifeCycleSubType, OB11LifeCycleEvent } from '../../event/meta/OB11LifeCycleEvent' +import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent' +import { WebsocketServerBase } from '../../../common/server/websocket' +import { IncomingMessage } from 'node:http' +import { wsReply } from './reply' +import { selfInfo } from '../../../common/data' +import { log } from '../../../common/utils/log' +import { getConfigUtil } from '../../../common/config' -let heartbeatRunning = false; +let heartbeatRunning = false class OB11WebsocketServer extends WebsocketServerBase { - authorizeFailed(wsClient: WebSocket) { - wsClient.send(JSON.stringify(OB11Response.res(null, "failed", 1403, "token验证失败"))) - } + authorizeFailed(wsClient: WebSocket) { + wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败'))) + } - async handleAction(wsClient: WebSocket, actionName: string, params: any, echo?: any) { - const action: BaseAction = actionMap.get(actionName); - if (!action) { - return wsReply(wsClient, OB11Response.error("不支持的api " + actionName, 1404, echo)) - } + async handleAction(wsClient: WebSocket, actionName: string, params: any, echo?: any) { + const action: BaseAction = actionMap.get(actionName) + if (!action) { + return wsReply(wsClient, OB11Response.error('不支持的api ' + actionName, 1404, echo)) + } + try { + let handleResult = await action.websocketHandle(params, echo) + wsReply(wsClient, handleResult) + } catch (e) { + wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo)) + } + } + + onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { + if (url == '/api' || url == '/api/' || url == '/') { + wsClient.on('message', async (msg) => { + let receiveData: { action: ActionName; params: any; echo?: any } = { action: null, params: {} } + let echo = null try { - let handleResult = await action.websocketHandle(params, echo); - wsReply(wsClient, handleResult) + receiveData = JSON.parse(msg.toString()) + echo = receiveData.echo + log('收到正向Websocket消息', receiveData) } catch (e) { - wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo)) + return wsReply(wsClient, OB11Response.error('json解析失败,请检查数据格式', 1400, echo)) } + this.handleAction(wsClient, receiveData.action, receiveData.params, receiveData.echo).then() + }) } + if (url == '/event' || url == '/event/' || url == '/') { + registerWsEventSender(wsClient) - onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { - if (url == "/api" || url == "/api/" || url == "/") { - wsClient.on("message", async (msg) => { - let receiveData: { action: ActionName, params: any, echo?: any } = {action: null, params: {}} - let echo = null - try { - receiveData = JSON.parse(msg.toString()) - echo = receiveData.echo - log("收到正向Websocket消息", receiveData); - } catch (e) { - return wsReply(wsClient, OB11Response.error("json解析失败,请检查数据格式", 1400, echo)) - } - this.handleAction(wsClient, receiveData.action, receiveData.params, receiveData.echo).then() - }) - } - if (url == "/event" || url == "/event/" || url == "/") { - registerWsEventSender(wsClient); + log('event上报ws客户端已连接') - log("event上报ws客户端已连接") - - try { - wsReply(wsClient, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) - } catch (e) { - log("发送生命周期失败", e) - } - const {heartInterval} = getConfigUtil().getConfig(); - const wsClientInterval = setInterval(() => { - postWsEvent(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval)); - }, heartInterval); // 心跳包 - wsClient.on("close", () => { - log("event上报ws客户端已断开") - clearInterval(wsClientInterval); - unregisterWsEventSender(wsClient); - }) - } + try { + wsReply(wsClient, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) + } catch (e) { + log('发送生命周期失败', e) + } + const { heartInterval } = getConfigUtil().getConfig() + const wsClientInterval = setInterval(() => { + postWsEvent(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval)) + }, heartInterval) // 心跳包 + wsClient.on('close', () => { + log('event上报ws客户端已断开') + clearInterval(wsClientInterval) + unregisterWsEventSender(wsClient) + }) } + } } export const ob11WebsocketServer = new OB11WebsocketServer() - diff --git a/src/onebot11/server/ws/reply.ts b/src/onebot11/server/ws/reply.ts index 4f426db..c29562a 100644 --- a/src/onebot11/server/ws/reply.ts +++ b/src/onebot11/server/ws/reply.ts @@ -1,18 +1,18 @@ -import {WebSocket as WebSocketClass} from "ws"; -import {OB11Response} from "../../action/OB11Response"; -import {PostEventType} from "../postOB11Event"; -import {log} from "../../../common/utils/log"; -import {isNull} from "../../../common/utils/helper"; +import { WebSocket as WebSocketClass } from 'ws' +import { OB11Response } from '../../action/OB11Response' +import { PostEventType } from '../postOB11Event' +import { log } from '../../../common/utils/log' +import { isNull } from '../../../common/utils/helper' export function wsReply(wsClient: WebSocketClass, data: OB11Response | PostEventType) { - try { - let packet = Object.assign({}, data); - if (isNull(packet["echo"])) { - delete packet["echo"]; - } - wsClient.send(JSON.stringify(packet)) - log("ws 消息上报", wsClient.url || "", data); - } catch (e) { - log("websocket 回复失败", e.stack, data); + try { + let packet = Object.assign({}, data) + if (isNull(packet['echo'])) { + delete packet['echo'] } -} \ No newline at end of file + wsClient.send(JSON.stringify(packet)) + log('ws 消息上报', wsClient.url || '', data) + } catch (e) { + log('websocket 回复失败', e.stack, data) + } +} diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 2d88e31..2f50149 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -1,270 +1,276 @@ -import {PicSubType, RawMessage} from "../ntqqapi/types"; -import {EventType} from "./event/OB11BaseEvent"; +import { PicSubType, RawMessage } from '../ntqqapi/types' +import { EventType } from './event/OB11BaseEvent' export interface OB11User { - user_id: number; - nickname: string; - remark?: string; - sex?: OB11UserSex; - level?: number; - age?: number; - qid?: string; - login_days?: number; + user_id: number + nickname: string + remark?: string + sex?: OB11UserSex + level?: number + age?: number + qid?: string + login_days?: number } export enum OB11UserSex { - male = "male", - female = "female", - unknown = "unknown" + male = 'male', + female = 'female', + unknown = 'unknown', } export enum OB11GroupMemberRole { - owner = "owner", - admin = "admin", - member = "member", + owner = 'owner', + admin = 'admin', + member = 'member', } export interface OB11GroupMember { - group_id: number - user_id: number - nickname: string - card?: string - sex?: OB11UserSex - age?: number - join_time?: number - last_sent_time?: number - level?: number - qq_level?: number - role?: OB11GroupMemberRole - title?: string - area?: string - unfriendly?: boolean - title_expire_time?: number - card_changeable?: boolean - // 以下为gocq字段 - shut_up_timestamp?: number - // 以下为扩展字段 - is_robot?: boolean + group_id: number + user_id: number + nickname: string + card?: string + sex?: OB11UserSex + age?: number + join_time?: number + last_sent_time?: number + level?: number + qq_level?: number + role?: OB11GroupMemberRole + title?: string + area?: string + unfriendly?: boolean + title_expire_time?: number + card_changeable?: boolean + // 以下为gocq字段 + shut_up_timestamp?: number + // 以下为扩展字段 + is_robot?: boolean } export interface OB11Group { - group_id: number - group_name: string - member_count?: number - max_member_count?: number + group_id: number + group_name: string + member_count?: number + max_member_count?: number } interface OB11Sender { - user_id: number, - nickname: string, - sex?: OB11UserSex, - age?: number, - card?: string, // 群名片 - level?: string, // 群等级 - role?: OB11GroupMemberRole + user_id: number + nickname: string + sex?: OB11UserSex + age?: number + card?: string // 群名片 + level?: string // 群等级 + role?: OB11GroupMemberRole } export enum OB11MessageType { - private = "private", - group = "group" + private = 'private', + group = 'group', } export interface OB11Message { - target_id?: number; // 自己发送的消息才有此字段 - self_id?: number, - time: number, - message_id: number, - real_id: number, - user_id: number, - group_id?: number, - message_type: "private" | "group", - sub_type?: "friend" | "group" | "normal", - sender: OB11Sender, - message: OB11MessageData[] | string, - message_format: 'array' | 'string', - raw_message: string, - font: number, - post_type?: EventType, - raw?: RawMessage + target_id?: number // 自己发送的消息才有此字段 + self_id?: number + time: number + message_id: number + real_id: number + user_id: number + group_id?: number + message_type: 'private' | 'group' + sub_type?: 'friend' | 'group' | 'normal' + sender: OB11Sender + message: OB11MessageData[] | string + message_format: 'array' | 'string' + raw_message: string + font: number + post_type?: EventType + raw?: RawMessage } export interface OB11ForwardMessage extends OB11Message { - content: OB11MessageData[] | string; + content: OB11MessageData[] | string } export interface OB11Return { - status: string - retcode: number - data: DataType - message: string, - echo?: any, // ws调用api才有此字段 - wording?: string, // go-cqhttp字段,错误信息 + status: string + retcode: number + data: DataType + message: string + echo?: any // ws调用api才有此字段 + wording?: string // go-cqhttp字段,错误信息 } export enum OB11MessageDataType { - text = "text", - image = "image", - music = "music", - video = "video", - voice = "record", - file = "file", - at = "at", - reply = "reply", - json = "json", - face = "face", - mface = "mface", // 商城表情 - markdown = "markdown", - node = "node", // 合并转发消息节点 - forward = "forward", // 合并转发消息,用于上报 - xml = "xml", - poke = "poke", - dice = "dice", - RPS = "rps" + text = 'text', + image = 'image', + music = 'music', + video = 'video', + voice = 'record', + file = 'file', + at = 'at', + reply = 'reply', + json = 'json', + face = 'face', + mface = 'mface', // 商城表情 + markdown = 'markdown', + node = 'node', // 合并转发消息节点 + forward = 'forward', // 合并转发消息,用于上报 + xml = 'xml', + poke = 'poke', + dice = 'dice', + RPS = 'rps', } -export interface OB11MessageMFace{ - type: OB11MessageDataType.mface, - data: { - emojiPackageId: number, - emojiId: string, - key: string - } +export interface OB11MessageMFace { + type: OB11MessageDataType.mface + data: { + emojiPackageId: number + emojiId: string + key: string + } } -export interface OB11MessageDice{ - type: OB11MessageDataType.dice, - data: { - result: number - } +export interface OB11MessageDice { + type: OB11MessageDataType.dice + data: { + result: number + } } -export interface OB11MessageRPS{ - type: OB11MessageDataType.RPS, - data: { - result: number - } +export interface OB11MessageRPS { + type: OB11MessageDataType.RPS + data: { + result: number + } } export interface OB11MessageText { - type: OB11MessageDataType.text, - data: { - text: string, // 纯文本 - } + type: OB11MessageDataType.text + data: { + text: string // 纯文本 + } } -export interface OB11MessagePoke{ - type: OB11MessageDataType.poke - data: { - qq?: number, - id?: number - } +export interface OB11MessagePoke { + type: OB11MessageDataType.poke + data: { + qq?: number + id?: number + } } interface OB11MessageFileBase { - data: { - thumb?: string; - name?: string; - file: string, - url?: string; - } + data: { + thumb?: string + name?: string + file: string + url?: string + } } - export interface OB11MessageImage extends OB11MessageFileBase { - type: OB11MessageDataType.image - data: OB11MessageFileBase['data'] & { - summary ? : string; // 图片摘要 - subType?: PicSubType - }, + type: OB11MessageDataType.image + data: OB11MessageFileBase['data'] & { + summary?: string // 图片摘要 + subType?: PicSubType + } } export interface OB11MessageRecord extends OB11MessageFileBase { - type: OB11MessageDataType.voice + type: OB11MessageDataType.voice } export interface OB11MessageFile extends OB11MessageFileBase { - type: OB11MessageDataType.file + type: OB11MessageDataType.file } export interface OB11MessageVideo extends OB11MessageFileBase { - type: OB11MessageDataType.video + type: OB11MessageDataType.video } export interface OB11MessageAt { - type: OB11MessageDataType.at - data: { - qq: string | "all" - } + type: OB11MessageDataType.at + data: { + qq: string | 'all' + } } export interface OB11MessageReply { - type: OB11MessageDataType.reply - data: { - id: string - } + type: OB11MessageDataType.reply + data: { + id: string + } } export interface OB11MessageFace { - type: OB11MessageDataType.face - data: { - id: string - } + type: OB11MessageDataType.face + data: { + id: string + } } -export type OB11MessageMixType = OB11MessageData[] | string | OB11MessageData; +export type OB11MessageMixType = OB11MessageData[] | string | OB11MessageData export interface OB11MessageNode { - type: OB11MessageDataType.node - data: { - id?: string - user_id?: number - nickname: string - content: OB11MessageMixType - } + type: OB11MessageDataType.node + data: { + id?: string + user_id?: number + nickname: string + content: OB11MessageMixType + } } -export interface OB11MessageCustomMusic{ - type: OB11MessageDataType.music - data: { - type: "custom" - url: string, - audio: string, - title: string, - content?: string, - image?: string - } +export interface OB11MessageCustomMusic { + type: OB11MessageDataType.music + data: { + type: 'custom' + url: string + audio: string + title: string + content?: string + image?: string + } } export interface OB11MessageJson { - type: OB11MessageDataType.json - data: {config: {token: string}} & any + type: OB11MessageDataType.json + data: { config: { token: string } } & any } export type OB11MessageData = - OB11MessageText | - OB11MessageFace | OB11MessageMFace | - OB11MessageAt | OB11MessageReply | - OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo | - OB11MessageNode | OB11MessageCustomMusic | OB11MessageJson | OB11MessagePoke | - OB11MessageDice | OB11MessageRPS + | OB11MessageText + | OB11MessageFace + | OB11MessageMFace + | OB11MessageAt + | OB11MessageReply + | OB11MessageImage + | OB11MessageRecord + | OB11MessageFile + | OB11MessageVideo + | OB11MessageNode + | OB11MessageCustomMusic + | OB11MessageJson + | OB11MessagePoke + | OB11MessageDice + | OB11MessageRPS export interface OB11PostSendMsg { - message_type?: "private" | "group" - user_id: string, - group_id?: string, - message: OB11MessageMixType; - messages?: OB11MessageMixType; // 兼容 go-cqhttp - auto_escape?: boolean | string + message_type?: 'private' | 'group' + user_id: string + group_id?: string + message: OB11MessageMixType + messages?: OB11MessageMixType // 兼容 go-cqhttp + auto_escape?: boolean | string } export interface OB11Version { - app_name: "LLOneBot" - app_version: string - protocol_version: "v11" + app_name: 'LLOneBot' + app_version: string + protocol_version: 'v11' } - export interface OB11Status { - online: boolean | null, - good: boolean + online: boolean | null + good: boolean } - diff --git a/src/preload.ts b/src/preload.ts index bfd8cac..86235d0 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,44 +1,44 @@ // Electron 主进程 与 渲染进程 交互的桥梁 -import {CheckVersion, Config, LLOneBotError} from "./common/types"; +import { CheckVersion, Config, LLOneBotError } from './common/types' import { - CHANNEL_ERROR, - CHANNEL_GET_CONFIG, - CHANNEL_LOG, - CHANNEL_CHECK_VERSION, - CHANNEL_SELECT_FILE, - CHANNEL_SET_CONFIG, - CHANNEL_UPDATE, -} from "./common/channels"; + CHANNEL_ERROR, + CHANNEL_GET_CONFIG, + CHANNEL_LOG, + CHANNEL_CHECK_VERSION, + CHANNEL_SELECT_FILE, + CHANNEL_SET_CONFIG, + CHANNEL_UPDATE, +} from './common/channels' -const {contextBridge} = require("electron"); -const {ipcRenderer} = require('electron'); +const { contextBridge } = require('electron') +const { ipcRenderer } = require('electron') const llonebot = { - log: (data: any) => { - ipcRenderer.send(CHANNEL_LOG, data); - }, - checkVersion:async (): Promise => { - return ipcRenderer.invoke(CHANNEL_CHECK_VERSION); - }, - updateLLOneBot:async (): Promise => { - return ipcRenderer.invoke(CHANNEL_UPDATE); - }, - setConfig: (ask: boolean, config: Config) => { - ipcRenderer.send(CHANNEL_SET_CONFIG, ask, config); - }, - getConfig: async (): Promise => { - return ipcRenderer.invoke(CHANNEL_GET_CONFIG); - }, - getError: async (): Promise => { - return ipcRenderer.invoke(CHANNEL_ERROR); - }, - selectFile: (): Promise => { - return ipcRenderer.invoke(CHANNEL_SELECT_FILE); - } + log: (data: any) => { + ipcRenderer.send(CHANNEL_LOG, data) + }, + checkVersion: async (): Promise => { + return ipcRenderer.invoke(CHANNEL_CHECK_VERSION) + }, + updateLLOneBot: async (): Promise => { + return ipcRenderer.invoke(CHANNEL_UPDATE) + }, + setConfig: (ask: boolean, config: Config) => { + ipcRenderer.send(CHANNEL_SET_CONFIG, ask, config) + }, + getConfig: async (): Promise => { + return ipcRenderer.invoke(CHANNEL_GET_CONFIG) + }, + getError: async (): Promise => { + return ipcRenderer.invoke(CHANNEL_ERROR) + }, + selectFile: (): Promise => { + return ipcRenderer.invoke(CHANNEL_SELECT_FILE) + }, } -export type LLOneBot = typeof llonebot; +export type LLOneBot = typeof llonebot // 在window对象下导出只读对象 -contextBridge.exposeInMainWorld("llonebot", llonebot); \ No newline at end of file +contextBridge.exposeInMainWorld('llonebot', llonebot) diff --git a/src/renderer/components/button.ts b/src/renderer/components/button.ts index fda59c8..6a345af 100644 --- a/src/renderer/components/button.ts +++ b/src/renderer/components/button.ts @@ -1,4 +1,3 @@ - export const SettingButton = (text: string, id?: string, type: string = 'secondary') => { - return `${text}`; -} \ No newline at end of file + return `${text}` +} diff --git a/src/renderer/components/index.ts b/src/renderer/components/index.ts index 7fd2fe8..fd4f228 100644 --- a/src/renderer/components/index.ts +++ b/src/renderer/components/index.ts @@ -1,5 +1,5 @@ -export * from './list'; -export * from './item'; -export * from './button'; -export * from './switch'; -export * from './select'; +export * from './list' +export * from './item' +export * from './button' +export * from './switch' +export * from './select' diff --git a/src/renderer/components/item.ts b/src/renderer/components/item.ts index 2f054a0..a8735e6 100644 --- a/src/renderer/components/item.ts +++ b/src/renderer/components/item.ts @@ -1,10 +1,15 @@ - -export const SettingItem = (title: string, subtitle?: string, action?: string, id?: string, visible: boolean = true) => { - return ` +export const SettingItem = ( + title: string, + subtitle?: string, + action?: string, + id?: string, + visible: boolean = true, +) => { + return `
${title} ${subtitle ? `${subtitle}` : ''}
${action ? `
${action}
` : ''} -
`; -} \ No newline at end of file +
` +} diff --git a/src/renderer/components/list.ts b/src/renderer/components/list.ts index af2d8ff..8e21954 100644 --- a/src/renderer/components/list.ts +++ b/src/renderer/components/list.ts @@ -1,10 +1,14 @@ - -export const SettingList = (items: string[], title?: string, isCollapsible: boolean = false, direction: string = 'column') => { - return ` +export const SettingList = ( + items: string[], + title?: string, + isCollapsible: boolean = false, + direction: string = 'column', +) => { + return ` ${items.join('')} -`; -} \ No newline at end of file +` +} diff --git a/src/renderer/components/option.ts b/src/renderer/components/option.ts index d134ba3..6ae1984 100644 --- a/src/renderer/components/option.ts +++ b/src/renderer/components/option.ts @@ -1,4 +1,3 @@ - export const SettingOption = (text: string, value?: string, isSelected: boolean = false) => { - return `${text}`; -} \ No newline at end of file + return `${text}` +} diff --git a/src/renderer/components/select.ts b/src/renderer/components/select.ts index ef2403a..5caf8cd 100644 --- a/src/renderer/components/select.ts +++ b/src/renderer/components/select.ts @@ -1,11 +1,11 @@ -import { SettingOption } from "./option"; +import { SettingOption } from './option' interface MouseEventExtend extends MouseEvent { - target: HTMLElement, + target: HTMLElement } // -const SelectTemplate = document.createElement('template'); +const SelectTemplate = document.createElement('template') SelectTemplate.innerHTML = ` @@ -17,61 +17,68 @@ SelectTemplate.innerHTML = ``, - ` + const parser = new DOMParser() + const doc = parser.parseFromString( + [ + '
', + ``, + `
`, - SettingList([ - SettingItem( - '正在检查 LLOneBot 更新', null, - SettingButton('请稍候', 'llonebot-update-button', 'secondary'), - ), - ]), - SettingList([ - SettingItem('启用 HTTP 服务', null, - SettingSwitch('ob11.enableHttp', config.ob11.enableHttp, {'control-display-id': 'config-ob11-httpPort'}), - ), - SettingItem('HTTP 服务监听端口', null, - `
`, - 'config-ob11-httpPort', config.ob11.enableHttp - ), - SettingItem('启用 HTTP 心跳', null, - SettingSwitch('ob11.enableHttpHeart', config.ob11.enableHttpHeart, {'control-display-id': 'config-ob11-enableHttpHeart'}), - ), - SettingItem('启用 HTTP 事件上报', null, - SettingSwitch('ob11.enableHttpPost', config.ob11.enableHttpPost, {'control-display-id': 'config-ob11-httpHosts'}), - ), - `
+ SettingList([ + SettingItem( + '正在检查 LLOneBot 更新', + null, + SettingButton('请稍候', 'llonebot-update-button', 'secondary'), + ), + ]), + SettingList([ + SettingItem( + '启用 HTTP 服务', + null, + SettingSwitch('ob11.enableHttp', config.ob11.enableHttp, { 'control-display-id': 'config-ob11-httpPort' }), + ), + SettingItem( + 'HTTP 服务监听端口', + null, + `
`, + 'config-ob11-httpPort', + config.ob11.enableHttp, + ), + SettingItem( + '启用 HTTP 心跳', + null, + SettingSwitch('ob11.enableHttpHeart', config.ob11.enableHttpHeart, { + 'control-display-id': 'config-ob11-enableHttpHeart', + }), + ), + SettingItem( + '启用 HTTP 事件上报', + null, + SettingSwitch('ob11.enableHttpPost', config.ob11.enableHttpPost, { + 'control-display-id': 'config-ob11-httpHosts', + }), + ), + `
HTTP 事件上报密钥 @@ -91,17 +106,26 @@ async function onSettingWindowCreated(view: Element) {
`, - SettingItem('启用正向 WebSocket 服务', null, - SettingSwitch('ob11.enableWs', config.ob11.enableWs, {'control-display-id': 'config-ob11-wsPort'}), - ), - SettingItem('正向 WebSocket 服务监听端口', null, - `
`, - 'config-ob11-wsPort', config.ob11.enableWs - ), - SettingItem('启用反向 WebSocket 服务', null, - SettingSwitch('ob11.enableWsReverse', config.ob11.enableWsReverse, {'control-display-id': 'config-ob11-wsHosts'}), - ), - `
+ SettingItem( + '启用正向 WebSocket 服务', + null, + SettingSwitch('ob11.enableWs', config.ob11.enableWs, { 'control-display-id': 'config-ob11-wsPort' }), + ), + SettingItem( + '正向 WebSocket 服务监听端口', + null, + `
`, + 'config-ob11-wsPort', + config.ob11.enableWs, + ), + SettingItem( + '启用反向 WebSocket 服务', + null, + SettingSwitch('ob11.enableWsReverse', config.ob11.enableWsReverse, { + 'control-display-id': 'config-ob11-wsHosts', + }), + ), + `
反向 WebSocket 监听地址 @@ -110,313 +134,303 @@ async function onSettingWindowCreated(view: Element) {
`, - SettingItem(' WebSocket 服务心跳间隔', - '控制每隔多久发送一个心跳包,单位为毫秒', - `
`, - ), - SettingItem('Access token', null, - `
`, - ), - SettingItem( - '新消息上报格式', - '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档', - SettingSelect([ - {text: '消息段', value: 'array'}, - {text: 'CQ码', value: 'string'}, - ], 'ob11.messagePostFormat', config.ob11.messagePostFormat), - ), - SettingItem( - 'ffmpeg 路径,发送语音、视频需要,同时保证ffprobe和ffmpeg在一起', ` 下载地址 , 路径:${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'}`, - SettingButton('选择ffmpeg', 'config-ffmpeg-select'), - ), - SettingItem( - '', null, - SettingButton('保存', 'config-ob11-save', 'primary'), - ) - ]), - SettingList([ - SettingItem( - '戳一戳消息, 暂时只支持Windows版的LLOneBot', - `重启QQ后生效,如果导致QQ崩溃请勿开启此项, 群戳一戳只能收到群号`, - SettingSwitch('enablePoke', config.enablePoke), - ), - SettingItem( - '使用 Base64 编码获取文件', - '调用 /get_image、/get_record、/get_file 时,没有 url 时添加 Base64 字段', - SettingSwitch('enableLocalFile2Url', config.enableLocalFile2Url), - ), - SettingItem( - '调试模式', - '开启后上报信息会添加 raw 字段以附带原始信息', - SettingSwitch('debug', config.debug), - ), - SettingItem( - '上报 Bot 自身发送的消息', - '上报 event 为 message_sent', - SettingSwitch('reportSelfMessage', config.reportSelfMessage), - ), - SettingItem( - '自动删除收到的文件', - '在收到文件后的指定时间内删除该文件', - SettingSwitch('autoDeleteFile', config.autoDeleteFile, {'control-display-id': 'config-auto-delete-file-second'}), - ), - SettingItem( - '自动删除文件时间', - '单位为秒', - `
`, - 'config-auto-delete-file-second', config.autoDeleteFile - ), - SettingItem( - '写入日志', - `将日志文件写入插件的数据文件夹`, - SettingSwitch('log', config.log), - ), - SettingItem( - '日志文件目录', - `${window.LiteLoader.plugins['LLOneBot'].path.data}/logs`, - SettingButton('打开', 'config-open-log-path'), - ), - ]), - SettingList([ - SettingItem( - 'GitHub 仓库', - `https://github.com/LLOneBot/LLOneBot`, - SettingButton('点个星星', 'open-github'), - ), - SettingItem( - 'LLOneBot 文档', - `https://llonebot.github.io/`, - SettingButton('看看文档', 'open-docs'), - ), - SettingItem( - 'Telegram 群', - `https://t.me/+nLZEnpne-pQ1OWFl`, - SettingButton('进去逛逛', 'open-telegram'), - ), - SettingItem( - 'QQ 群', - `545402644`, - SettingButton('我要进去', 'open-qq-group'), - ), - ]), - '
', - ].join(''), "text/html"); + SettingItem( + ' WebSocket 服务心跳间隔', + '控制每隔多久发送一个心跳包,单位为毫秒', + `
`, + ), + SettingItem( + 'Access token', + null, + `
`, + ), + SettingItem( + '新消息上报格式', + '如客户端无特殊需求推荐保持默认设置,两者的详细差异可参考 OneBot v11 文档', + SettingSelect( + [ + { text: '消息段', value: 'array' }, + { text: 'CQ码', value: 'string' }, + ], + 'ob11.messagePostFormat', + config.ob11.messagePostFormat, + ), + ), + SettingItem( + 'ffmpeg 路径,发送语音、视频需要,同时保证ffprobe和ffmpeg在一起', + ` 下载地址 , 路径:${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'}`, + SettingButton('选择ffmpeg', 'config-ffmpeg-select'), + ), + SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), + ]), + SettingList([ + SettingItem( + '戳一戳消息, 暂时只支持Windows版的LLOneBot', + `重启QQ后生效,如果导致QQ崩溃请勿开启此项, 群戳一戳只能收到群号`, + SettingSwitch('enablePoke', config.enablePoke), + ), + SettingItem( + '使用 Base64 编码获取文件', + '调用 /get_image、/get_record、/get_file 时,没有 url 时添加 Base64 字段', + SettingSwitch('enableLocalFile2Url', config.enableLocalFile2Url), + ), + SettingItem('调试模式', '开启后上报信息会添加 raw 字段以附带原始信息', SettingSwitch('debug', config.debug)), + SettingItem( + '上报 Bot 自身发送的消息', + '上报 event 为 message_sent', + SettingSwitch('reportSelfMessage', config.reportSelfMessage), + ), + SettingItem( + '自动删除收到的文件', + '在收到文件后的指定时间内删除该文件', + SettingSwitch('autoDeleteFile', config.autoDeleteFile, { + 'control-display-id': 'config-auto-delete-file-second', + }), + ), + SettingItem( + '自动删除文件时间', + '单位为秒', + `
`, + 'config-auto-delete-file-second', + config.autoDeleteFile, + ), + SettingItem('写入日志', `将日志文件写入插件的数据文件夹`, SettingSwitch('log', config.log)), + SettingItem( + '日志文件目录', + `${window.LiteLoader.plugins['LLOneBot'].path.data}/logs`, + SettingButton('打开', 'config-open-log-path'), + ), + ]), + SettingList([ + SettingItem('GitHub 仓库', `https://github.com/LLOneBot/LLOneBot`, SettingButton('点个星星', 'open-github')), + SettingItem('LLOneBot 文档', `https://llonebot.github.io/`, SettingButton('看看文档', 'open-docs')), + SettingItem('Telegram 群', `https://t.me/+nLZEnpne-pQ1OWFl`, SettingButton('进去逛逛', 'open-telegram')), + SettingItem('QQ 群', `545402644`, SettingButton('我要进去', 'open-qq-group')), + ]), + '
', + ].join(''), + 'text/html', + ) - const showError = async () => { - await (new Promise((res) => setTimeout(() => res(true), 1000))); + const showError = async () => { + await new Promise((res) => setTimeout(() => res(true), 1000)) - const errDom = document.querySelector('#llonebot-error') || doc.querySelector('#llonebot-error'); - const errCodeDom = errDom.querySelector('code'); - const errMsg = await window.llonebot.getError(); + const errDom = document.querySelector('#llonebot-error') || doc.querySelector('#llonebot-error') + const errCodeDom = errDom.querySelector('code') + const errMsg = await window.llonebot.getError() - if (!errMsg){ - errDom.classList.remove('show'); - } - else { - errDom.classList.add('show'); - } - errCodeDom.innerHTML = errMsg; + if (!errMsg) { + errDom.classList.remove('show') + } else { + errDom.classList.add('show') } - showError().then(); + errCodeDom.innerHTML = errMsg + } + showError().then() - // 外链按钮 - doc.querySelector('#open-github').addEventListener('click', () => { - window.LiteLoader.api.openExternal('https://github.com/LLOneBot/LLOneBot') - }) - doc.querySelector('#open-telegram').addEventListener('click', () => { - window.LiteLoader.api.openExternal('https://t.me/+nLZEnpne-pQ1OWFl') - }) - doc.querySelector('#open-qq-group').addEventListener('click', () => { - window.LiteLoader.api.openExternal('https://qm.qq.com/q/bDnHRG38aI') - }) - doc.querySelector('#open-docs').addEventListener('click', () => { - window.LiteLoader.api.openExternal('https://llonebot.github.io/') - }) - // 生成反向地址列表 - const buildHostListItem = (type: string, host: string, index: number, inputAttrs: any = {}) => { - const dom = { - container: document.createElement('setting-item'), - input: document.createElement('input'), - inputContainer: document.createElement('div'), - deleteBtn: document.createElement('setting-button'), - }; - dom.container.classList.add('setting-host-list-item'); - dom.container.dataset.direction = 'row'; - Object.assign(dom.input, inputAttrs); - dom.input.classList.add('q-input__inner'); - dom.input.type = 'url'; - dom.input.value = host; - dom.input.addEventListener('input', () => { - ob11Config[type][index] = dom.input.value; - }); - - dom.inputContainer.classList.add('q-input'); - dom.inputContainer.appendChild(dom.input); - - dom.deleteBtn.innerHTML = '删除'; - dom.deleteBtn.dataset.type = 'secondary'; - dom.deleteBtn.addEventListener('click', () => { - ob11Config[type].splice(index, 1); - initReverseHost(type); - }); - - dom.container.appendChild(dom.inputContainer); - dom.container.appendChild(dom.deleteBtn); - - return dom.container; - }; - const buildHostList = (hosts: string[], type: string, inputAttr: any = {}) => { - const result: HTMLElement[] = []; - - hosts.forEach((host, index) => { - result.push(buildHostListItem(type, host, index, inputAttr)); - }); - - return result; - }; - const addReverseHost = (type: string, doc: Document = document, inputAttr: any = {}) => { - const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`); - hostContainerDom.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)); - ob11Config[type].push(''); - }; - const initReverseHost = (type: string, doc: Document = document) => { - const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`); - [...hostContainerDom.childNodes].forEach(dom => dom.remove()); - buildHostList(ob11Config[type], type).forEach(dom => { - hostContainerDom.appendChild(dom); - }); - }; - initReverseHost('httpHosts', doc); - initReverseHost('wsHosts', doc); - - doc.querySelector('#config-ob11-httpHosts-add').addEventListener('click', () => addReverseHost('httpHosts', document, {'placeholder': '如:http://127.0.0.1:5140/onebot'})); - doc.querySelector('#config-ob11-wsHosts-add').addEventListener('click', () => addReverseHost('wsHosts', document, {'placeholder': '如:ws://127.0.0.1:5140/onebot'})); - - doc.querySelector('#config-ffmpeg-select').addEventListener('click', () => { - window.llonebot.selectFile() - .then(path => { - if (!isEmpty(path)) { - setConfig('ffmpeg', path); - document.querySelector('#config-ffmpeg-path-text').innerHTML = path; - } - }) - }); - - doc.querySelector('#config-open-log-path').addEventListener('click', () => { - window.LiteLoader.api.openPath(window.LiteLoader.plugins['LLOneBot'].path.data); + // 外链按钮 + doc.querySelector('#open-github').addEventListener('click', () => { + window.LiteLoader.api.openExternal('https://github.com/LLOneBot/LLOneBot') + }) + doc.querySelector('#open-telegram').addEventListener('click', () => { + window.LiteLoader.api.openExternal('https://t.me/+nLZEnpne-pQ1OWFl') + }) + doc.querySelector('#open-qq-group').addEventListener('click', () => { + window.LiteLoader.api.openExternal('https://qm.qq.com/q/bDnHRG38aI') + }) + doc.querySelector('#open-docs').addEventListener('click', () => { + window.LiteLoader.api.openExternal('https://llonebot.github.io/') + }) + // 生成反向地址列表 + const buildHostListItem = (type: string, host: string, index: number, inputAttrs: any = {}) => { + const dom = { + container: document.createElement('setting-item'), + input: document.createElement('input'), + inputContainer: document.createElement('div'), + deleteBtn: document.createElement('setting-button'), + } + dom.container.classList.add('setting-host-list-item') + dom.container.dataset.direction = 'row' + Object.assign(dom.input, inputAttrs) + dom.input.classList.add('q-input__inner') + dom.input.type = 'url' + dom.input.value = host + dom.input.addEventListener('input', () => { + ob11Config[type][index] = dom.input.value }) - // 开关 - doc.querySelectorAll('setting-switch[data-config-key]').forEach((dom: HTMLElement) => { - dom.addEventListener('click', () => { - const active = dom.getAttribute('is-active') === null; + dom.inputContainer.classList.add('q-input') + dom.inputContainer.appendChild(dom.input) - setConfig(dom.dataset.configKey, active); + dom.deleteBtn.innerHTML = '删除' + dom.deleteBtn.dataset.type = 'secondary' + dom.deleteBtn.addEventListener('click', () => { + ob11Config[type].splice(index, 1) + initReverseHost(type) + }) - if (active) dom.setAttribute('is-active', ''); - else dom.removeAttribute('is-active'); + dom.container.appendChild(dom.inputContainer) + dom.container.appendChild(dom.deleteBtn) - if (!isEmpty(dom.dataset.controlDisplayId)) { - const displayDom = document.querySelector(`#${dom.dataset.controlDisplayId}`); - if (active) displayDom.removeAttribute('is-hidden'); - else displayDom.setAttribute('is-hidden', ''); - } - }); - }); + return dom.container + } + const buildHostList = (hosts: string[], type: string, inputAttr: any = {}) => { + const result: HTMLElement[] = [] - // 输入框 - doc.querySelectorAll('setting-item .q-input input.q-input__inner[data-config-key]').forEach((dom: HTMLInputElement) => { - dom.addEventListener('input', () => { - const Type = dom.getAttribute('type'); - const configKey = dom.dataset.configKey; - const configValue = Type === 'number' ? (parseInt(dom.value) >= 1 ? parseInt(dom.value) : 1) : dom.value; + hosts.forEach((host, index) => { + result.push(buildHostListItem(type, host, index, inputAttr)) + }) - setConfig(configKey, configValue); - }); - }); + return result + } + const addReverseHost = (type: string, doc: Document = document, inputAttr: any = {}) => { + const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) + hostContainerDom.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)) + ob11Config[type].push('') + } + const initReverseHost = (type: string, doc: Document = document) => { + const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) + ;[...hostContainerDom.childNodes].forEach((dom) => dom.remove()) + buildHostList(ob11Config[type], type).forEach((dom) => { + hostContainerDom.appendChild(dom) + }) + } + initReverseHost('httpHosts', doc) + initReverseHost('wsHosts', doc) - // 下拉框 - doc.querySelectorAll('ob-setting-select[data-config-key]').forEach((dom: HTMLElement) => { - dom.addEventListener('selected', (e: CustomEvent) => { - const configKey = dom.dataset.configKey; - const configValue = e.detail.value; + doc + .querySelector('#config-ob11-httpHosts-add') + .addEventListener('click', () => + addReverseHost('httpHosts', document, { placeholder: '如:http://127.0.0.1:5140/onebot' }), + ) + doc + .querySelector('#config-ob11-wsHosts-add') + .addEventListener('click', () => + addReverseHost('wsHosts', document, { placeholder: '如:ws://127.0.0.1:5140/onebot' }), + ) - setConfig(configKey, configValue); - }); - }); + doc.querySelector('#config-ffmpeg-select').addEventListener('click', () => { + window.llonebot.selectFile().then((path) => { + if (!isEmpty(path)) { + setConfig('ffmpeg', path) + document.querySelector('#config-ffmpeg-path-text').innerHTML = path + } + }) + }) - // 保存按钮 - doc.querySelector('#config-ob11-save').addEventListener('click', () => { - config.ob11 = ob11Config; + doc.querySelector('#config-open-log-path').addEventListener('click', () => { + window.LiteLoader.api.openPath(window.LiteLoader.plugins['LLOneBot'].path.data) + }) - window.llonebot.setConfig(false, config); - // window.location.reload(); - showError().then() - alert('保存成功'); - }); + // 开关 + doc.querySelectorAll('setting-switch[data-config-key]').forEach((dom: HTMLElement) => { + dom.addEventListener('click', () => { + const active = dom.getAttribute('is-active') === null - doc.body.childNodes.forEach(node => { - view.appendChild(node); - }); - // 更新逻辑 - async function checkVersionFunc(ResultVersion: CheckVersion) { - const titleDom = view.querySelector("#llonebot-update-title")!; - const buttonDom = view.querySelector("#llonebot-update-button")!; + setConfig(dom.dataset.configKey, active) - if (ResultVersion.version === "") { - titleDom.innerHTML = "检查更新失败"; - buttonDom.innerHTML = "点击重试"; + if (active) dom.setAttribute('is-active', '') + else dom.removeAttribute('is-active') - buttonDom.addEventListener("click", async () => { - window.llonebot.checkVersion().then(checkVersionFunc); - }); + if (!isEmpty(dom.dataset.controlDisplayId)) { + const displayDom = document.querySelector(`#${dom.dataset.controlDisplayId}`) + if (active) displayDom.removeAttribute('is-hidden') + else displayDom.setAttribute('is-hidden', '') + } + }) + }) - return; - } - if (!ResultVersion.result) { - titleDom.innerHTML = "当前已是最新版本 v" + ResultVersion.version; - buttonDom.innerHTML = "无需更新"; + // 输入框 + doc + .querySelectorAll('setting-item .q-input input.q-input__inner[data-config-key]') + .forEach((dom: HTMLInputElement) => { + dom.addEventListener('input', () => { + const Type = dom.getAttribute('type') + const configKey = dom.dataset.configKey + const configValue = Type === 'number' ? (parseInt(dom.value) >= 1 ? parseInt(dom.value) : 1) : dom.value + + setConfig(configKey, configValue) + }) + }) + + // 下拉框 + doc.querySelectorAll('ob-setting-select[data-config-key]').forEach((dom: HTMLElement) => { + dom.addEventListener('selected', (e: CustomEvent) => { + const configKey = dom.dataset.configKey + const configValue = e.detail.value + + setConfig(configKey, configValue) + }) + }) + + // 保存按钮 + doc.querySelector('#config-ob11-save').addEventListener('click', () => { + config.ob11 = ob11Config + + window.llonebot.setConfig(false, config) + // window.location.reload(); + showError().then() + alert('保存成功') + }) + + doc.body.childNodes.forEach((node) => { + view.appendChild(node) + }) + // 更新逻辑 + async function checkVersionFunc(ResultVersion: CheckVersion) { + const titleDom = view.querySelector('#llonebot-update-title')! + const buttonDom = view.querySelector('#llonebot-update-button')! + + if (ResultVersion.version === '') { + titleDom.innerHTML = '检查更新失败' + buttonDom.innerHTML = '点击重试' + + buttonDom.addEventListener('click', async () => { + window.llonebot.checkVersion().then(checkVersionFunc) + }) + + return + } + if (!ResultVersion.result) { + titleDom.innerHTML = '当前已是最新版本 v' + ResultVersion.version + buttonDom.innerHTML = '无需更新' + } else { + titleDom.innerHTML = '已检测到最新版本 v' + ResultVersion.version + buttonDom.innerHTML = '点击更新' + buttonDom.dataset.type = 'primary' + + const update = async () => { + buttonDom.innerHTML = '正在更新中...' + const result = await window.llonebot.updateLLOneBot() + if (result) { + buttonDom.innerHTML = '更新完成,请重启' } else { - titleDom.innerHTML = "已检测到最新版本 v" + ResultVersion.version; - buttonDom.innerHTML = "点击更新"; - buttonDom.dataset.type = 'primary'; - - const update = async () => { - buttonDom.innerHTML = "正在更新中..."; - const result = await window.llonebot.updateLLOneBot(); - if (result) { - buttonDom.innerHTML = "更新完成,请重启"; - } else { - buttonDom.innerHTML = "更新失败,前往仓库下载"; - } - - buttonDom.removeEventListener("click", update); - } - buttonDom.addEventListener("click", update); + buttonDom.innerHTML = '更新失败,前往仓库下载' } - } - window.llonebot.checkVersion().then(checkVersionFunc); - window.addEventListener('beforeunload', (event) => { - if (JSON.stringify(ob11Config) === JSON.stringify(config.ob11)) return; - config.ob11 = ob11Config; - window.llonebot.setConfig(true, config); - }); + buttonDom.removeEventListener('click', update) + } + buttonDom.addEventListener('click', update) + } + } + window.llonebot.checkVersion().then(checkVersionFunc) + window.addEventListener('beforeunload', (event) => { + if (JSON.stringify(ob11Config) === JSON.stringify(config.ob11)) return + config.ob11 = ob11Config + window.llonebot.setConfig(true, config) + }) } function init() { - const hash = location.hash - if (hash === '#/blank') { - - } + const hash = location.hash + if (hash === '#/blank') { + } } if (location.hash === '#/blank') { - (window as any).navigation.addEventListener('navigatesuccess', init, {once: true}) + ;(window as any).navigation.addEventListener('navigatesuccess', init, { once: true }) } else { - init() + init() } -export { - onSettingWindowCreated -} +export { onSettingWindowCreated } diff --git a/src/renderer/style.css b/src/renderer/style.css index f78e0af..5273955 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -42,8 +42,8 @@ setting-item .q-input .q-input__inner { padding: 0px 8px; } -setting-item .q-input input[type=number].q-input__inner::-webkit-outer-spin-button, -setting-item .q-input input[type=number].q-input__inner::-webkit-inner-spin-button { +setting-item .q-input input[type='number'].q-input__inner::-webkit-outer-spin-button, +setting-item .q-input input[type='number'].q-input__inner::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } @@ -177,4 +177,4 @@ ob-setting-select::part(option-list) { #llonebot-error.show { display: block; -} \ No newline at end of file +} diff --git a/src/version.ts b/src/version.ts index e9b9090..9d5ae7c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = "3.23.0" \ No newline at end of file +export const version = '3.23.0' diff --git a/tsconfig.json b/tsconfig.json index 20056ca..c149942 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,15 +7,9 @@ "esModuleInterop": true, "allowJs": true, "allowSyntheticDefaultImports": true, - "moduleResolution": "node", + "moduleResolution": "node" // "sourceMap": true }, - "include": [ - "src/*", - "src/**/*", - "scripts/*" - ], - "exclude": [ - "node_modules", - ] -} \ No newline at end of file + "include": ["src/*", "src/**/*", "scripts/*"], + "exclude": ["node_modules"] +} From b78bd235f98b6b59b5ba2dfa5a9565bfd1aa49d5 Mon Sep 17 00:00:00 2001 From: student_2333 Date: Tue, 30 Apr 2024 14:13:40 +0800 Subject: [PATCH 3/3] fix --- src/ntqqapi/types/msg.ts | 2 +- src/onebot11/types.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index 15582a9..4d88c52 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -252,6 +252,7 @@ export interface FaceElement { export interface MarketFaceElement { emojiPackageId: number + faceName: string emojiId: string key: string } @@ -403,7 +404,6 @@ export interface RawMessage { arkElement: ArkElement grayTipElement: GrayTipElement faceElement: FaceElement - mfaceElement: MarketFaceElement videoElement: VideoElement fileElement: FileElement marketFaceElement: MarketFaceElement diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 0423af2..fabf632 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -126,9 +126,10 @@ export enum OB11MessageDataType { export interface OB11MessageMFace { type: OB11MessageDataType.mface data: { - emojiPackageId: number - emojiId: string + emoji_package_id: number + emoji_id: string key: string + text?: string // 仅收 } }