├── .commitlintrc.js ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── issues.yml │ ├── pr-merged.yml │ └── pr-open-check.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── .versionrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── License ├── README.md ├── babel.config.json ├── example ├── README.md ├── index.js ├── package-lock.json └── package.json ├── jest.config.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts ├── dev.js ├── gen │ ├── index.js │ ├── sdk.js │ ├── template │ │ ├── api.handlebars │ │ └── type.handlebars │ └── utils.js └── publish.js ├── src ├── bot.ts ├── client │ ├── client.ts │ ├── session │ │ ├── session-manager.ts │ │ └── session.ts │ └── websocket │ │ └── websocket.ts ├── index.ts ├── openapi │ ├── openapi.ts │ └── v1 │ │ ├── announce.ts │ │ ├── audio.ts │ │ ├── channel-permissions.ts │ │ ├── channel.ts │ │ ├── direct-message.ts │ │ ├── guild-permissions.ts │ │ ├── guild.ts │ │ ├── interaction.ts │ │ ├── me.ts │ │ ├── member.ts │ │ ├── message.ts │ │ ├── mute.ts │ │ ├── openapi.ts │ │ ├── pins-message.ts │ │ ├── reaction.ts │ │ ├── resource.ts │ │ ├── role.ts │ │ └── schedule.ts ├── types │ ├── index.ts │ ├── openapi │ │ ├── index.ts │ │ └── v1 │ │ │ ├── announce.ts │ │ │ ├── audio.ts │ │ │ ├── channel-permission.ts │ │ │ ├── channel.ts │ │ │ ├── direct-message.ts │ │ │ ├── guild-permission.ts │ │ │ ├── guild.ts │ │ │ ├── interaction.ts │ │ │ ├── me.ts │ │ │ ├── member.ts │ │ │ ├── message.ts │ │ │ ├── mute.ts │ │ │ ├── pins-message.ts │ │ │ ├── reaction.ts │ │ │ ├── role.ts │ │ │ └── schedule.ts │ └── websocket-types.ts └── utils │ ├── constants.ts │ ├── logger.ts │ └── utils.ts ├── test ├── openapi │ ├── config.ts │ └── v1 │ │ ├── audio.spec.ts │ │ ├── channel.spec.ts │ │ ├── channer-permissions.spec.ts │ │ ├── direct-message.spec.ts │ │ ├── guild.spec.ts │ │ ├── me.spec.ts │ │ ├── member.spec.ts │ │ ├── message.spec.ts │ │ ├── openapi.spec.ts │ │ ├── reaction.spec.ts │ │ ├── resource.spec.ts │ │ └── roles.spec.ts ├── utils │ └── utils.spec.ts └── websocket │ └── websocket.spec.ts └── tsconfig.json /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | 17 | [*.yml] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | example 4 | es 5 | lib 6 | typings -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['alloy', 'alloy/typescript'], 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | sourceType: 'module', 6 | ecmaFeatures: { 7 | jsx: true, 8 | }, 9 | }, 10 | plugins: ['jest'], 11 | env: { 12 | jest: true, 13 | }, 14 | rules: { 15 | '@typescript-eslint/indent': 'off', 16 | '@typescript-eslint/explicit-member-accessibility': 'off', 17 | '@typescript-eslint/no-var-requires': 'off', 18 | '@typescript-eslint/consistent-type-assertions': 'off', 19 | '@typescript-eslint/typedef': 'off', 20 | 'no-new-func': 'off', 21 | '@typescript-eslint/no-empty-interface': 'off', 22 | complexity: 'off', 23 | '@typescript-eslint/no-this-alias': 'off', 24 | '@typescript-eslint/no-require-imports': 'off', 25 | '@typescript-eslint/prefer-optional-chain': 'off', 26 | 'max-params': 'off', 27 | 'no-param-reassign': 'off', 28 | 'no-trailing-spaces': [ 29 | 'error', 30 | { 31 | skipBlankLines: true, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: Issues Notifier 2 | on: 3 | issues: 4 | types: [opened, reopened] 5 | jobs: 6 | add-assigness: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Work Weixin Notifier 10 | uses: jerray/work-weixin-notifier@v1.1.0 11 | with: 12 | key: ${{ secrets.weixin_bot_key }} 13 | content: | 14 | # 【NodeSDK】 ISSUE [ ${{github.event.issue.state}} ] 15 | 16 | > Detail: [${{ github.event.issue.html_url }}](${{ github.event.issue.html_url }}) 17 | > Title: ${{ github.event.issue.title }} 18 | > Body: ${{github.event.issue.body}} 19 | > User: ${{ github.event.issue.user.login }} 20 | > Attention: <@nillwang> <@jackqqxu> 21 | 22 | type: markdown 23 | status: ${{ job.status }} 24 | -------------------------------------------------------------------------------- /.github/workflows/pr-merged.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: ['main'] 4 | types: [closed] 5 | 6 | jobs: 7 | pr-approved-notice: 8 | runs-on: ubuntu-latest 9 | if: github.event.pull_request.merged == true 10 | steps: 11 | - name: Work Weixin Notifier 12 | uses: jerray/work-weixin-notifier@v1.1.0 13 | with: 14 | key: ${{ secrets.weixin_bot_key }} 15 | content: | 16 | # 【NodeSDK】 PR [ merged ] 17 | 18 | > 标题: ${{ github.event.pull_request.title }} 19 | > 合并人: ${{ github.event.pull_request.merged_by.login }} 20 | 21 | > 详情: [${{github.event.pull_request.html_url}}](${{github.event.pull_request.html_url}}) 22 | > 请关注: <@nillwang> <@jackqqxu> 23 | 24 | type: markdown 25 | status: ${{ job.status }} 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-open-check.yml: -------------------------------------------------------------------------------- 1 | name: PR Check & Notifier 2 | on: 3 | pull_request: 4 | branches: [main] 5 | types: [opened, edited, reopened, synchronize] 6 | 7 | jobs: 8 | add-assigness: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [12.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | 20 | - uses: wagoid/commitlint-github-action@v4 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: 'npm' 27 | - run: npm i 28 | - run: npm run lint 29 | 30 | - name: Work Weixin Notifier 31 | uses: jerray/work-weixin-notifier@v1.1.0 32 | with: 33 | key: ${{ secrets.weixin_bot_key }} 34 | content: | 35 | # 【NodeSDK】 PR [ ${{github.event.pull_request.state}} ] 36 | 37 | > 标题: ${{ github.event.pull_request.title }} 38 | > 发起人: ${{ github.event.pull_request.user.login }} 39 | 40 | > 详情: [${{github.event.pull_request.html_url}}](${{github.event.pull_request.html_url}}) 41 | > 请关注: <@nillwang> <@jackqqxu> 42 | 43 | type: markdown 44 | status: ${{ job.status }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | .DS_Store 5 | *.log 6 | .vscode 7 | .idea 8 | dist 9 | compiled 10 | .awcache 11 | .rpt2_cache 12 | docs 13 | .cache 14 | typings 15 | es 16 | lib 17 | test-config 18 | package-lock.json 19 | example/config.json 20 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | npx --no-install commitlint --edit $1 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | npx lint-staged -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .npmignore 4 | .DS_Store 5 | .vscode 6 | dist 7 | webpack.config.js 8 | test 9 | docs 10 | 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | **/*.ejs 3 | **/*.html 4 | package.json -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // max 120 characters per line 3 | printWidth: 120, 4 | // use 2 spaces for indentation 5 | tabWidth: 2, 6 | // use spaces instead of indentations 7 | useTabs: false, 8 | // semicolon at the end of the line 9 | semi: true, 10 | // use single quotes 11 | singleQuote: true, 12 | // object's key is quoted only when necessary 13 | quoteProps: 'as-needed', 14 | // use double quotes instead of single quotes in jsx 15 | jsxSingleQuote: false, 16 | // no comma at the end 17 | trailingComma: 'all', 18 | // spaces are required at the beginning and end of the braces 19 | bracketSpacing: true, 20 | // end tag of jsx need to wrap 21 | bracketSameLine: false, 22 | // brackets are required for arrow function parameter, even when there is only one parameter 23 | arrowParens: 'always', 24 | // format the entire contents of the file 25 | rangeStart: 0, 26 | rangeEnd: Infinity, 27 | // no need to write the beginning @prettier of the file 28 | requirePragma: false, 29 | // No need to automatically insert @prettier at the beginning of the file 30 | insertPragma: false, 31 | // use default break criteria 32 | proseWrap: 'preserve', 33 | // decide whether to break the html according to the display style 34 | htmlWhitespaceSensitivity: 'css', 35 | // vue files script and style tags indentation 36 | vueIndentScriptAndStyle: false, 37 | // lf for newline 38 | endOfLine: 'lf', 39 | // formats quoted code embedded 40 | embeddedLanguageFormatting: 'auto', 41 | }; 42 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "feat", 5 | "section": "Features" 6 | }, 7 | { 8 | "type": "fix", 9 | "section": "Bug Fixes" 10 | }, 11 | { 12 | "type": "refactor", 13 | "section": "Refactors" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // "editor.formatOnSave": true, 3 | "files.eol": "\n", 4 | "editor.tabSize": 2, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "eslint.validate": ["javascript", "typescript",], 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": true, 9 | "source.fixAll": true, 10 | "source.fixAll.stylelint": true 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [2.9.4](https://github.com/tencent-connect/bot-node-sdk/compare/v2.9.3...v2.9.4) (2022-05-18) 6 | 7 | 8 | ### Features 9 | 10 | * 事件分发添加事件ID ([dd9800e](https://github.com/tencent-connect/bot-node-sdk/commit/dd9800ec1cc8e0f305d70baab60dc31bf5fc9d91)) 11 | * 修复依赖版本问题 ([1be18a3](https://github.com/tencent-connect/bot-node-sdk/commit/1be18a300ec6241b08dcdf56ae7560a0f5863cb2)) 12 | * 新增机器人获取表情表态成员列表接口 ([b4739d7](https://github.com/tencent-connect/bot-node-sdk/commit/b4739d719db4d860d2d96fb5f7a6476fc4788053)) 13 | 14 | ### [2.9.3](https://github.com/tencent-connect/bot-node-sdk/compare/v2.9.1...v2.9.3) (2022-05-12) 15 | 16 | 17 | ### Features 18 | 19 | * 事件分发添加事件ID ([0e5d4fb](https://github.com/tencent-connect/bot-node-sdk/commit/0e5d4fbbb8745b8628b037ad8c2d6d58676b176e)) 20 | * 撤回消息API补充hidetip参数 ([#57](https://github.com/tencent-connect/bot-node-sdk/issues/57)) ([2bddf51](https://github.com/tencent-connect/bot-node-sdk/commit/2bddf513b4ee490f669f638abb7cc371e799b5a9)) 21 | * 补齐INTERACTION事件,增加异步更新交互数据接口 ([#59](https://github.com/tencent-connect/bot-node-sdk/issues/59)) ([2f1f184](https://github.com/tencent-connect/bot-node-sdk/commit/2f1f18446553ab9943ece7d97eb1603e9c05ac93)) 22 | 23 | ### [2.9.1](https://github.com/tencent-connect/bot-node-sdk/compare/v2.9.0...v2.9.1) (2022-03-05) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * 修正表情表态类型定义 ([6d65236](https://github.com/tencent-connect/bot-node-sdk/commit/6d652366150be5e276a3dc4efcea8e182b8a7335)) 29 | * 修正拼写错误 ([ff4133f](https://github.com/tencent-connect/bot-node-sdk/commit/ff4133fd4c43f5ca95fa268ec5ca928d07a2e8f2)) 30 | 31 | ## [2.9.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.8.2...v2.9.0) (2022-03-03) 32 | 33 | 34 | ### Features 35 | 36 | * 补充ChannelAPI相关字段类型 ([7839499](https://github.com/tencent-connect/bot-node-sdk/commit/783949960b305e7099e9d6fd3c93df8c5189073e)) 37 | * 添加设置公告推荐子频道API ([b085c23](https://github.com/tencent-connect/bot-node-sdk/commit/b085c23ae3e9e04ec6ea072e3c5a634b4701beca)) 38 | * 新增发表表情表态,设置精华消息API ([a2bed09](https://github.com/tencent-connect/bot-node-sdk/commit/a2bed09ab365bc5c0b777a7e537b6de5652cb9f0)) 39 | 40 | ### [2.8.2](https://github.com/tencent-connect/bot-node-sdk/compare/v2.8.0...v2.8.2) (2022-02-25) 41 | 42 | 43 | ### Features 44 | 45 | * 添加api辅助生成脚本 ([176fe90](https://github.com/tencent-connect/bot-node-sdk/commit/176fe908f6d5b6cf47e431a23c95b7485148c6ea)) 46 | * 添加npm发布脚本 ([2f8af4b](https://github.com/tencent-connect/bot-node-sdk/commit/2f8af4b3ee8355c012f5a46f67a182d0e4a00d49)) 47 | * 完善messageAPI类型定义 ([5b89807](https://github.com/tencent-connect/bot-node-sdk/commit/5b89807311e629a363a1ce933f2f69ffdf6e8290)) 48 | * 消息API类型完善 ([91ec2bf](https://github.com/tencent-connect/bot-node-sdk/commit/91ec2bf124a3005793fdc4266c61e1bcac84ff12)) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * 修正文件命名 ([bb3b4c2](https://github.com/tencent-connect/bot-node-sdk/commit/bb3b4c2f98994f272145d0b421a5a8d423c9f2ac)) 54 | 55 | ## [2.8.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.7.0...v2.8.0) (2022-01-27) 56 | 57 | 58 | ### Features 59 | 60 | * 添加频道权限API ([8d8c5d0](https://github.com/tencent-connect/bot-node-sdk/commit/8d8c5d0dddc0d5f44820d587ea8f86794d542516)) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * 修复无限重连以及ws重复监听的问题 ([1203f94](https://github.com/tencent-connect/bot-node-sdk/commit/1203f947af7ad5f90916d030177076c9cde94d1d)) 66 | * 修正example ([bc11bb3](https://github.com/tencent-connect/bot-node-sdk/commit/bc11bb3e4c2938d2061904b8d827621d5db0030b)) 67 | * 修正example ([d19ac9c](https://github.com/tencent-connect/bot-node-sdk/commit/d19ac9cbeaf63e070e592fe5341f78bfc0d6fbf7)) 68 | 69 | ## [2.7.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.6.0...v2.7.0) (2022-01-23) 70 | 71 | 72 | ### Features 73 | 74 | * readme增加贡献者展示 ([0398bff](https://github.com/tencent-connect/bot-node-sdk/commit/0398bff84a0bbdf46c0652f8f4f903d557e5bd09)) 75 | * 支持沙箱 ([a6339fa](https://github.com/tencent-connect/bot-node-sdk/commit/a6339fa9d32c3fd427ea204b306ea03b1478f320)) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * 接收数据类型为buffer时不被识别问题 ([dbe2bce](https://github.com/tencent-connect/bot-node-sdk/commit/dbe2bce45259c58c3cd802ef64f83ee0104d6112)), closes [#733e6111bdc3b853b252f3c0defc819f3e83309](https://github.com/tencent-connect/bot-node-sdk/issues/733e6111bdc3b853b252f3c0defc819f3e83309) 81 | 82 | ## [2.6.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.5.1...v2.6.0) (2022-01-21) 83 | 84 | 85 | ### Features 86 | 87 | * 完善私信API ([68f6b03](https://github.com/tencent-connect/bot-node-sdk/commit/68f6b03af0cdf65b3afe91832f81854a52f03e3f)) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * 修复example中的config拼写错误 ([6965b88](https://github.com/tencent-connect/bot-node-sdk/commit/6965b88024acc8431c71b0c5944baf4fd67b3c1a)) 93 | 94 | ### [2.5.1](https://github.com/tencent-connect/bot-node-sdk/compare/v2.5.0...v2.5.1) (2022-01-14) 95 | 96 | 97 | ### Refactors 98 | 99 | * **license:** 调整license ([5fae53a](https://github.com/tencent-connect/bot-node-sdk/commit/5fae53a8a13b89c01d926c7dd06a79c738864ab9)) 100 | 101 | ## [2.5.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.4.0...v2.5.0) (2022-01-12) 102 | 103 | ### Features 104 | 105 | * 完善intents事件 ([fdd8569](https://github.com/tencent-connect/bot-node-sdk/commit/fdd8569ab3c7256ceb0125f62a60f45544f3f1ac)) 106 | 107 | ## [2.4.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.3.0...v2.4.0) (2022-01-12) 108 | 109 | ### Features 110 | 111 | * 获取频道列表参数补齐 ([bbfc4e7](https://github.com/tencent-connect/bot-node-sdk/commit/bbfc4e72c9766a1487089cfda4f0897dbb53eb08)) 112 | * 扩展创建子频道参数,支持创建私密子频道 ([38ee7a4](https://github.com/tencent-connect/bot-node-sdk/commit/38ee7a42237d0deb7ae70a8ca2aeb2d826fe8d16)) 113 | 114 | ## [2.3.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.2.0...v2.3.0) (2022-01-11) 115 | 116 | ### Features 117 | 118 | * 添加日程API ([ad05262](https://github.com/tencent-connect/bot-node-sdk/commit/ad05262947b045c93dd68502f7f74c0c0bf7d478)) 119 | 120 | ## [2.2.0](https://github.com/tencent-connect/bot-node-sdk/compare/v2.1.0...v2.2.0) (2022-01-08) 121 | 122 | ### Features 123 | 124 | * 添加公告API ([dfd33c9](https://github.com/tencent-connect/bot-node-sdk/commit/dfd33c97b1475278c258cb3d992dcbc0108d839b)) 125 | 126 | ## [2.1.0](https://github.com/tencent-connect/bot-node-sdk/compare/v1.5.0...v2.1.0) (2022-01-08) 127 | 128 | ### Features 129 | 130 | * 添加禁言API ([9d5e47b](https://github.com/tencent-connect/bot-node-sdk/commit/9d5e47bf56ad90358c4c8cba1ceb7dc18e765ad4)) 131 | * 重命名sdk ([5d7e22e](https://github.com/tencent-connect/bot-node-sdk/commit/5d7e22efb29c7d60a9432fb0d225db17415894fd)) 132 | * sdk中添加撤回消息API ([69058d6](https://github.com/tencent-connect/bot-node-sdk/commit/69058d6a8a4ad20fa634f9d89c58470f415b0459)) 133 | 134 | ### Bug Fixes 135 | 136 | * 修复MuteAPI返回类型问题 ([7ab3a8f](https://github.com/tencent-connect/bot-node-sdk/commit/7ab3a8f45de62f6d9bfd7ed29c706b4c7fefba5b)) 137 | 138 | ## [2.0.0](https://github.com/tencent-connect/bot-node-sdk/compare/v1.5.0...v2.0.0) (2022-01-05) 139 | 140 | ### Features 141 | 142 | * 重命名sdk ([f755cc0](https://github.com/tencent-connect/bot-node-sdk/commit/f755cc0584c75a90fa18ab32f76617373d93a445)) 143 | 144 | ## [1.5.0](https://github.com/tencent-connect/bot-node-sdk/compare/v1.4.0...v1.5.0) (2022-01-01) 145 | 146 | ### Features 147 | 148 | * 将类型定义导出到打包结果 ([99b2c57](https://github.com/tencent-connect/bot-node-sdk/commit/99b2c57642f386881b694d032605e538586566b6)) 149 | 150 | ## [1.4.0](https://github.com/tencent-connect/bot-node-sdk/compare/v1.3.1...v1.4.0) (2022-01-01) 151 | 152 | ### Features 153 | 154 | * 添加子频道身份组权限api(测试用例待补充)' ([400fc34](https://github.com/tencent-connect/bot-node-sdk/commit/400fc34232069f740a88af6e19113cf3b550a353)) 155 | 156 | ### [1.3.1](https://github.com/tencent-connect/bot-node-sdk/compare/v1.3.0...v1.3.1) (2021-12-31) 157 | 158 | ### Features 159 | 160 | * 异常返回带上traceid ([b151628](https://github.com/tencent-connect/bot-node-sdk/commit/b1516285bfe1ac3bed798266e5b40c7dcabf9900)) 161 | 162 | ## [1.3.0](https://github.com/tencent-connect/bot-node-sdk/compare/v1.2.2...v1.3.0) (2021-12-30) 163 | 164 | ### Features 165 | 166 | * 补充'身份组成员'接口传参方式 ([cb0ae47](https://github.com/tencent-connect/bot-node-sdk/commit/cb0ae47aea7c1edeebaec58d70e7f28f6a10417d)) 167 | 168 | ### [1.2.2](https://github.com/tencent-connect/bot-node-sdk/compare/v1.2.0...v1.2.2) (2021-12-28) 169 | 170 | ### Bug Fixes 171 | 172 | * err返回值兼容response.data不存在的情况 ([fe7c7cc](https://github.com/tencent-connect/bot-node-sdk/commit/fe7c7cce93869e9a7d360b052d64394a57fe84be)) 173 | 174 | ### [1.2.1](https://github.com/tencent-connect/bot-node-sdk/compare/v1.2.0...v1.2.1) (2021-12-25) 175 | 176 | ### Bug Fixes 177 | 178 | * err返回值兼容response.data不存在的情况 ([fe7c7cc](https://github.com/tencent-connect/bot-node-sdk/commit/fe7c7cce93869e9a7d360b052d64394a57fe84be)) 179 | 180 | ## [1.2.0](https://github.com/tencent-connect/bot-node-sdk/compare/v1.1.2...v1.2.0) (2021-12-24) 181 | 182 | ### Features 183 | 184 | * 修改shards与intents入参为可选 ([29d5c96](https://github.com/tencent-connect/bot-node-sdk/commit/29d5c961ee4fe11faea840264057ba8ddd4cb2da)) 185 | 186 | ### Bug Fixes 187 | 188 | * 修复ws模块拼写错误 ([dd3a85b](https://github.com/tencent-connect/bot-node-sdk/commit/dd3a85b97dcd044e679052e061d87ad1052939b7)) 189 | * 修改错误拼写 ([56a7270](https://github.com/tencent-connect/bot-node-sdk/commit/56a7270d86221354a846d0603ba2ea70ced78467)) 190 | * ws相关方法参数类型修复 ([2ed828d](https://github.com/tencent-connect/bot-node-sdk/commit/2ed828d95c6c9b1564524c3d3ca24d03ef6f7327)) 191 | 192 | ### [1.1.2](https://github.com/tencent-connect/bot-node-sdk/compare/v1.1.0...v1.1.2) (2021-12-22) 193 | 194 | ### Bug Fixes 195 | 196 | * 配置文件修改 ([ea84dd7](https://github.com/tencent-connect/bot-node-sdk/commit/ea84dd7c083258ea334d3792bbd141114b2266f2)) 197 | * 去除多余文件 ([ae367b4](https://github.com/tencent-connect/bot-node-sdk/commit/ae367b42d9a894fbf12baadf27b95a1cdde1caef)) 198 | * support for passing filter params when post & patch role ([1f66b6c](https://github.com/tencent-connect/bot-node-sdk/commit/1f66b6cf0ec3b7c903092e364365b9d738de2531)) 199 | 200 | ### [1.1.1](https://github.com/tencent-connect/bot-node-sdk/compare/v1.1.0...v1.1.1) (2021-12-22) 201 | 202 | ### Bug Fixes 203 | 204 | * support for passing filter params when post & patch role ([1f66b6c](https://github.com/tencent-connect/bot-node-sdk/commit/1f66b6cf0ec3b7c903092e364365b9d738de2531)) 205 | * 去除多余文件 ([ae367b4](https://github.com/tencent-connect/bot-node-sdk/commit/ae367b42d9a894fbf12baadf27b95a1cdde1caef)) 206 | * 配置文件修改 ([ea84dd7](https://github.com/tencent-connect/bot-node-sdk/commit/ea84dd7c083258ea334d3792bbd141114b2266f2)) 207 | 208 | ## [1.1.0](https://git.woa.com/qq-channel-bot/bot-node-sdk/compare/v1.0.0...v1.1.0) (2021-12-09) 209 | 210 | ### Features 211 | 212 | * 去除APPID/token等信息 ([9997c5b](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/9997c5b4a972cbb3c1e399f3ee6259594d8af587)) 213 | * 修改ws信息获取时的鉴权信息 ([bcd1521](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/bcd152139458243628b1582c7719c2878b09b4be)) 214 | * ws断线优化,事件分发优化 ([ccfab14](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/ccfab14faa843156c543e8526a1226df26d22622)) 215 | 216 | ## 1.0.0 (2021-12-03) 217 | 218 | ### Features 219 | 220 | * 代码优化 ([dc30d86](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/dc30d869c7b48f9bc3ad23afe2ddedb80fcb1e6e)) 221 | * 代码优化 ([65df46e](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/65df46e7c3615684385dbdfc631a305637f0bb0c)) 222 | * 底层会话优化 ([473b6f7](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/473b6f7f0b1ef90fcf4416ee84f5dca6c83ef74e)) 223 | * 功能优化 ([2a9545d](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/2a9545d2770da9b8b32bebbdeecf9a733cacc0d0)) 224 | * 会话优化 ([7c042a2](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/7c042a20d503ad6dedf833d837f418221445a157)) 225 | * 结构调整 ([b36bc04](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/b36bc0451addfc000fab37555f7d82c4f6050406)) 226 | * 添加单测用例 ([23581ef](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/23581ef2cf6e535a81c87da4fe13814c97fb64dd)) 227 | * 添加readme, 部分代码优化 ([08bc505](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/08bc505258c20a8b763bb18ce9f2fd5043704773)) 228 | * 心跳重连 ([67445ba](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/67445ba178cdc4c7be404a49cdfb90470d7fbbb7)) 229 | * 优化心跳 ([7626e20](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/7626e20f6031fbb39623383ef05a6e19db3d81fc)) 230 | * add channel permissions openapis ([5b70182](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/5b7018235454d37a53aa994594b0493d58d943ef)) 231 | * add direct message openapi ([19e6cf6](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/19e6cf6581f3dddfc798db6f3768ed2775b4a598)) 232 | * add example ([b6e076b](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/b6e076b6f177af2d0d65e9c749e663653233a3a1)) 233 | * add meApi ([2f37b33](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/2f37b338954d93b37890a16f930ddccba2f86c9b)) 234 | * add openapi ([dfebe4a](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/dfebe4a667d49a238e0c1b1d7512a46669be8854)) 235 | * add openapis ([991dcd1](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/991dcd11c412b1a31cc608536337c4dbc891f28e)) 236 | * add resty-client ([05c213e](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/05c213e038752c915632c01db33eeb9c86f1b7b5)) 237 | * add user openapis ([171a5bd](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/171a5bd708964d1336a516c2a2f6a14f247c960f)) 238 | * guildMembers接口添加分页参数 ([4bd7a61](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/4bd7a615e8958e7d0ae1b6685d42a1d983d019bf)) 239 | * openapi design ([ea9a19a](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/ea9a19a65befbac74ae355aad0a8f6acecb5b298)) 240 | * orange-ci 自动化构建 ([ab1611c](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/ab1611c8fad3aa69cb615fb24df304e2b49e055f)) 241 | * register openapis ([d7f18d1](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/d7f18d14f3b1e11c859e86c480971895f0e825fd)) 242 | * SDK基础架构 ([7cca61c](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/7cca61c2da1a296b11588b8dedb313f7684184a3)) 243 | * tune the main process ([ec70a0b](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/ec70a0b370f0337409fdab7f239d849acd9d9b1f)) 244 | * websocket基础链接+心跳重连 ([2e330c8](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/2e330c8c95e270c7849ed493b0772ddd16274e81)) 245 | * ws监听事件优化,结构调整 ([cb91c8d](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/cb91c8db982f8ee3ca12a48b32401da81885fc98)) 246 | * ws优化 ([abf75cc](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/abf75cce3f96e704b2230f248a8b1c623e37461b)) 247 | * ws优化 ([0105a85](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/0105a8514ab13db3dc125ec7b7578041ec7d9233)) 248 | * wss请求地址优化 ([9fd4bcd](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/9fd4bcd475e0eb28ee9ece7fab9ea5dfc301bc4e)) 249 | * wss优化 ([6bb374e](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/6bb374e75c0e81360a2e17c04261336fa6cb808a)) 250 | 251 | ### Bug Fixes 252 | 253 | * 修正eslint校验 ([7e6cd0d](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/7e6cd0d3631bc963624bfb4d94b92e5a2f005be5)) 254 | * add fix openapis boundary ([0dc47e2](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/0dc47e25c435f7dec72207b737598fa61e19e31e)) 255 | * fix apis ([c183a39](https://git.woa.com/qq-channel-bot/bot-node-sdk/commit/c183a39fff8dfef8c8bd0c1bc5fa17cc410f5870)) 256 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tencent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QQ 频道机器人 SDK qq-guild-bot 2 | 3 | 用于开发 QQ 频道机器人的 Node.js SDK。 4 | 5 | ## 使用文档 6 | 7 | [NodeSDK 文档](https://bot.q.qq.com/wiki/develop/nodesdk/) 8 | 9 | ## 本地开发 10 | 11 | ```shell 12 | # clone repo 13 | git clone https://github.com/tencent-connect/bot-node-sdk.git 14 | 15 | # cd repo 16 | cd bot-node-sdk 17 | 18 | # run 19 | npm run dev 20 | 21 | # run example 22 | npm run linkdev 23 | npm run example 24 | ``` 25 | 26 | ## 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 27 | 28 | - 👏 如果您有针对 SDK 的错误修复,请以分支`fix/xxx`向`main`分支发 PR 29 | - 👏 如果您有新的内容贡献,请以分支`feature/xxx`向`main`分支发起 PR 30 | - 👏 如果您有相关的建议或意见,请提[issues](https://github.com/tencent-connect/bot-node-sdk/issues) 31 | 32 | ## 加入官方社区 33 | 34 | 欢迎扫码加入**QQ 频道开发者社区**。 35 | 36 | ![开发者社区](https://mpqq.gtimg.cn/privacy/qq_guild_developer.png) 37 | 38 | ## 贡献者 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ## 注意 49 | 50 | `qq-guild-bot`自`v2.0.0`开始,被用作 QQ 频道机器人官方 SDK。原`qq-guild-bot`对应的能力,已迁移到[qq-guild-bot-es](https://www.npmjs.com/package/qq-guild-bot-es),请知晓! 51 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env", "@babel/typescript"], 3 | "plugins": [ 4 | "@babel/plugin-transform-runtime", 5 | "@babel/plugin-proposal-class-properties" 6 | ], 7 | "env": { 8 | "esm": { 9 | "presets": [ 10 | [ 11 | "@babel/env", 12 | { 13 | "modules": false 14 | } 15 | ] 16 | ], 17 | "plugins": [ 18 | [ 19 | "@babel/plugin-transform-runtime", 20 | { 21 | "useESModules": true 22 | } 23 | ] 24 | ] 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # 简单示例 2 | 3 | ## 步骤 4 | 5 | - 1、将 `config.example.json` 重命名为 `config.json`,并更新相关配置信息 6 | - 2、`yarn example` 7 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | // 以下仅为用法示意,详情请参照文档:https://bot.q.qq.com/wiki/develop/nodesdk/ 2 | const { createOpenAPI, createWebsocket } = require('qq-guild-bot'); 3 | 4 | const testConfigWs = { 5 | appID: '', 6 | token: '', 7 | }; 8 | 9 | const client = createOpenAPI(testConfigWs); 10 | 11 | const ws = createWebsocket(testConfigWs); 12 | ws.on('READY', (wsdata) => { 13 | console.log('[READY] 事件接收 :', wsdata); 14 | }); 15 | 16 | ws.on('ERROR', (data) => { 17 | console.log('[ERROR] 事件接收 :', data); 18 | }); 19 | ws.on('GUILDS', (data) => { 20 | console.log('[GUILDS] 事件接收 :', data); 21 | }); 22 | ws.on('GUILD_MEMBERS', (data) => { 23 | console.log('[GUILD_MEMBERS] 事件接收 :', data); 24 | }); 25 | ws.on('GUILD_MESSAGES', (data) => { 26 | console.log('[GUILD_MESSAGES] 事件接收 :', data); 27 | }); 28 | ws.on('GUILD_MESSAGE_REACTIONS', (data) => { 29 | console.log('[GUILD_MESSAGE_REACTIONS] 事件接收 :', data); 30 | }); 31 | ws.on('DIRECT_MESSAGE', (data) => { 32 | console.log('[DIRECT_MESSAGE] 事件接收 :', data); 33 | }); 34 | ws.on('INTERACTION', (data) => { 35 | console.log('[INTERACTION] 事件接收 :', data); 36 | }); 37 | ws.on('MESSAGE_AUDIT', (data) => { 38 | console.log('[MESSAGE_AUDIT] 事件接收 :', data); 39 | }); 40 | ws.on('FORUMS_EVENT', (data) => { 41 | console.log('[FORUMS_EVENT] 事件接收 :', data); 42 | }); 43 | ws.on('AUDIO_ACTION', (data) => { 44 | console.log('[AUDIO_ACTION] 事件接收 :', data); 45 | }); 46 | ws.on('PUBLIC_GUILD_MESSAGES', async (eventData) => { 47 | console.log('[PUBLIC_GUILD_MESSAGES] 事件接收 :', eventData); 48 | const {data} = await client.messageApi.postMessage('', { 49 | content: 'test' 50 | }) 51 | console.log(data); 52 | }); 53 | 54 | // client.guildApi.guild('').then((data) => { 55 | // console.log(data); 56 | // }); 57 | 58 | // // ✅ 59 | // client.channelApi.channels(guildID).then((res) => { 60 | // console.log(res.data); 61 | // }); 62 | -------------------------------------------------------------------------------- /example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "example", 9 | "version": "1.0.0", 10 | "license": "ISC" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "example": "node index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "collectCoverage": true, 4 | "cacheDirectory": ".cache", 5 | "roots": [""], 6 | "transform": { 7 | "^.+\\.(t|j)s?x?$": "babel-jest" 8 | }, 9 | "testEnvironment": "node", 10 | "coverageDirectory": "coverage", 11 | "coverageProvider": "v8", 12 | "coverageThreshold": { 13 | "global": { 14 | "branches": 100, 15 | "functions": 100, 16 | "lines": 100, 17 | "statements": 100 18 | } 19 | }, 20 | "moduleNameMapper": { 21 | "^@src/(.+)$": "/src/$1" 22 | }, 23 | "moduleFileExtensions": ["ts", "js"], 24 | "transformIgnorePatterns":["/node_modules/(?!(map-obj|quick-lru))"], 25 | "testRegex": "(spec)\\.(ts|js)?$" 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qq-guild-bot", 3 | "version": "2.9.4", 4 | "description": "QQ频道机器人NodeSDK", 5 | "publishConfig": { 6 | "registry": "https://registry.npmjs.org/" 7 | }, 8 | "keywords": [ 9 | "tencent", 10 | "nodejs", 11 | "qq", 12 | "channel", 13 | "guild", 14 | "bot" 15 | ], 16 | "files": [ 17 | "es", 18 | "lib", 19 | "typings" 20 | ], 21 | "module": "es/index.js", 22 | "main": "lib/index.js", 23 | "typings": "typings/index.d.ts", 24 | "scripts": { 25 | "prepare": "husky install", 26 | "dev": "npm run clean & cross-env NODE_ENV=dev rollup -c rollup.config.js -w", 27 | "build": "npm run clean &cross-env NODE_ENV=production rollup -c rollup.config.js", 28 | "lint": "npx eslint \"./**/*.{js,ts}\" --fix", 29 | "format": "prettier --write ./src", 30 | "test": "jest --config jest.config.json", 31 | "example": "cd example && npm run dev", 32 | "publish:npm": "node scripts/publish.js", 33 | "gc": "git-cz", 34 | "gen": "node scripts/gen/index.js", 35 | "release": "standard-version", 36 | "clean": "rimraf lib es typings", 37 | "linkdev": "node scripts/dev.js" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.15.0", 41 | "@babel/generator": "^7.17.0", 42 | "@babel/parser": "^7.17.0", 43 | "@babel/plugin-proposal-class-properties": "^7.14.5", 44 | "@babel/plugin-transform-runtime": "^7.15.0", 45 | "@babel/preset-env": "^7.15.0", 46 | "@babel/preset-typescript": "^7.15.0", 47 | "@babel/traverse": "^7.17.0", 48 | "@babel/types": "^7.17.0", 49 | "@commitlint/cli": "^13.1.0", 50 | "@commitlint/config-conventional": "^13.1.0", 51 | "@rollup/plugin-babel": "^5.3.0", 52 | "@rollup/plugin-commonjs": "^20.0.0", 53 | "@rollup/plugin-json": "^4.1.0", 54 | "@rollup/plugin-node-resolve": "^13.0.4", 55 | "@rollup/plugin-replace": "^3.0.0", 56 | "@types/jest": "^27.0.2", 57 | "@types/lodash.assignin": "^4.2.6", 58 | "@types/ws": "^8.2.0", 59 | "@typescript-eslint/eslint-plugin": "^4.29.2", 60 | "@typescript-eslint/parser": "^4.29.2", 61 | "chalk": "^4.1.2", 62 | "commitizen": "^4.2.4", 63 | "cross-env": "^7.0.3", 64 | "cz-conventional-changelog": "^3.3.0", 65 | "eslint": "^7.32.0", 66 | "eslint-config-alloy": "^4.1.0", 67 | "eslint-config-prettier": "^8.3.0", 68 | "eslint-plugin-jest": "^24.4.0", 69 | "eslint-plugin-prettier": "^3.4.0", 70 | "handlebars": "^4.7.7", 71 | "husky": "^7.0.1", 72 | "inquirer": "^8.2.0", 73 | "jest": "^25.3.0", 74 | "lint-staged": "^11.1.2", 75 | "lodash.clonedeep": "^4.5.0", 76 | "prettier": "^2.3.2", 77 | "readline-sync": "^1.4.10", 78 | "rimraf": "^3.0.2", 79 | "rollup": "^2.56.2", 80 | "rollup-plugin-dts": "^4.0.0", 81 | "rollup-plugin-typescript-paths": "^1.3.0", 82 | "standard-version": "^9.3.1", 83 | "typescript": "^4.4.4" 84 | }, 85 | "dependencies": { 86 | "loglevel": "^1.8.0", 87 | "resty-client": "0.0.5", 88 | "ws": "^7.4.4" 89 | }, 90 | "resolutions": { 91 | "minimist": "1.2.6", 92 | "ansi-regex": "4.1.1" 93 | }, 94 | "repository": { 95 | "type": "git", 96 | "url": "https://github.com/tencent-connect/bot-node-sdk.git" 97 | }, 98 | "author": "joyqwang", 99 | "license": "MIT", 100 | "homepage": "", 101 | "lint-staged": { 102 | "src/**/*.ts?(x)": [ 103 | "eslint --fix", 104 | "prettier --write" 105 | ] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 4 | import { typescriptPaths } from 'rollup-plugin-typescript-paths'; 5 | import replace from '@rollup/plugin-replace'; 6 | import dts from 'rollup-plugin-dts'; 7 | import json from '@rollup/plugin-json'; 8 | 9 | const ENV = process.env.NODE_ENV; 10 | const extensions = ['.ts', '.js']; 11 | const external = ['ws', 'resty-client']; 12 | 13 | export default [ 14 | { 15 | input: 'src/index.ts', 16 | output: { 17 | file: 'lib/index.js', 18 | format: 'cjs', 19 | sourcemap: ENV === 'dev', 20 | }, 21 | plugins: [ 22 | json(), 23 | babel({ 24 | exclude: 'node_modules/**', 25 | extensions, 26 | babelHelpers: 'runtime', 27 | }), 28 | nodeResolve({ browser: true, extensions }), 29 | typescriptPaths({ 30 | preserveExtensions: true, 31 | }), 32 | commonjs(), 33 | replace({ 34 | preventAssignment: true, 35 | 'process.env.NODE_ENV': JSON.stringify(ENV), 36 | }), 37 | ], 38 | external, 39 | }, 40 | { 41 | input: 'src/index.ts', 42 | output: { 43 | file: 'es/index.js', 44 | format: 'es', 45 | sourcemap: ENV === 'dev', 46 | }, 47 | plugins: [ 48 | json(), 49 | babel({ 50 | exclude: 'node_modules/**', 51 | extensions, 52 | babelHelpers: 'runtime', 53 | }), 54 | nodeResolve({ browser: true, extensions }), 55 | typescriptPaths({ 56 | preserveExtensions: true, 57 | }), 58 | commonjs(), 59 | replace({ 60 | preventAssignment: true, 61 | 'process.env.NODE_ENV': JSON.stringify(ENV), 62 | }), 63 | ], 64 | external, 65 | }, 66 | { 67 | input: 'src/index.ts', 68 | output: [{ file: 'typings/index.d.ts', format: 'es' }], 69 | plugins: [ 70 | json(), 71 | typescriptPaths({ 72 | preserveExtensions: true, 73 | }), 74 | dts(), 75 | ], 76 | }, 77 | ]; 78 | -------------------------------------------------------------------------------- /scripts/dev.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | 3 | try { 4 | execSync('npm link'); 5 | execSync('cd example && npm link qq-guild-bot'); 6 | } catch (error) {} 7 | -------------------------------------------------------------------------------- /scripts/gen/index.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const genSDK = require('./sdk'); 3 | 4 | const promptList = [ 5 | { 6 | type: 'input', 7 | message: '请输入API名称(如:mute,多个单词使用驼峰形式,如:channelPermission):', 8 | name: 'name', 9 | validate: function (val) { 10 | if (val.match(/^\w+$/g)) { 11 | return true; 12 | } 13 | return '请输入合法的API名称'; 14 | }, 15 | }, 16 | ]; 17 | 18 | inquirer 19 | .prompt(promptList) 20 | .then((answers) => { 21 | const { name } = answers; 22 | genSDK(name); 23 | }) 24 | .catch((err) => { 25 | console.log(err); 26 | }); 27 | -------------------------------------------------------------------------------- /scripts/gen/sdk.js: -------------------------------------------------------------------------------- 1 | const Handlebars = require('handlebars'); 2 | const { upperFirst, getKebabCase } = require('./utils'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const chalk = require('chalk'); 6 | const parser = require('@babel/parser'); 7 | const t = require('@babel/types'); 8 | const traverse = require('@babel/traverse').default; 9 | const generate = require('@babel/generator').default; 10 | const cloneDeep = require('lodash.clonedeep'); 11 | 12 | // AST操作参考文档 13 | // https://babeljs.io/docs/en/babel-types 14 | // https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-pushing-a-variable-declaration-to-a-parent-scope 15 | 16 | let apiClassName; // 类 名称 17 | let apiClassType; // 类 类型 18 | let methodName; // 方法名称 默认一个 19 | let uriName; // 接口url名称 20 | let uriValue = 'xxx'; // 接口url值 21 | let apiFileName; // 新增文件名 22 | let apiInstanceName; // 示例名字 23 | 24 | /** 25 | * 根据模板生成文件内容并写入对应文件 26 | * 27 | * @param {*} tplFileName 28 | * @param {*} targetFilePath 29 | * @param {*} tplArgs 30 | */ 31 | const genFileByTemplate = (tplFileName, targetFilePath, tplArgs) => { 32 | try { 33 | const templateContent = fs.readFileSync(path.join(__dirname, `./template/${tplFileName}`)); 34 | const template = Handlebars.compile(templateContent.toString()); 35 | const apiFileContent = template(tplArgs); 36 | fs.existsSync(targetFilePath); 37 | fs.writeFileSync(targetFilePath, apiFileContent); 38 | } catch (error) { 39 | console.log(chalk.red(`从模板${tplFileName}生成文件失败`)); 40 | console.log(error); 41 | } 42 | }; 43 | 44 | /** 45 | * 传入文件路径,读取文件内容 生成ast,支持修改ast,最后根据ast生成代码 46 | * 47 | * @param {*} filePath 48 | * @param {*} patch 49 | */ 50 | const genCodeByAST = (filePath, patch) => { 51 | const fileContent = fs.readFileSync(filePath); 52 | let ast = parser.parse(fileContent.toString(), { 53 | sourceType: 'module', 54 | plugins: ['typescript'], 55 | }); 56 | traverse(ast, { 57 | enter(path) { 58 | patch(path); 59 | }, 60 | }); 61 | const newContent = generate(ast, { 62 | // retainFunctionParens:true, 63 | comments: false, 64 | }).code; 65 | fs.writeFileSync(filePath, newContent); 66 | }; 67 | 68 | /** 69 | * 新增文件 70 | * @param {*} name 71 | */ 72 | const genApiFiles = (name) => { 73 | // 1. src/openapi/v1下文件生成 74 | const apiFilePath = path.join(__dirname, `../../src/openapi/v1/${apiFileName}.ts`); 75 | genFileByTemplate('api.handlebars', apiFilePath, { 76 | className: apiClassName, 77 | classType: apiClassType, 78 | methodName, 79 | uriName, 80 | }); 81 | // 2. src/types/openapi/v1类型文件生成 82 | const typeFilePath = path.join(__dirname, `../../src/types/openapi/v1/${apiFileName}.ts`); 83 | genFileByTemplate('type.handlebars', typeFilePath, { 84 | className: apiClassName, 85 | classType: apiClassType, 86 | methodName, 87 | }); 88 | }; 89 | 90 | /** 91 | * 更新 src/openapi/v1/resource.ts 文件 92 | * 新增 apiURI 映射 93 | */ 94 | const pathResourceFile = () => { 95 | const resourceFilePath = path.resolve(__dirname, '../../src/openapi/v1/resource.ts'); 96 | genCodeByAST(resourceFilePath, (path) => { 97 | if (path.isObjectExpression()) { 98 | const newNode = cloneDeep(path.node.properties[0]); 99 | newNode.key.name = uriName; 100 | newNode.value.value = uriValue; 101 | path.node.properties.push(newNode); 102 | } 103 | }); 104 | }; 105 | 106 | /** 107 | * 更新src/types/openapi/index.ts文件 108 | * 类型定义补充 109 | */ 110 | const patchTypeDefinitionFile = () => { 111 | const typeFilePath = path.resolve(__dirname, '../../src/types/openapi/index.ts'); 112 | genCodeByAST(typeFilePath, (path) => { 113 | // import语句 114 | if (path.isImportDeclaration() && path.getNextSibling().isExportNamedDeclaration()) { 115 | let importNode = t.importDeclaration( 116 | [t.importSpecifier(t.identifier(apiClassType), t.identifier(apiClassType))], 117 | t.stringLiteral(`./v1/${apiFileName}`), 118 | ); 119 | path.insertBefore(importNode); 120 | } 121 | // IOpenAPI属性 122 | if (path.isIdentifier({ name: 'IOpenAPI' }) && path.parent.type === 'TSInterfaceDeclaration') { 123 | path.parentPath 124 | .get('body') 125 | .pushContainer( 126 | 'body', 127 | t.tSPropertySignature( 128 | t.identifier(apiInstanceName), 129 | t.tsTypeAnnotation(t.tsTypeReference(t.identifier(apiClassType))), 130 | ), 131 | ); 132 | } 133 | // 添加导出 134 | if (path.type === 'ExportAllDeclaration' && !path.getNextSibling().node) { 135 | path.insertBefore(t.exportAllDeclaration(t.stringLiteral(`./v1/${apiFileName}`))); 136 | } 137 | }); 138 | }; 139 | 140 | /** 141 | * 更新 src/openapi/v1/openapi.ts 文件 142 | * API类定义补充 143 | */ 144 | const patchDefinitionFile = () => { 145 | const definitionFilePath = path.resolve(__dirname, '../../src/openapi/v1/openapi.ts'); 146 | genCodeByAST(definitionFilePath, (path) => { 147 | // 导入类名 148 | if (path.isImportDeclaration() && path.node.source.value === 'resty-client') { 149 | path.insertAfter( 150 | t.importDeclaration( 151 | [t.importDefaultSpecifier(t.identifier(apiClassName))], 152 | t.stringLiteral(`./${apiFileName}`), 153 | ), 154 | ); 155 | } 156 | // 导入类型 157 | if (path.isImportDeclaration() && path.node.source.value === '@src/types') { 158 | path.node.specifiers.push(t.importSpecifier(t.identifier(apiClassType), t.identifier(apiClassType))); 159 | } 160 | // 成员变量声明 161 | if (path.isClassProperty() && path.getNextSibling().isClassMethod()) { 162 | let newNode = cloneDeep(path.node); 163 | newNode.key.name = apiInstanceName; 164 | newNode.typeAnnotation.typeAnnotation.typeName.name = apiClassType; 165 | path.insertBefore(newNode); 166 | } 167 | // api实例对象创建 168 | if (path.isClassMethod() && path.node.key.name === 'register') { 169 | path 170 | .get('body') 171 | .pushContainer( 172 | 'body', 173 | t.expressionStatement( 174 | t.assignmentExpression( 175 | '=', 176 | t.memberExpression(t.identifier('client'), t.identifier(apiInstanceName)), 177 | t.newExpression(t.identifier(apiClassName), [ 178 | t.memberExpression(t.thisExpression(), t.identifier('request')), 179 | t.memberExpression(t.thisExpression(), t.identifier('config')), 180 | ]), 181 | ), 182 | ), 183 | ); 184 | console.log(10); 185 | } 186 | }); 187 | }; 188 | 189 | /** 190 | * 更新各种需要补充的文件 191 | */ 192 | const patchApiFiles = () => { 193 | pathResourceFile(); 194 | patchTypeDefinitionFile(); 195 | patchDefinitionFile(); 196 | }; 197 | 198 | module.exports = (name) => { 199 | apiClassName = upperFirst(name); 200 | apiClassType = `${apiClassName}API`; 201 | methodName = name; 202 | uriName = `${methodName}URI`; 203 | apiFileName = getKebabCase(name); 204 | apiInstanceName = `${name}Api`; 205 | 206 | genApiFiles(name); 207 | patchApiFiles(); 208 | }; 209 | -------------------------------------------------------------------------------- /scripts/gen/template/api.handlebars: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, {{classType}} } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class {{className}} implements {{classType}} { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // TODO:添加注释,补全内容,补全类型 14 | public {{methodName}}(): Promise> { 15 | const options = { 16 | method: 'GET' as const, 17 | url: getURL('{{uriName}}'), 18 | }; 19 | return this.request(options); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scripts/gen/template/type.handlebars: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= {{className}} 接口 ============= 5 | */ 6 | export interface {{classType}} { 7 | {{methodName}}: () => Promise>; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /scripts/gen/utils.js: -------------------------------------------------------------------------------- 1 | module.exports.upperFirst = (str) => str.replace(str[0], str[0].toUpperCase()); 2 | 3 | module.exports.getKebabCase = (str) => 4 | str.replace(/[A-Z]/g, (item) => { 5 | return '-' + item.toLowerCase(); 6 | }); 7 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | const readlineSync = require('readline-sync'); 2 | const chalk = require('chalk'); 3 | const { execSync } = require('child_process'); 4 | 5 | // 发布流程:npm run release -> npm run build -> npm publish -> git push --tag -> git push 6 | 7 | let headCommit; // head commitID 8 | let preVersion; // 自动生成的版本号 9 | 10 | const log = (msg, breakLineBefore = true, breakLineAfter = false, color = 'blue') => { 11 | breakLineBefore && console.log('\n'); 12 | console.log(chalk[color](msg)); 13 | breakLineAfter && console.log('\n'); 14 | }; 15 | 16 | const continuePublish = () => { 17 | // 执行build 18 | log('----- 2. build(start) -----'); 19 | execSync('npm run build'); 20 | log('----- build(end) -----'); 21 | 22 | // 执行 publish 23 | log('----- 3. publish(start) -----'); 24 | execSync('npm publish'); 25 | log('----- publish(end) -----'); 26 | 27 | // push 标签 28 | log('----- 4. push tag(start) -----'); 29 | execSync('git push --tag'); 30 | log('----- push tag(end) -----'); 31 | 32 | // push 代码 33 | log('----- 5. push code(start) -----'); 34 | execSync('git push'); 35 | log('----- push code(end) -----'); 36 | 37 | log('===== finish publish =====', true, false, 'green'); 38 | }; 39 | 40 | // 发布版本 41 | const runRelease = (version) => { 42 | log('----- 1. release(start) -----', false); 43 | if (version) { 44 | log(`release version : ${version}`, false, false, 'yellowBright'); 45 | execSync(`npm run release -- --release-as ${version}`); 46 | } else { 47 | const output = execSync('npm run release').toString('utf-8'); 48 | const extractVersion = /tagging release (v[0-9]+\.[0-9]+\.[0-9]+)/.exec(output)[1]; 49 | if (extractVersion) { 50 | preVersion = extractVersion; 51 | console.log(chalk.yellow(`preVersion: ${preVersion}`)); 52 | } 53 | } 54 | log('----- release(end) -----', true, true); 55 | }; 56 | 57 | // 清除副作用:commit、tag 58 | const clearEffect = () => { 59 | // 回滚commit 60 | execSync(`git reset --hard ${headCommit}`); 61 | // 删除tag 62 | execSync(`git tag -d ${preVersion}`); 63 | preVersion = ''; 64 | }; 65 | 66 | // 停止发布 67 | const stopPublish = () => { 68 | log('===== stop publish =====', true, false, 'red'); 69 | }; 70 | 71 | try { 72 | headCommit = execSync('git rev-parse --short HEAD').toString(); 73 | console.log('HEAD: ', headCommit); 74 | 75 | log('===== start publish =====', true, true, 'green'); 76 | 77 | // 开始release 78 | runRelease(); 79 | 80 | if (readlineSync.keyInYN(chalk.yellowBright('Is this version ok?'))) { 81 | continuePublish(); 82 | } else { 83 | clearEffect(); 84 | // 手动输入版本号 85 | if (readlineSync.keyInYN(chalk.yellowBright('continue with input version?'))) { 86 | const version = readlineSync.question('verison(e.g. 1.1.1): ', { 87 | limit: /[0-9]+\.[0-9]+\.[0-9]+$/, 88 | }); 89 | runRelease(version); 90 | preVersion = `v${version}`; 91 | continuePublish(); 92 | } else { 93 | stopPublish(); 94 | } 95 | } 96 | } catch (error) { 97 | log('publish broken', false, false, 'redBright'); 98 | console.log(error); 99 | clearEffect(); 100 | } 101 | -------------------------------------------------------------------------------- /src/bot.ts: -------------------------------------------------------------------------------- 1 | import { apiVersion, OpenAPI, v1Setup } from '@src/openapi/v1/openapi'; 2 | import { versionMapping } from '@src/openapi/openapi'; 3 | import { APIVersion, Config } from './types/openapi'; 4 | import { GetWsParam } from '@src/types'; 5 | import WebsocketClient from './client/client'; 6 | 7 | // 注册v1接口 8 | v1Setup(); 9 | 10 | let defaultImpl = versionMapping[apiVersion] as typeof OpenAPI; 11 | 12 | // SelectOpenAPIVersion 指定使用哪个版本的 api 实现,如果不指定,sdk将默认使用第一个 setup 的 api 实现 13 | export function selectOpenAPIVersion(version: APIVersion) { 14 | if (!versionMapping[version]) { 15 | return false; 16 | } 17 | defaultImpl = versionMapping[version]; 18 | } 19 | // 如果需要使用其他版本的实现,需要在调用这个方法之前调用 SelectOpenAPIVersion 方法 20 | export function createOpenAPI(config: Config) { 21 | return defaultImpl.newClient(config); 22 | } 23 | // ws连接新建 24 | export function createWebsocket(config: GetWsParam) { 25 | return new WebsocketClient(config); 26 | } 27 | -------------------------------------------------------------------------------- /src/client/client.ts: -------------------------------------------------------------------------------- 1 | import { GetWsParam, SessionEvents, SessionRecord, WebsocketCloseReason } from '@src/types/websocket-types'; 2 | import Session from '@src/client/session/session'; 3 | import { EventEmitter } from 'ws'; 4 | import { BotLogger } from '@src/utils/logger'; 5 | 6 | const MAX_RETRY = 10; 7 | 8 | export default class WebsocketClient extends EventEmitter { 9 | session!: Session; 10 | retry = 0; 11 | 12 | constructor(config: GetWsParam) { 13 | super(); 14 | this.connect(config); 15 | 16 | this.on(SessionEvents.EVENT_WS, (data) => { 17 | switch (data.eventType) { 18 | case SessionEvents.RECONNECT: 19 | BotLogger.info('[CLIENT] 等待断线重连中...'); 20 | break; 21 | case SessionEvents.DISCONNECT: 22 | if (this.retry < (config.maxRetry || MAX_RETRY)) { 23 | BotLogger.info('[CLIENT] 重新连接中,尝试次数:', this.retry + 1); 24 | this.connect(config, WebsocketCloseReason.find((v) => v.code === data.code)?.resume ? data.eventMsg : null); 25 | this.retry += 1; 26 | } else { 27 | BotLogger.info('[CLIENT] 超过重试次数,连接终止'); 28 | this.emit(SessionEvents.DEAD, { eventType: SessionEvents.ERROR, msg: '连接已死亡,请检查网络或重启' }); 29 | } 30 | break; 31 | case SessionEvents.READY: 32 | BotLogger.info('[CLIENT] 连接成功'); 33 | this.retry = 0; 34 | break; 35 | default: 36 | } 37 | }); 38 | } 39 | 40 | // 连接 41 | connect(config: GetWsParam, sessionRecord?: SessionRecord) { 42 | const event = this; 43 | // 新建一个会话 44 | this.session = new Session(config, event, sessionRecord); 45 | return this.session; 46 | } 47 | 48 | // 断开连接 49 | disconnect() { 50 | // 关闭会话 51 | this.session.closeSession(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/client/session/session-manager.ts: -------------------------------------------------------------------------------- 1 | // 暂时用不到session manager 2 | -------------------------------------------------------------------------------- /src/client/session/session.ts: -------------------------------------------------------------------------------- 1 | import { GetWsParam, SessionEvents, SessionRecord, WsObjRequestOptions } from '@src/types/websocket-types'; 2 | import { Ws } from '@src/client/websocket/websocket'; 3 | import { EventEmitter } from 'ws'; 4 | import resty from 'resty-client'; 5 | import { addAuthorization } from '@src/utils/utils'; 6 | import { BotLogger } from '@src/utils/logger'; 7 | 8 | export default class Session { 9 | config: GetWsParam; 10 | heartbeatInterval!: number; 11 | ws!: Ws; 12 | event!: EventEmitter; 13 | sessionRecord: SessionRecord | undefined; 14 | 15 | constructor(config: GetWsParam, event: EventEmitter, sessionRecord?: SessionRecord) { 16 | this.config = config; 17 | this.event = event; 18 | // 如果会话记录存在的话,继续透传 19 | if (sessionRecord) { 20 | this.sessionRecord = sessionRecord; 21 | } 22 | this.createSession(); 23 | } 24 | 25 | // 新建会话 26 | createSession() { 27 | this.ws = new Ws(this.config, this.event, this.sessionRecord || undefined); 28 | // 拿到 ws地址等信息 29 | const reqOptions = WsObjRequestOptions(this.config.sandbox as boolean); 30 | 31 | addAuthorization(reqOptions.headers, this.config.appID, this.config.token); 32 | 33 | resty 34 | .create(reqOptions) 35 | .get(reqOptions.url as string, {}) 36 | .then((r) => { 37 | const wsData = r.data; 38 | if (!wsData) throw new Error('获取ws连接信息异常'); 39 | this.ws.createWebsocket(wsData); 40 | }) 41 | .catch((e) => { 42 | BotLogger.info('[ERROR] createSession: ', e); 43 | this.event.emit(SessionEvents.EVENT_WS, { 44 | eventType: SessionEvents.DISCONNECT, 45 | eventMsg: this.sessionRecord, 46 | }); 47 | }); 48 | } 49 | 50 | // 关闭会话 51 | closeSession() { 52 | this.ws.closeWs(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/client/websocket/websocket.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AvailableIntentsEventsEnum, 3 | GetWsParam, 4 | IntentEvents, 5 | OpCode, 6 | SessionEvents, 7 | SessionRecord, 8 | WebsocketCloseReason, 9 | WsAddressObj, 10 | WsEventType, 11 | wsResData, 12 | } from '@src/types/websocket-types'; 13 | import WebSocket, { EventEmitter } from 'ws'; 14 | import { toObject } from '@src/utils/utils'; 15 | import { Properties } from '@src/utils/constants'; 16 | import { BotLogger } from '@src/utils/logger'; 17 | 18 | // websocket连接 19 | export class Ws { 20 | ws!: WebSocket; 21 | event!: EventEmitter; 22 | config: GetWsParam; 23 | heartbeatInterval!: number; 24 | // 心跳参数,默认为心跳测试 25 | heartbeatParam = { 26 | op: OpCode.HEARTBEAT, 27 | d: null, // 心跳唯一值 28 | }; 29 | // 是否是断线重连,如果是断线重连的话,不需要走鉴权 30 | isReconnect: boolean; 31 | // 记录会话参数 32 | sessionRecord = { 33 | sessionID: '', 34 | seq: 0, 35 | }; 36 | alive = false; 37 | 38 | constructor(config: GetWsParam, event: EventEmitter, sessionRecord?: SessionRecord) { 39 | this.config = config; 40 | this.isReconnect = false; 41 | this.event = event; 42 | // 如果是重连,则拿到重新的会话记录,然后进入重连步骤 43 | if (sessionRecord) { 44 | this.sessionRecord.sessionID = sessionRecord.sessionID; 45 | this.sessionRecord.seq = sessionRecord.seq; 46 | this.isReconnect = true; 47 | } 48 | } 49 | 50 | // 创建一个websocket连接 51 | createWebsocket(wsData: WsAddressObj) { 52 | // 先链接到ws 53 | this.connectWs(wsData); 54 | // 对消息进行监听 55 | return this.createListening(); 56 | } 57 | 58 | // 创建监听 59 | createListening() { 60 | // websocket连接已开启 61 | this.ws.on('open', () => { 62 | BotLogger.info(`[CLIENT] 开启`); 63 | }); 64 | 65 | // 接受消息 66 | this.ws.on('message', (data: wsResData) => { 67 | // BotLogger.info(`[CLIENT] 收到消息: ${data}`); 68 | 69 | // 先将消息解析 70 | const wsRes = toObject(data); 71 | // 先判断websocket连接是否成功 72 | if (wsRes?.op === OpCode.HELLO && wsRes?.d?.heartbeat_interval) { 73 | // websocket连接成功,拿到心跳周期 74 | this.heartbeatInterval = wsRes?.d?.heartbeat_interval; 75 | // 非断线重连时,需要鉴权 76 | this.isReconnect ? this.reconnectWs() : this.authWs(); 77 | return; 78 | } 79 | 80 | // 鉴权通过 81 | if (wsRes.t === SessionEvents.READY) { 82 | BotLogger.info(`[CLIENT] 鉴权通过`); 83 | const { d, s } = wsRes; 84 | const { session_id } = d; 85 | // 获取当前会话参数 86 | if (session_id && s) { 87 | this.sessionRecord.sessionID = session_id; 88 | this.sessionRecord.seq = s; 89 | this.heartbeatParam.d = s; 90 | } 91 | this.event.emit(SessionEvents.READY, { eventType: SessionEvents.READY, msg: d || '' }); 92 | // 第一次发送心跳 93 | BotLogger.info(`[CLIENT] 发送第一次心跳`, this.heartbeatParam); 94 | this.sendWs(this.heartbeatParam); 95 | return; 96 | } 97 | 98 | // 心跳测试 99 | if (wsRes.op === OpCode.HEARTBEAT_ACK || wsRes.t === SessionEvents.RESUMED) { 100 | if (!this.alive) { 101 | this.alive = true; 102 | this.event.emit(SessionEvents.EVENT_WS, { eventType: SessionEvents.READY }); 103 | } 104 | BotLogger.info('[CLIENT] 心跳校验', this.heartbeatParam); 105 | setTimeout(() => { 106 | this.sendWs(this.heartbeatParam); 107 | }, this.heartbeatInterval); 108 | } 109 | 110 | // 收到服务端锻炼重连的通知 111 | if (wsRes.op === OpCode.RECONNECT) { 112 | // 通知会话,当前已断线 113 | this.event.emit(SessionEvents.EVENT_WS, { eventType: SessionEvents.RECONNECT }); 114 | } 115 | 116 | // 服务端主动推送的消息 117 | if (wsRes.op === OpCode.DISPATCH) { 118 | // 更新心跳唯一值 119 | const { s } = wsRes; 120 | if (s) { 121 | this.sessionRecord.seq = s; 122 | this.heartbeatParam.d = s; 123 | } 124 | // OpenAPI事件分发 125 | this.dispatchEvent(wsRes.t, wsRes); 126 | } 127 | }); 128 | 129 | // 监听websocket关闭事件 130 | this.ws.on('close', (data: number) => { 131 | BotLogger.info('[CLIENT] 连接关闭', data); 132 | // 通知会话,当前已断线 133 | this.alive = false; 134 | this.event.emit(SessionEvents.EVENT_WS, { 135 | eventType: SessionEvents.DISCONNECT, 136 | eventMsg: this.sessionRecord, 137 | code: data, 138 | }); 139 | if (data) { 140 | this.handleWsCloseEvent(data); 141 | } 142 | }); 143 | 144 | // 监听websocket错误 145 | this.ws.on('error', () => { 146 | BotLogger.info(`[CLIENT] 连接错误`); 147 | this.event.emit(SessionEvents.CLOSED, { eventType: SessionEvents.CLOSED }); 148 | }); 149 | 150 | return this.ws; 151 | } 152 | 153 | // 连接ws 154 | connectWs(wsData: WsAddressObj) { 155 | // 创建websocket连接 156 | this.ws = new WebSocket(wsData.url); 157 | } 158 | 159 | // 鉴权 160 | authWs() { 161 | // 鉴权参数 162 | const authOp = { 163 | op: OpCode.IDENTIFY, // 鉴权参数 164 | d: { 165 | token: `Bot ${this.config.appID}.${this.config.token}`, // 根据配置转换token 166 | intents: this.getValidIntents(), // todo 接受的类型 167 | shard: this.checkShards(this.config.shards) || [0, 1], // 分片信息,给一个默认值 168 | properties: { 169 | $os: Properties.os, 170 | $browser: Properties.browser, 171 | $device: Properties.device, 172 | }, 173 | }, 174 | }; 175 | // 发送鉴权请求 176 | this.sendWs(authOp); 177 | } 178 | 179 | // 校验intents类型 180 | getValidIntents() { 181 | // 判断用户有没有给到需要监听的事件类型 182 | const intentsIn = this.getValidIntentsType(); 183 | if (intentsIn.length > 0) { 184 | const intents = { value: 0 }; 185 | if (intentsIn.length === 1) { 186 | intents.value = IntentEvents[intentsIn[0]]; 187 | return intents.value; 188 | } 189 | intentsIn.forEach((e) => { 190 | intents.value = IntentEvents[e] | intents.value; 191 | }); 192 | return intents.value; 193 | } 194 | } 195 | 196 | // 校验intents格式 197 | getValidIntentsType(): AvailableIntentsEventsEnum[] { 198 | const intentsIn = this.config.intents; 199 | // 全部可监听事件 200 | const defaultIntents = Object.keys(AvailableIntentsEventsEnum) as AvailableIntentsEventsEnum[]; 201 | // 如果开发者没传intents,我们默认给他开启全部监听事件 202 | if (!intentsIn) { 203 | BotLogger.info('[CLIENT] intents不存在,默认开启全部监听事件'); 204 | return defaultIntents; 205 | } 206 | // 如果开发者传入intents为空数组,我们默认给他开启全部监听事件 207 | if (intentsIn.length === 0) { 208 | BotLogger.info('[CLIENT] intents为空,默认开启全部监听事件'); 209 | return defaultIntents; 210 | } 211 | // 如果intents大于可监听数 212 | if (intentsIn.length > defaultIntents.length) { 213 | BotLogger.info('[CLIENT] intents中的监听事件大于可监听数,仅开启有效监听事件'); 214 | } 215 | // 如果intents中数据格式不对 216 | const typeIn = intentsIn.every((item) => typeof item === 'string'); 217 | if (!typeIn) { 218 | BotLogger.info('[CLIENT] intents中存在不合法类型,仅开启有效监听事件'); 219 | return intentsIn.filter((item) => typeof item === 'string'); 220 | } 221 | return intentsIn; 222 | } 223 | 224 | // 校验shards 225 | checkShards(shardsArr: Array | undefined) { 226 | // 没有传shards进来 227 | if (!shardsArr) { 228 | return BotLogger.info('shards 不存在'); 229 | } 230 | // 传进来的符合要求 231 | if (Array.isArray(shardsArr) && shardsArr.length === 2 && shardsArr[0] < shardsArr[1]) { 232 | return shardsArr; 233 | } 234 | return BotLogger.info('shards 错误'); 235 | } 236 | 237 | // 发送websocket 238 | sendWs(msg: unknown) { 239 | try { 240 | // 先将消息转为字符串 241 | this.ws.send(typeof msg === 'string' ? msg : JSON.stringify(msg)); 242 | } catch (e) { 243 | BotLogger.info(e); 244 | } 245 | } 246 | 247 | // 重新连接 248 | reconnect() { 249 | BotLogger.info('[CLIENT] 等待断线重连'); 250 | } 251 | 252 | // 重新重连Ws 253 | reconnectWs() { 254 | const reconnectParam = { 255 | op: OpCode.RESUME, 256 | d: { 257 | token: `Bot ${this.config.appID}.${this.config.token}`, 258 | session_id: this.sessionRecord.sessionID, 259 | seq: this.sessionRecord.seq, 260 | }, 261 | }; 262 | this.sendWs(reconnectParam); 263 | } 264 | 265 | // OpenAPI事件分发 266 | dispatchEvent(eventType: string, wsRes: wsResData) { 267 | const msg = wsRes.d; 268 | const eventId = wsRes.id || ''; 269 | // 如果没有事件,即刻退出 270 | if (!msg || !eventType) return; 271 | this.event.emit(WsEventType[eventType], { eventType, eventId, msg }); 272 | } 273 | 274 | // 主动关闭会话 275 | closeWs() { 276 | this.ws.close(); 277 | } 278 | 279 | // ws关闭的原因 280 | handleWsCloseEvent(code: number) { 281 | WebsocketCloseReason.forEach((e) => { 282 | if (e.code === code) { 283 | this.event.emit(SessionEvents.ERROR, { eventType: SessionEvents.ERROR, msg: e.reason }); 284 | } 285 | }); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bot'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/openapi/openapi.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPI } from './v1/openapi'; 2 | 3 | export const versionMapping = Object.create(null); 4 | 5 | export function register(version: string, api: typeof OpenAPI) { 6 | versionMapping[version] = api; 7 | } 8 | -------------------------------------------------------------------------------- /src/openapi/v1/announce.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, AnnounceAPI, IAnnounce, RecommendObj } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Announce implements AnnounceAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 创建guild公告 14 | public postGuildAnnounce(guildID: string, channelID: string, messageID: string): Promise> { 15 | const options = { 16 | method: 'POST' as const, 17 | url: getURL('guildAnnouncesURI'), 18 | rest: { 19 | guildID, 20 | }, 21 | data: { 22 | channel_id: channelID, 23 | message_id: messageID, 24 | }, 25 | }; 26 | return this.request(options); 27 | } 28 | 29 | // 删除guild公告 30 | public deleteGuildAnnounce(guildID: string, messageID: string): Promise> { 31 | const options = { 32 | method: 'DELETE' as const, 33 | url: getURL('guildAnnounceURI'), 34 | rest: { 35 | guildID, 36 | messageID, 37 | }, 38 | }; 39 | return this.request(options); 40 | } 41 | 42 | // 创建频道公告推荐子频道 43 | public postGuildRecommend(guildID: string, recommendObj: RecommendObj): Promise> { 44 | const options = { 45 | method: 'POST' as const, 46 | url: getURL('guildAnnouncesURI'), 47 | rest: { 48 | guildID, 49 | }, 50 | data: recommendObj, 51 | }; 52 | return this.request(options); 53 | } 54 | 55 | // 创建channel公告 56 | public postChannelAnnounce(channelID: string, messageID: string): Promise> { 57 | const options = { 58 | method: 'POST' as const, 59 | url: getURL('channelAnnouncesURI'), 60 | rest: { 61 | channelID, 62 | }, 63 | data: { 64 | message_id: messageID, 65 | }, 66 | }; 67 | return this.request(options); 68 | } 69 | 70 | // 删除channel公告 71 | public deleteChannelAnnounce(channelID: string, messageID: string): Promise> { 72 | const options = { 73 | method: 'DELETE' as const, 74 | url: getURL('channelAnnounceURI'), 75 | rest: { 76 | channelID, 77 | messageID, 78 | }, 79 | }; 80 | return this.request(options); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/openapi/v1/audio.ts: -------------------------------------------------------------------------------- 1 | import { AudioAPI, AudioControl, Config, OpenAPIRequest } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Audio implements AudioAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | // 执行音频播放,暂停等操作 13 | postAudio(channelID: string, audioControl: AudioControl): Promise> { 14 | const options = { 15 | method: 'POST' as const, 16 | url: getURL('audioControlURI'), 17 | rest: { 18 | channelID, 19 | }, 20 | data: audioControl, 21 | }; 22 | return this.request(options); 23 | } 24 | // 机器人上麦 25 | botOnMic(channelID: string): Promise> { 26 | const options = { 27 | method: 'PUT' as const, 28 | url: getURL('botMic'), 29 | rest: { 30 | channelID, 31 | }, 32 | data: {}, 33 | }; 34 | return this.request<{}>(options); 35 | } 36 | // 机器人下麦 37 | botOffMic(channelID: string): Promise> { 38 | const options = { 39 | method: 'DELETE' as const, 40 | url: getURL('botMic'), 41 | rest: { 42 | channelID, 43 | }, 44 | data: {}, 45 | }; 46 | return this.request<{}>(options); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/openapi/v1/channel-permissions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OpenAPIRequest, 3 | Config, 4 | ChannelPermissionsAPI, 5 | IChannelPermissions, 6 | IChannelRolePermissions, 7 | UpdateChannelPermissions, 8 | } from '@src/types'; 9 | import { RestyResponse } from 'resty-client'; 10 | import { getURL } from './resource'; 11 | export default class ChannelPermissions implements ChannelPermissionsAPI { 12 | public request: OpenAPIRequest; 13 | public config: Config; 14 | constructor(request: OpenAPIRequest, config: Config) { 15 | this.request = request; 16 | this.config = config; 17 | } 18 | 19 | // 获取指定子频道的权限 20 | public channelPermissions(channelID: string, userID: string): Promise> { 21 | const options = { 22 | method: 'GET' as const, 23 | url: getURL('channelPermissionsURI'), 24 | rest: { 25 | channelID, 26 | userID, 27 | }, 28 | }; 29 | return this.request(options); 30 | } 31 | 32 | // 修改指定子频道的权限 33 | public putChannelPermissions( 34 | channelID: string, 35 | userID: string, 36 | p: UpdateChannelPermissions, 37 | ): Promise> { 38 | try { 39 | // 校验参数 40 | parseInt(p.add, 10); 41 | parseInt(p.remove, 10); 42 | } catch (error) { 43 | return Promise.reject(new Error('invalid parameter')); 44 | } 45 | const options = { 46 | method: 'PUT' as const, 47 | url: getURL('channelPermissionsURI'), 48 | rest: { 49 | channelID, 50 | userID, 51 | }, 52 | data: p, 53 | }; 54 | return this.request(options); 55 | } 56 | 57 | // 获取指定子频道身份组的权限 58 | public channelRolePermissions(channelID: string, roleID: string): Promise> { 59 | const options = { 60 | method: 'GET' as const, 61 | url: getURL('channelRolePermissionsURI'), 62 | rest: { 63 | channelID, 64 | roleID, 65 | }, 66 | }; 67 | return this.request(options); 68 | } 69 | 70 | // 修改指定子频道身份组的权限 71 | public putChannelRolePermissions( 72 | channelID: string, 73 | roleID: string, 74 | p: UpdateChannelPermissions, 75 | ): Promise> { 76 | try { 77 | // 校验参数 78 | parseInt(p.add, 10); 79 | parseInt(p.remove, 10); 80 | } catch (error) { 81 | return Promise.reject(new Error('invalid parameter')); 82 | } 83 | const options = { 84 | method: 'PUT' as const, 85 | url: getURL('channelRolePermissionsURI'), 86 | rest: { 87 | channelID, 88 | roleID, 89 | }, 90 | data: p, 91 | }; 92 | return this.request(options); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/openapi/v1/channel.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, ChannelAPI, PostChannelObj, IChannel, PatchChannelObj } from '@src/types'; 2 | import { getTimeStampNumber } from '@src/utils/utils'; 3 | import { RestyResponse } from 'resty-client'; 4 | import { getURL } from './resource'; 5 | 6 | export default class Channel implements ChannelAPI { 7 | public request: OpenAPIRequest; 8 | public config: Config; 9 | constructor(request: OpenAPIRequest, config: Config) { 10 | this.request = request; 11 | this.config = config; 12 | } 13 | // 获取子频道信息 14 | public channel(channelID: string): Promise> { 15 | const options = { 16 | method: 'GET' as const, 17 | url: getURL('channelURI'), 18 | rest: { 19 | channelID, 20 | }, 21 | }; 22 | return this.request(options); 23 | } 24 | 25 | // 获取频道下的子频道列表 26 | public channels(guildID: string): Promise> { 27 | const options = { 28 | method: 'GET' as const, 29 | url: getURL('channelsURI'), 30 | rest: { 31 | guildID, 32 | }, 33 | }; 34 | return this.request(options); 35 | } 36 | 37 | // 创建子频道 38 | public postChannel(guildID: string, channel: PostChannelObj): Promise> { 39 | if (channel.position === 0) { 40 | channel.position = getTimeStampNumber(); 41 | } 42 | const options = { 43 | method: 'POST' as const, 44 | url: getURL('channelsURI'), 45 | rest: { 46 | guildID, 47 | }, 48 | data: channel, 49 | }; 50 | return this.request(options); 51 | } 52 | 53 | // 修改子频道信息 54 | public patchChannel(channelID: string, channel: PatchChannelObj): Promise> { 55 | if (channel.position === 0) { 56 | channel.position = getTimeStampNumber(); 57 | } 58 | const options = { 59 | method: 'PATCH' as const, 60 | url: getURL('channelURI'), 61 | rest: { 62 | channelID, 63 | }, 64 | data: channel, 65 | }; 66 | return this.request(options); 67 | } 68 | // 删除指定子频道 69 | public deleteChannel(channelID: string): Promise> { 70 | const options = { 71 | method: 'DELETE' as const, 72 | url: getURL('channelURI'), 73 | rest: { 74 | channelID, 75 | }, 76 | }; 77 | return this.request(options); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/openapi/v1/direct-message.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OpenAPIRequest, 3 | Config, 4 | DirectMessageAPI, 5 | DirectMessageToCreate, 6 | IDirectMessage, 7 | IMessage, 8 | MessageToCreate, 9 | } from '@src/types'; 10 | import { RestyResponse } from 'resty-client'; 11 | import { getURL } from './resource'; 12 | 13 | export default class DirectMessage implements DirectMessageAPI { 14 | public request: OpenAPIRequest; 15 | public config: Config; 16 | constructor(request: OpenAPIRequest, config: Config) { 17 | this.request = request; 18 | this.config = config; 19 | } 20 | // 创建私信频道 21 | public createDirectMessage(dm: DirectMessageToCreate): Promise> { 22 | const options = { 23 | method: 'POST' as const, 24 | url: getURL('userMeDMURI'), 25 | data: dm, 26 | }; 27 | return this.request(options); 28 | } 29 | 30 | // 在私信频道内发消息 31 | public postDirectMessage(guildID: string, msg: MessageToCreate): Promise> { 32 | const options = { 33 | method: 'POST' as const, 34 | url: getURL('dmsURI'), 35 | rest: { 36 | guildID, 37 | }, 38 | data: msg, 39 | }; 40 | return this.request(options); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/openapi/v1/guild-permissions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GuildPermissionRes, 3 | GuildPermissionDemand, 4 | GuildPermissionsAPI, 5 | Config, 6 | OpenAPIRequest, 7 | PermissionDemandToCreate, 8 | } from '@src/types'; 9 | import { RestyResponse } from 'resty-client'; 10 | import { getURL } from './resource'; 11 | 12 | export default class GuildPermissions implements GuildPermissionsAPI { 13 | public request: OpenAPIRequest; 14 | public config: Config; 15 | constructor(request: OpenAPIRequest, config: Config) { 16 | this.request = request; 17 | this.config = config; 18 | } 19 | 20 | // 获取频道可用权限列表 21 | public permissions(guildID: string): Promise> { 22 | const options = { 23 | method: 'GET' as const, 24 | url: getURL('guildPermissionURI'), 25 | rest: { 26 | guildID, 27 | }, 28 | }; 29 | return this.request(options); 30 | } 31 | 32 | // 创建频道 API 接口权限授权链接 33 | public postPermissionDemand( 34 | guildID: string, 35 | permissionDemandObj: PermissionDemandToCreate, 36 | ): Promise> { 37 | const options = { 38 | method: 'POST' as const, 39 | url: getURL('guildPermissionDemandURI'), 40 | rest: { 41 | guildID, 42 | }, 43 | data: permissionDemandObj, 44 | }; 45 | return this.request(options); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/openapi/v1/guild.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, GuildAPI, GuildMembersPager, IGuild, IMember, IVoiceMember } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Guild implements GuildAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | // 获取频道信息 13 | public guild(guildID: string): Promise> { 14 | const options = { 15 | method: 'GET' as const, 16 | url: getURL('guildURI'), 17 | rest: { 18 | guildID, 19 | }, 20 | }; 21 | return this.request(options); 22 | } 23 | // 获取某个成员信息 24 | public guildMember(guildID: string, userID: string): Promise> { 25 | const options = { 26 | method: 'GET' as const, 27 | url: getURL('guildMemberURI'), 28 | rest: { 29 | guildID, 30 | userID, 31 | }, 32 | }; 33 | return this.request(options); 34 | } 35 | // 获取频道成员列表 36 | public guildMembers(guildID: string, pager?: GuildMembersPager): Promise> { 37 | pager = pager || { after: '0', limit: 1 }; 38 | const options = { 39 | method: 'GET' as const, 40 | url: getURL('guildMembersURI'), 41 | rest: { 42 | guildID, 43 | }, 44 | params: pager, 45 | }; 46 | return this.request(options); 47 | } 48 | // 删除指定频道成员 49 | public deleteGuildMember(guildID: string, userID: string): Promise> { 50 | const options = { 51 | method: 'DELETE' as const, 52 | url: getURL('guildMemberURI'), 53 | rest: { 54 | guildID, 55 | userID, 56 | }, 57 | }; 58 | return this.request(options); 59 | } 60 | // 语音子频道在线成员列表 61 | public guildVoiceMembers(channelID: string): Promise> { 62 | const options = { 63 | method: 'GET' as const, 64 | url: getURL('guildVoiceMembersURI'), 65 | rest: { 66 | channelID, 67 | }, 68 | }; 69 | return this.request(options); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/openapi/v1/interaction.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, InteractionAPI, InteractionData } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Interaction implements InteractionAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 异步更新交互数据 14 | public putInteraction(interactionID: string, interactionData: InteractionData): Promise> { 15 | const options = { 16 | method: 'PUT' as const, 17 | url: getURL('interactionURI'), 18 | headers: { 19 | 'Content-Type': 'none', 20 | }, 21 | rest: { 22 | interactionID, 23 | }, 24 | data: interactionData, 25 | }; 26 | return this.request(options); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/openapi/v1/me.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, IUser, MeAPI, IGuild, MeGuildsReq } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Me implements MeAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 获取当前用户信息 14 | public me(): Promise> { 15 | const options = { 16 | method: 'GET' as const, 17 | url: getURL('userMeURI'), 18 | }; 19 | return this.request(options); 20 | } 21 | 22 | // 获取当前用户频道列表 23 | public meGuilds(options?: MeGuildsReq): Promise> { 24 | const reqOptions = { 25 | method: 'GET' as const, 26 | url: getURL('userMeGuildsURI'), 27 | params: options, 28 | }; 29 | return this.request(reqOptions); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/openapi/v1/member.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, MemberAddRoleBody, MemberAPI } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Member implements MemberAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | // 增加频道身份组成员 13 | public memberAddRole( 14 | guildID: string, 15 | roleID: string, 16 | userID: string, 17 | channel?: string | MemberAddRoleBody, // 兼容原来传递 channel 对象的逻辑,后续仅支持 string 18 | ): Promise> { 19 | const channelObj = 20 | typeof channel === 'string' 21 | ? { 22 | channel: { 23 | id: channel, 24 | }, 25 | } 26 | : channel; 27 | 28 | const options = { 29 | method: 'PUT' as const, 30 | url: getURL('memberRoleURI'), 31 | rest: { 32 | guildID, 33 | userID, 34 | roleID, 35 | }, 36 | data: channelObj, 37 | }; 38 | return this.request(options); 39 | } 40 | // 删除频道身份组成员 41 | public memberDeleteRole( 42 | guildID: string, 43 | roleID: string, 44 | userID: string, 45 | channel?: string | MemberAddRoleBody, // 兼容原来传递 channel 对象的逻辑,后续仅支持 string 46 | ): Promise> { 47 | const channelObj = 48 | typeof channel === 'string' 49 | ? { 50 | channel: { 51 | id: channel, 52 | }, 53 | } 54 | : channel; 55 | 56 | const options = { 57 | method: 'DELETE' as const, 58 | url: getURL('memberRoleURI'), 59 | rest: { 60 | guildID, 61 | userID, 62 | roleID, 63 | }, 64 | data: channelObj, 65 | }; 66 | return this.request(options); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/openapi/v1/message.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, IMessage, IMessageRes, MessageAPI, MessagesPager, MessageToCreate } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Message implements MessageAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | // 获取指定消息 13 | public message(channelID: string, messageID: string): Promise> { 14 | const options = { 15 | method: 'GET' as const, 16 | url: getURL('messageURI'), 17 | rest: { 18 | channelID, 19 | messageID, 20 | }, 21 | }; 22 | return this.request(options); 23 | } 24 | // 获取消息列表 25 | public messages(channelID: string, pager?: MessagesPager): Promise> { 26 | const params = Object.create(null); 27 | if (pager && pager.type && pager.id) { 28 | params[pager.type] = pager.id; 29 | params.limit = pager.limit || 20; 30 | } 31 | 32 | const options = { 33 | method: 'GET' as const, 34 | url: getURL('messagesURI'), 35 | rest: { 36 | channelID, 37 | }, 38 | params, 39 | }; 40 | return this.request(options); 41 | } 42 | 43 | // 发送消息 44 | public postMessage(channelID: string, message: MessageToCreate): Promise> { 45 | const options = { 46 | method: 'POST' as const, 47 | url: getURL('messagesURI'), 48 | rest: { 49 | channelID, 50 | }, 51 | data: message, 52 | }; 53 | return this.request(options); 54 | } 55 | 56 | // 撤回消息 57 | public deleteMessage(channelID: string, messageID: string, hideTip?: boolean): Promise> { 58 | const params = Object.create(null); 59 | if (hideTip) { 60 | params.hidetip = hideTip; 61 | } 62 | const options = { 63 | method: 'DELETE' as const, 64 | url: getURL('messageURI'), 65 | rest: { 66 | channelID, 67 | messageID, 68 | }, 69 | params, 70 | }; 71 | return this.request(options); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/openapi/v1/mute.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, MuteAPI, MuteOptions } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Mute implements MuteAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 禁言某个member 14 | public muteMember(guildID: string, userID: string, options: MuteOptions): Promise> { 15 | if (!options) { 16 | return Promise.reject(new Error("'options' required!")); 17 | } 18 | 19 | const reqOptions = { 20 | method: 'PATCH' as const, 21 | url: getURL('muteMemberURI'), 22 | rest: { 23 | guildID, 24 | userID, 25 | }, 26 | data: { 27 | mute_end_timestamp: options?.timeTo, 28 | mute_seconds: options?.seconds, 29 | }, 30 | }; 31 | return this.request(reqOptions); 32 | } 33 | 34 | // 禁言所有人 35 | public muteAll(guildID: string, options: MuteOptions): Promise> { 36 | if (!options) { 37 | return Promise.reject(new Error("'options' required!")); 38 | } 39 | 40 | const reqOptions = { 41 | method: 'PATCH' as const, 42 | url: getURL('muteURI'), 43 | rest: { 44 | guildID, 45 | }, 46 | data: { 47 | mute_end_timestamp: options?.timeTo, 48 | mute_seconds: options?.seconds, 49 | }, 50 | }; 51 | return this.request(reqOptions); 52 | } 53 | 54 | // 禁言批量member 55 | public muteMembers(guildID: string, userIDList: Array, options: MuteOptions): Promise> { 56 | if (!options) { 57 | return Promise.reject(new Error("'options' required!")); 58 | } 59 | 60 | const reqOptions = { 61 | method: 'PATCH' as const, 62 | url: getURL('muteMembersURI'), 63 | rest: { 64 | guildID, 65 | }, 66 | data: { 67 | mute_end_timestamp: options?.timeTo, 68 | mute_seconds: options?.seconds, 69 | user_ids: userIDList, 70 | }, 71 | }; 72 | return this.request(reqOptions); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/openapi/v1/openapi.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-promise-reject-errors */ 2 | import { register } from '@src/openapi/openapi'; 3 | import resty, { RequestOptions, RestyResponse } from 'resty-client'; 4 | import PinsMessage from './pins-message'; 5 | import Reaction from './reaction'; 6 | import Guild from './guild'; 7 | import Channel from './channel'; 8 | import Me from './me'; 9 | import Message from './message'; 10 | import Member from './member'; 11 | import Role from './role'; 12 | import DirectMessage from './direct-message'; 13 | import ChannelPermissions from './channel-permissions'; 14 | import Audio from './audio'; 15 | import Mute from './mute'; 16 | import Announce from './announce'; 17 | import Schedule from './schedule'; 18 | import GuildPermissions from './guild-permissions'; 19 | import Interaction from './interaction'; 20 | import { addUserAgent, addAuthorization, buildUrl } from '@src/utils/utils'; 21 | import { 22 | GuildAPI, 23 | ChannelAPI, 24 | MeAPI, 25 | MessageAPI, 26 | Config, 27 | IOpenAPI, 28 | MemberAPI, 29 | RoleAPI, 30 | DirectMessageAPI, 31 | ChannelPermissionsAPI, 32 | AudioAPI, 33 | MuteAPI, 34 | ScheduleAPI, 35 | AnnounceAPI, 36 | GuildPermissionsAPI, 37 | ReactionAPI, 38 | PinsMessageAPI, 39 | InteractionAPI, 40 | } from '@src/types'; 41 | export const apiVersion = 'v1'; 42 | export class OpenAPI implements IOpenAPI { 43 | static newClient(config: Config) { 44 | return new OpenAPI(config); 45 | } 46 | 47 | config: Config = { 48 | appID: '', 49 | token: '', 50 | }; 51 | public guildApi!: GuildAPI; 52 | public channelApi!: ChannelAPI; 53 | public meApi!: MeAPI; 54 | public messageApi!: MessageAPI; 55 | public memberApi!: MemberAPI; 56 | public roleApi!: RoleAPI; 57 | public muteApi!: MuteAPI; 58 | public announceApi!: AnnounceAPI; 59 | public scheduleApi!: ScheduleAPI; 60 | public directMessageApi!: DirectMessageAPI; 61 | public channelPermissionsApi!: ChannelPermissionsAPI; 62 | public audioApi!: AudioAPI; 63 | public reactionApi!: ReactionAPI; 64 | public interactionApi!: InteractionAPI; 65 | public pinsMessageApi!: PinsMessageAPI; 66 | public guildPermissionsApi!: GuildPermissionsAPI; 67 | 68 | constructor(config: Config) { 69 | this.config = config; 70 | this.register(this); 71 | } 72 | 73 | public register(client: IOpenAPI) { 74 | // 注册聚合client 75 | client.guildApi = new Guild(this.request, this.config); 76 | client.channelApi = new Channel(this.request, this.config); 77 | client.meApi = new Me(this.request, this.config); 78 | client.messageApi = new Message(this.request, this.config); 79 | client.memberApi = new Member(this.request, this.config); 80 | client.roleApi = new Role(this.request, this.config); 81 | client.muteApi = new Mute(this.request, this.config); 82 | client.announceApi = new Announce(this.request, this.config); 83 | client.scheduleApi = new Schedule(this.request, this.config); 84 | client.directMessageApi = new DirectMessage(this.request, this.config); 85 | client.channelPermissionsApi = new ChannelPermissions(this.request, this.config); 86 | client.audioApi = new Audio(this.request, this.config); 87 | client.guildPermissionsApi = new GuildPermissions(this.request, this.config); 88 | client.reactionApi = new Reaction(this.request, this.config); 89 | client.interactionApi = new Interaction(this.request, this.config); 90 | client.pinsMessageApi = new PinsMessage(this.request, this.config); 91 | } 92 | // 基础rest请求 93 | public request = any>(options: RequestOptions): Promise> { 94 | const { appID, token } = this.config; 95 | 96 | options.headers = { ...options.headers }; 97 | 98 | // 添加 UA 99 | addUserAgent(options.headers); 100 | // 添加鉴权信息 101 | addAuthorization(options.headers, appID, token); 102 | // 组装完整Url 103 | const botUrl = buildUrl(options.url, this.config.sandbox); 104 | 105 | // 简化错误信息,后续可考虑通过中间件形式暴露给用户自行处理 106 | resty.useRes( 107 | (result) => result, 108 | (error) => { 109 | let traceid = error?.response?.headers?.['x-tps-trace-id']; 110 | if (error?.response?.data) { 111 | return Promise.reject({ 112 | ...error.response.data, 113 | traceid, 114 | }); 115 | } 116 | if (error?.response) { 117 | return Promise.reject({ 118 | ...error.response, 119 | traceid, 120 | }); 121 | } 122 | return Promise.reject(error); 123 | }, 124 | ); 125 | 126 | const client = resty.create(options); 127 | return client.request(botUrl!, options); 128 | } 129 | } 130 | 131 | export function v1Setup() { 132 | register(apiVersion, OpenAPI); 133 | } 134 | -------------------------------------------------------------------------------- /src/openapi/v1/pins-message.ts: -------------------------------------------------------------------------------- 1 | import { Config, IPinsMessage, OpenAPIRequest, PinsMessageAPI } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class PinsMessage implements PinsMessageAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 获取精华消息 14 | public pinsMessage(channelID: string): Promise> { 15 | const options = { 16 | method: 'GET' as const, 17 | url: getURL('pinsMessageURI'), 18 | rest: { 19 | channelID, 20 | }, 21 | }; 22 | return this.request(options); 23 | } 24 | 25 | // 发送精华消息 26 | public putPinsMessage(channelID: string, messageID: string): Promise> { 27 | const options = { 28 | method: 'PUT' as const, 29 | url: getURL('pinsMessageIdURI'), 30 | headers: { 31 | 'Content-Type': 'application/json;', 32 | }, 33 | rest: { 34 | channelID, 35 | messageID, 36 | }, 37 | }; 38 | return this.request(options); 39 | } 40 | 41 | // 删除精华消息 42 | public deletePinsMessage(channelID: string, messageID: string): Promise> { 43 | const options = { 44 | method: 'DELETE' as const, 45 | url: getURL('pinsMessageIdURI'), 46 | rest: { 47 | channelID, 48 | messageID, 49 | }, 50 | }; 51 | return this.request(options); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/openapi/v1/reaction.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, ReactionAPI, ReactionObj, ReactionUserListObj } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Reaction implements ReactionAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 发表表情表态 14 | public postReaction(channelId: string, reactionToCreate: ReactionObj): Promise> { 15 | const options = { 16 | method: 'PUT' as const, 17 | url: getURL('reactionURI'), 18 | rest: { 19 | channelID: channelId, 20 | messageID: reactionToCreate?.message_id, 21 | emojiType: reactionToCreate?.emoji_type, 22 | emojiID: reactionToCreate?.emoji_id, 23 | }, 24 | }; 25 | return this.request(options); 26 | } 27 | 28 | // 删除表情表态 29 | public deleteReaction(channelId: string, reactionToDelete: ReactionObj): Promise> { 30 | const options = { 31 | method: 'DELETE' as const, 32 | url: getURL('reactionURI'), 33 | rest: { 34 | channelID: channelId, 35 | messageID: reactionToDelete?.message_id, 36 | emojiType: reactionToDelete?.emoji_type, 37 | emojiID: reactionToDelete?.emoji_id, 38 | }, 39 | }; 40 | return this.request(options); 41 | } 42 | 43 | // 拉取表情表态用户列表 44 | public getReactionUserList( 45 | channelId: string, 46 | reactionToCreate: ReactionObj, 47 | options: ReactionUserListObj, 48 | ): Promise> { 49 | if (!options) { 50 | return Promise.reject(new Error("'options' required!")); 51 | } 52 | 53 | const reqOptions = { 54 | method: 'GET' as const, 55 | url: getURL('reactionURI'), 56 | rest: { 57 | channelID: channelId, 58 | messageID: reactionToCreate?.message_id, 59 | emojiType: reactionToCreate?.emoji_type, 60 | emojiID: reactionToCreate?.emoji_id, 61 | }, 62 | params: options, 63 | }; 64 | return this.request(reqOptions); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/openapi/v1/resource.ts: -------------------------------------------------------------------------------- 1 | const apiMap = { 2 | guildURI: '/guilds/:guildID', 3 | guildMembersURI: '/guilds/:guildID/members', 4 | guildMemberURI: '/guilds/:guildID/members/:userID', 5 | channelsURI: '/guilds/:guildID/channels', 6 | channelURI: '/channels/:channelID', 7 | guildAnnouncesURI: '/guilds/:guildID/announces', 8 | guildAnnounceURI: '/guilds/:guildID/announces/:messageID', 9 | channelAnnouncesURI: '/channels/:channelID/announces', 10 | channelAnnounceURI: '/channels/:channelID/announces/:messageID', 11 | messagesURI: '/channels/:channelID/messages', 12 | messageURI: '/channels/:channelID/messages/:messageID', 13 | userMeURI: '/users/@me', 14 | userMeGuildsURI: '/users/@me/guilds', 15 | muteURI: '/guilds/:guildID/mute', 16 | muteMemberURI: '/guilds/:guildID/members/:userID/mute', 17 | muteMembersURI: '/guilds/:guildID/mute', 18 | gatewayURI: '/gateway', 19 | gatewayBotURI: '/gateway/bot', 20 | audioControlURI: '/channels/:channelID/audio', 21 | rolesURI: '/guilds/:guildID/roles', 22 | roleURI: '/guilds/:guildID/roles/:roleID', 23 | memberRoleURI: '/guilds/:guildID/members/:userID/roles/:roleID', 24 | userMeDMURI: '/users/@me/dms', 25 | dmsURI: '/dms/:guildID/messages', 26 | channelPermissionsURI: '/channels/:channelID/members/:userID/permissions', 27 | channelRolePermissionsURI: '/channels/:channelID/roles/:roleID/permissions', 28 | schedulesURI: '/channels/:channelID/schedules', 29 | scheduleURI: '/channels/:channelID/schedules/:scheduleID', 30 | guildPermissionURI: '/guilds/:guildID/api_permission', 31 | guildPermissionDemandURI: '/guilds/:guildID/api_permission/demand', 32 | wsInfo: '/gateway/bot', 33 | reactionURI: '/channels/:channelID/messages/:messageID/reactions/:emojiType/:emojiID', 34 | pinsMessageIdURI: '/channels/:channelID/pins/:messageID', 35 | pinsMessageURI: '/channels/:channelID/pins', 36 | interactionURI: '/interactions/:interactionID', 37 | guildVoiceMembersURI: '/channels/:channelID/voice/members', // 语音子频道在线成员车查询 38 | botMic: '/channels/:channelID/mic', // 机器人上麦|下麦 39 | }; 40 | export const getURL = (endpoint: keyof typeof apiMap) => apiMap[endpoint]; 41 | -------------------------------------------------------------------------------- /src/openapi/v1/role.ts: -------------------------------------------------------------------------------- 1 | import { Config, OpenAPIRequest, GuildRoles, IRole, IRoleFilter, RoleAPI, UpdateRoleRes } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | // 默认的filter:0 1 代表是否设置 0-否 1-是 6 | export const defaultFilter: IRoleFilter = { 7 | name: 1, 8 | color: 1, 9 | hoist: 1, 10 | }; 11 | 12 | // 用户组默认颜色值 13 | export const defaultColor = 4278245297; 14 | export default class Role implements RoleAPI { 15 | public request: OpenAPIRequest; 16 | public config: Config; 17 | constructor(request: OpenAPIRequest, config: Config) { 18 | this.request = request; 19 | this.config = config; 20 | } 21 | // 获取频道身份组列表 22 | public roles(guildID: string): Promise> { 23 | const options = { 24 | method: 'GET' as const, 25 | url: getURL('rolesURI'), 26 | rest: { 27 | guildID, 28 | }, 29 | }; 30 | return this.request(options); 31 | } 32 | 33 | // 创建频道身份组 34 | public postRole( 35 | guildID: string, 36 | role: Omit, 37 | filter = defaultFilter, 38 | ): Promise> { 39 | if (role.color === 0) { 40 | role.color = defaultColor; 41 | } 42 | const options = { 43 | method: 'POST' as const, 44 | url: getURL('rolesURI'), 45 | rest: { 46 | guildID, 47 | }, 48 | data: { 49 | guild_id: guildID, 50 | filter, 51 | info: role, 52 | }, 53 | }; 54 | return this.request(options); 55 | } 56 | 57 | // 修改频道身份组 58 | public patchRole( 59 | guildID: string, 60 | roleID: string, 61 | role: IRole, 62 | filter = defaultFilter, 63 | ): Promise> { 64 | if (role.color === 0) { 65 | role.color = defaultColor; 66 | } 67 | 68 | const options = { 69 | method: 'PATCH' as const, 70 | url: getURL('roleURI'), 71 | rest: { 72 | guildID, 73 | roleID, 74 | }, 75 | data: { 76 | guild_id: guildID, 77 | filter, 78 | info: role, 79 | }, 80 | }; 81 | return this.request(options); 82 | } 83 | 84 | // 删除频道身份组 85 | public deleteRole(guildID: string, roleID: string): Promise> { 86 | const options = { 87 | method: 'DELETE' as const, 88 | url: getURL('roleURI'), 89 | rest: { 90 | guildID, 91 | roleID, 92 | }, 93 | }; 94 | return this.request(options); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/openapi/v1/schedule.ts: -------------------------------------------------------------------------------- 1 | import { Config, ISchedule, OpenAPIRequest, ScheduleAPI, ScheduleToCreate, ScheduleToPatch } from '@src/types'; 2 | import { RestyResponse } from 'resty-client'; 3 | import { getURL } from './resource'; 4 | 5 | export default class Schedule implements ScheduleAPI { 6 | public request: OpenAPIRequest; 7 | public config: Config; 8 | constructor(request: OpenAPIRequest, config: Config) { 9 | this.request = request; 10 | this.config = config; 11 | } 12 | 13 | // 获取日程列表 14 | public schedules(channelID: string, since?: string): Promise> { 15 | if (since && since.length !== 13) { 16 | return Promise.reject(new Error("Param 'since' is invalid, millisecond timestamp expected!")); 17 | } 18 | const options = { 19 | method: 'GET' as const, 20 | url: getURL('schedulesURI'), 21 | rest: { 22 | channelID, 23 | }, 24 | params: { 25 | since, 26 | }, 27 | }; 28 | return this.request(options); 29 | } 30 | 31 | // 获取日程 32 | public schedule(channelID: string, scheduleID: string): Promise> { 33 | const options = { 34 | method: 'GET' as const, 35 | url: getURL('scheduleURI'), 36 | rest: { 37 | channelID, 38 | scheduleID, 39 | }, 40 | }; 41 | return this.request(options); 42 | } 43 | 44 | // 创建日程 45 | public postSchedule(channelID: string, schedule: ScheduleToCreate): Promise> { 46 | const options = { 47 | method: 'POST' as const, 48 | url: getURL('schedulesURI'), 49 | rest: { 50 | channelID, 51 | }, 52 | data: { 53 | schedule, 54 | }, 55 | }; 56 | return this.request(options); 57 | } 58 | 59 | // 修改日程 60 | public patchSchedule( 61 | channelID: string, 62 | scheduleID: string, 63 | schedule: ScheduleToPatch, 64 | ): Promise> { 65 | const options = { 66 | method: 'PATCH' as const, 67 | url: getURL('scheduleURI'), 68 | rest: { 69 | channelID, 70 | scheduleID, 71 | }, 72 | data: { 73 | schedule, 74 | }, 75 | }; 76 | return this.request(options); 77 | } 78 | 79 | // 删除日程 80 | public deleteSchedule(channelID: string, scheduleID: string): Promise> { 81 | const options = { 82 | method: 'DELETE' as const, 83 | url: getURL('scheduleURI'), 84 | rest: { 85 | channelID, 86 | scheduleID, 87 | }, 88 | }; 89 | return this.request(options); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type Nullish = null | undefined; 2 | 3 | export * from './websocket-types'; 4 | export * from './openapi'; 5 | -------------------------------------------------------------------------------- /src/types/openapi/index.ts: -------------------------------------------------------------------------------- 1 | import { RequestOptions, RestyResponse } from 'resty-client'; 2 | import { AudioAPI } from './v1/audio'; 3 | import { ChannelAPI } from './v1/channel'; 4 | import { ChannelPermissionsAPI } from './v1/channel-permission'; 5 | import { DirectMessageAPI } from './v1/direct-message'; 6 | import { GuildAPI } from './v1/guild'; 7 | import { MeAPI } from './v1/me'; 8 | import { MemberAPI } from './v1/member'; 9 | import { MessageAPI } from './v1/message'; 10 | import { RoleAPI } from './v1/role'; 11 | import { MuteAPI } from './v1/mute'; 12 | import { AnnounceAPI } from './v1/announce'; 13 | import { ScheduleAPI } from './v1/schedule'; 14 | import { ReactionAPI } from './v1/reaction'; 15 | import { InteractionAPI } from './v1/interaction'; 16 | import { PinsMessageAPI } from './v1/pins-message'; 17 | import { GuildPermissionsAPI } from './v1/guild-permission'; 18 | 19 | export type OpenAPIRequest = = any>(options: RequestOptions) => Promise>; 20 | 21 | export interface Config { 22 | appID: string; 23 | token: string; 24 | sandbox?: boolean; 25 | } 26 | 27 | export interface IOpenAPI { 28 | config: Config; 29 | request: OpenAPIRequest; 30 | guildApi: GuildAPI; 31 | channelApi: ChannelAPI; 32 | meApi: MeAPI; 33 | messageApi: MessageAPI; 34 | memberApi: MemberAPI; 35 | roleApi: RoleAPI; 36 | muteApi: MuteAPI; 37 | announceApi: AnnounceAPI; 38 | scheduleApi: ScheduleAPI; 39 | directMessageApi: DirectMessageAPI; 40 | channelPermissionsApi: ChannelPermissionsAPI; 41 | audioApi: AudioAPI; 42 | guildPermissionsApi: GuildPermissionsAPI; 43 | reactionApi: ReactionAPI; 44 | interactionApi: InteractionAPI; 45 | pinsMessageApi: PinsMessageAPI; 46 | } 47 | 48 | export type APIVersion = `v${number}`; 49 | 50 | export interface Token { 51 | appID: number; 52 | accessToken: string; 53 | type: string; 54 | } 55 | 56 | // WebsocketAPI websocket 接入地址 57 | export interface WebsocketAPI { 58 | ws: () => any; 59 | } 60 | 61 | export * from './v1/audio'; 62 | export * from './v1/channel'; 63 | export * from './v1/channel-permission'; 64 | export * from './v1/direct-message'; 65 | export * from './v1/guild'; 66 | export * from './v1/me'; 67 | export * from './v1/member'; 68 | export * from './v1/message'; 69 | export * from './v1/role'; 70 | export * from './v1/mute'; 71 | export * from './v1/announce'; 72 | export * from './v1/schedule'; 73 | export * from './v1/reaction'; 74 | export * from './v1/interaction'; 75 | export * from './v1/pins-message'; 76 | export * from './v1/guild-permission'; 77 | -------------------------------------------------------------------------------- /src/types/openapi/v1/announce.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= Announce 公告接口 ============= 5 | */ 6 | export interface AnnounceAPI { 7 | postGuildAnnounce: (guildID: string, channelID: string, messageID: string) => Promise>; 8 | deleteGuildAnnounce: (guildID: string, messageID: string) => Promise>; 9 | postGuildRecommend: (guildID: string, recommendObj: RecommendObj) => Promise>; 10 | postChannelAnnounce: (channelID: string, messageID: string) => Promise>; 11 | deleteChannelAnnounce: (channelID: string, messageID: string) => Promise>; 12 | } 13 | 14 | // 公告对象(Announce) 15 | export interface IAnnounce { 16 | guild_id: string; // 频道ID 17 | channel_id: string; // 子频道ID 18 | message_id: string; // 消息ID 19 | announce_type?: number; // 推荐类别 0:成员公告; 1:欢迎公告 20 | recommend_channels?: RecommendChannel[]; // 推荐子频道列表 21 | } 22 | 23 | export interface RecommendObj { 24 | announces_type?: number; // 公告类别 0:成员公告,1:欢迎公告,默认为成员公告 25 | recommend_channels: RecommendChannel[]; // 推荐子频道列表 26 | } 27 | 28 | export interface RecommendChannel { 29 | channel_id: string; 30 | introduce: string; // 推荐语 31 | } 32 | -------------------------------------------------------------------------------- /src/types/openapi/v1/audio.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | // ============= Audio 音频接口 ============= 4 | export interface AudioAPI { 5 | postAudio: (channelID: string, value: AudioControl) => Promise>; 6 | botOnMic: (channelID: string) => Promise>; 7 | botOffMic: (channelID: string) => Promise>; 8 | } 9 | 10 | // 语音对象-参数 11 | export interface AudioControl { 12 | audioUrl: string; 13 | text: string; 14 | status: number; 15 | } 16 | -------------------------------------------------------------------------------- /src/types/openapi/v1/channel-permission.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= ChannelPermissions 子频道权限接口 ============= 5 | */ 6 | export interface ChannelPermissionsAPI { 7 | // ChannelPermissions 获取指定子频道的权限 8 | channelPermissions: (channelID: string, userID: string) => Promise>; 9 | // PutChannelPermissions 修改指定子频道的权限 10 | putChannelPermissions: ( 11 | channelID: string, 12 | userID: string, 13 | p: UpdateChannelPermissions, 14 | ) => Promise>; 15 | 16 | // ChannelRolePermissions 获取指定子频道身份组的权限 17 | channelRolePermissions: (channelID: string, roleID: string) => Promise>; 18 | // PutChannelRolePermissions 修改指定子频道身份组的权限 19 | putChannelRolePermissions: ( 20 | channelID: string, 21 | roleID: string, 22 | p: UpdateChannelPermissions, 23 | ) => Promise>; 24 | } 25 | 26 | export interface IChannelPermissions { 27 | channel_id: string; 28 | user_id: string; 29 | permissions: string; 30 | } 31 | 32 | export interface IChannelRolePermissions { 33 | channel_id: string; 34 | role_id: string; 35 | permissions: string; 36 | } 37 | 38 | export interface UpdateChannelPermissions { 39 | add: string; 40 | remove: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/types/openapi/v1/channel.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= Channel 子频道接口 ============= 5 | */ 6 | export interface ChannelAPI { 7 | channel: (channelID: string) => Promise>; 8 | channels: (guildID: string) => Promise>; 9 | postChannel: (guildID: string, channel: PostChannelObj) => Promise>; 10 | patchChannel: (channelID: string, channel: PatchChannelObj) => Promise>; 11 | deleteChannel: (channelID: string) => Promise>; 12 | } 13 | 14 | // 子频道类型 ChannelType 15 | // 0.文字子频道 1.保留,不可用 2.语音子频道 3.保留,不可用 4.子频道分类 10005.直播子频道 16 | export type ChannelType = 0 | 1 | 2 | 3 | 4 | 10005; 17 | 18 | // 子频道子类型 ChannelSubType 19 | // 0.闲聊 1.公告 2.攻略 3.开黑 20 | export type ChannelSubType = 0 | 1 | 2 | 3; 21 | 22 | // 子频道对象(Channel) 23 | export interface IChannel extends PostChannelObj { 24 | id: string; // 频道 ID 25 | guild_id: string; // 群 ID 26 | owner_id: string; // 拥有者 ID 27 | speak_permission?: number; // 子频道发言权限 28 | application_id?: string; // 用于标识应用子频道应用类型 29 | } 30 | 31 | export interface PostChannelObj { 32 | name: string; // 频道名称 33 | type: ChannelType; // 频道类型 34 | sub_type?: ChannelSubType; // 子频道子类型 35 | position: number; // 排序位置 36 | parent_id: string; // 父频道的ID 37 | private_type?: number; // 子频道私密类型 38 | private_user_ids?: string[]; // 子频道私密类型成员 ID 39 | permissions?: string; // 用户拥有的子频道权限 40 | } 41 | 42 | export type PatchChannelObj = Partial>; 43 | -------------------------------------------------------------------------------- /src/types/openapi/v1/direct-message.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | import { IMessage, MessageToCreate } from './message'; 3 | 4 | /** 5 | * ============= DirectMessage 私信接口 ============= 6 | */ 7 | export interface DirectMessageAPI { 8 | // CreateDirectMessage 创建私信频道 9 | createDirectMessage: (dm: DirectMessageToCreate) => Promise>; 10 | // PostDirectMessage 在私信频道内发消息 11 | postDirectMessage: (guildID: string, msg: MessageToCreate) => Promise>; 12 | } 13 | 14 | // DirectMessageToCreate 创建私信频道的结构体定义 15 | export interface DirectMessageToCreate { 16 | source_guild_id: string; // 频道ID 17 | recipient_id: string; // 用户ID 18 | } 19 | 20 | // 子频道权限对象(ChannelPermissions) 21 | export interface IDirectMessage { 22 | guild_id: string; // 频道ID 23 | channel_id: string; // 子频道id 24 | create_time: string; // 私信频道创建的时间戳 25 | } 26 | -------------------------------------------------------------------------------- /src/types/openapi/v1/guild-permission.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= GuildPermission API权限接口 ============= 5 | */ 6 | export interface GuildPermissionsAPI { 7 | permissions: (guildID: string) => Promise>; 8 | postPermissionDemand: ( 9 | guildID: string, 10 | permissionDemandObj: PermissionDemandToCreate, 11 | ) => Promise>; 12 | } 13 | 14 | export interface GuildPermission { 15 | path: string; // API 接口名,例如 /guilds/{guild_id}/members/{user_id} 16 | method: string; // 请求方法,例如 GET 17 | desc: string; // API 接口名称,例如 获取频道信 18 | auth_status: number; // 授权状态,auth_stats 为 1 时已授权 19 | } 20 | 21 | export interface GuildPermissionRes { 22 | apis: GuildPermission[]; 23 | } 24 | 25 | export interface GuildPermissionDemand { 26 | guild_id: string; // 申请接口权限的频道 id 27 | channel_id: string; // 接口权限需求授权链接发送的子频道 id 28 | api_identify: GuildPermissionDemandIdentify; // 权限接口唯一标识 29 | title: string; // 接口权限链接中的接口权限描述信息 30 | desc: string; // 接口权限链接中的机器人可使用功能的描述信息 31 | } 32 | 33 | export interface PermissionDemandToCreate { 34 | channel_id: string; // 授权链接发送的子频道 ID 35 | api_identify: GuildPermissionDemandIdentify; // api 权限需求标识对象 36 | desc?: string; // 机器人申请对应的 API 接口权限后可以使用功能的描述 37 | } 38 | 39 | export interface GuildPermissionDemandIdentify { 40 | path: string; // API 接口名,例如 /guilds/{guild_id}/members/{user_id} 41 | method: string; // 请求方法,例如 GET 42 | } 43 | -------------------------------------------------------------------------------- /src/types/openapi/v1/guild.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | import { IChannel } from './channel'; 3 | import { IUser } from './me'; 4 | 5 | /** 6 | * ============= Guild 频道接口 ============= 7 | */ 8 | export interface GuildAPI { 9 | guild: (guildID: string) => Promise>; 10 | guildMember: (guildID: string, userID: string) => Promise>; 11 | guildMembers: (guildID: string, pager?: GuildMembersPager) => Promise>; 12 | deleteGuildMember: (guildID: string, userID: string) => Promise>; 13 | guildVoiceMembers: (channelID: string) => Promise>; 14 | } 15 | 16 | // 频道对象(Guild) 17 | export interface IGuild { 18 | id: string; // 频道ID(与客户端上看到的频道ID不同) 19 | name: string; // 频道名称 20 | icon: string; // 频道头像 21 | owner_id: string; // 拥有者ID 22 | owner: boolean; // 是否为拥有者 23 | member_count: number; // 成员数量 24 | max_members: number; // 最大成员数目 25 | description: string; // 频道描述 26 | joined_at: number; // 当前用户加入群的时间 27 | channels: IChannel[]; // 频道列表 28 | unionworld_id: string; // 游戏绑定公会区服ID 29 | union_org_id: string; // 游戏绑定公会/战队ID 30 | } 31 | 32 | // Member 群成员 33 | export interface IMember { 34 | guild_id: string; 35 | joined_at: string; 36 | nick: string; 37 | user: IUser; 38 | roles: string[]; 39 | deaf: boolean; 40 | mute: boolean; 41 | } 42 | 43 | // 语音子频道参与语音的Member群成员 44 | export interface IVoiceMember { 45 | user: IUser; 46 | nick: string; 47 | joined_at: string; 48 | mute: boolean; 49 | } 50 | 51 | export interface GuildMembersPager { 52 | // 上一次回包中最大的用户ID, 如果是第一次请求填0,默认为0 53 | after: string; 54 | limit: number; 55 | } 56 | -------------------------------------------------------------------------------- /src/types/openapi/v1/interaction.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= Interaction 接口 ============= 5 | */ 6 | export interface InteractionAPI { 7 | putInteraction: (interactionID: string, interactionData: InteractionData) => Promise>; 8 | } 9 | 10 | export interface InteractionData { 11 | code: number; // 0成功,1操作失败,2操作频繁,3重复操作,4没有权限,5仅管理员操作 12 | } 13 | -------------------------------------------------------------------------------- /src/types/openapi/v1/me.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | import { IGuild } from './guild'; 3 | 4 | /** 5 | * ============= User 用户接口 ============= 6 | */ 7 | export interface MeAPI { 8 | me: () => Promise>; 9 | meGuilds: (options?: MeGuildsReq) => Promise>; 10 | } 11 | export interface IUser { 12 | id: string; 13 | username: string; 14 | avatar: string; 15 | bot: boolean; 16 | union_openid: string; // 特殊关联应用的 openid 17 | union_user_account: string; // 机器人关联的用户信息,与union_openid关联的应用是同一个 18 | } 19 | 20 | export interface MeGuildsReq { 21 | before?: string; // 读此id之前的数据 22 | after?: string; // 读此id之后的数据 23 | limit?: number; // 每次拉取多少条数据 最大不超过 100 24 | } 25 | -------------------------------------------------------------------------------- /src/types/openapi/v1/member.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | import { IChannel } from './channel'; 3 | 4 | /** 5 | * ============= Member 成员接口 ============= 6 | */ 7 | export interface MemberAPI { 8 | memberAddRole: ( 9 | guildID: string, 10 | roleID: string, 11 | userID: string, 12 | /** 兼容原来传递 channel 对象的逻辑,后续仅支持 string */ 13 | channel?: string | MemberAddRoleBody, 14 | ) => Promise>; 15 | memberDeleteRole: ( 16 | guildID: string, 17 | roleID: string, 18 | userID: string, 19 | /** 兼容原来传递 channel 对象的逻辑,后续仅支持 string */ 20 | channel?: string | MemberAddRoleBody, 21 | ) => Promise>; 22 | } 23 | 24 | // 身份组添加、删除成员 只需要传id 25 | export type MemberAddRoleBody = Pick; 26 | -------------------------------------------------------------------------------- /src/types/openapi/v1/message.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | import { IMember } from './guild'; 3 | import { IUser } from './me'; 4 | 5 | /** 6 | * ============= Message 消息接口 ============= 7 | */ 8 | export interface MessageAPI { 9 | message: (channelID: string, messageID: string) => Promise>; 10 | messages: (channelID: string, pager: MessagesPager) => Promise>; 11 | postMessage: (channelID: string, message: MessageToCreate) => Promise>; 12 | deleteMessage: (channelID: string, messageID: string, hideTip?: boolean) => Promise>; 13 | } 14 | 15 | // MessageAttachment 附件定义 16 | export interface MessageAttachment { 17 | url: string; 18 | } 19 | 20 | export interface EmbedThumbnail { 21 | url: string; 22 | } 23 | // EmbedField Embed字段描述 24 | 25 | export interface EmbedField { 26 | name: string; // 字段名 27 | } 28 | export interface Embed { 29 | title: string; 30 | description?: string; 31 | prompt?: string; 32 | thumbnail?: EmbedThumbnail; 33 | fields?: EmbedField[]; 34 | } 35 | 36 | // Ark 消息模版 37 | export interface Ark { 38 | template_id: string; // ark 模版 ID 39 | kv: ArkKV[]; 40 | } 41 | 42 | // ArkKV Ark 键值对 43 | export interface ArkKV { 44 | key: string; 45 | value: string; 46 | obj: ArkObj[]; 47 | } 48 | 49 | // ArkObj Ark 对象 50 | export interface ArkObj { 51 | obj_kv: ArkObjKV[]; 52 | } 53 | 54 | // ArkObjKV Ark 对象键值对 55 | export interface ArkObjKV { 56 | key: string; 57 | value: string; 58 | } 59 | 60 | // 消息对象(Message) 61 | export interface IMessage { 62 | id: string; // 消息ID 63 | channel_id: string; // 子频道ID 64 | guild_id: string; // 频道ID 65 | content: string; // 内容 66 | timestamp: string; // 发送时间 67 | edited_timestamp: string; // 消息编辑时间 68 | mention_everyone: boolean; // 是否@all 69 | author: IUser; 70 | member: IMember; // 消息发送方Author的member属性,只是部分属性 71 | attachments: MessageAttachment[]; // 附件 72 | embeds: Embed[]; // 结构化消息-embeds 73 | mentions: IUser[]; // 消息中的提醒信息(@)列表 74 | ark: Ark; // ark 消息 75 | seq?: number; // 用于消息间的排序 76 | seq_in_channel?: string; // 子频道消息 seq 77 | } 78 | 79 | // 接口返回的数据多一层message 80 | export interface IMessageRes { 81 | message: IMessage; 82 | } 83 | 84 | // MessagesPager 消息分页 85 | export interface MessagesPager { 86 | // around: 读此id前后的消息 before:读此id之前的消息 after:读此id之后的消息 87 | type: 'around' | 'before' | 'after'; // 拉取类型 88 | id: string; // 消息ID 89 | limit: string; // 最大20 90 | } 91 | 92 | export interface MessageReference { 93 | message_id: string; // 需要引用回复的消息 ID 94 | ignore_get_message_error?: boolean; // 是否忽略获取引用消息详情错误,默认否(如不忽略,当获取引用消息详情出错时,消息将不会发出) 95 | } 96 | 97 | // 消息体结构 98 | export interface MessageToCreate { 99 | content?: string; 100 | embed?: Embed; 101 | ark?: Ark; 102 | message_reference?: MessageReference; 103 | image?: string; 104 | msg_id?: string; // 要回复的消息id,不为空则认为是被动消息,公域机器人会异步审核,不为空是被动消息,公域机器人会校验语料 105 | keyboard?: MessageKeyboard; 106 | } 107 | 108 | // MessageKeyboard 消息按钮组件 109 | export interface MessageKeyboard { 110 | id?: string; 111 | content?: CustomKeyboard; 112 | } 113 | 114 | // CustomKeyboard 自定义 Keyboard 115 | export interface CustomKeyboard { 116 | rows?: Row[]; 117 | } 118 | 119 | // Row 每行结构 120 | export interface Row { 121 | buttons?: Button[]; 122 | } 123 | 124 | // Button 单个按纽 125 | export interface Button { 126 | id?: string; // 按钮 ID 127 | render_data?: RenderData; // 渲染展示字段 128 | action?: Action; // 该按纽操作相关字段 129 | } 130 | 131 | // RenderData 按纽渲染展示 132 | export interface RenderData { 133 | label?: string; // 按纽上的文字 134 | visited_label?: string; // 点击后按纽上文字 135 | style?: number; // 按钮样式,0:灰色线框,1:蓝色线框 136 | } 137 | 138 | // Action 按纽点击操作 139 | export interface Action { 140 | type?: number; // 操作类型 141 | permission?: Permission; // 可操作 142 | click_limit?: number; // 可点击的次数, 默认不限 143 | data?: string; // 操作相关数据 144 | at_bot_show_channel_list?: boolean; // false:当前 true:弹出展示子频道选择器 145 | } 146 | 147 | // Permission 按纽操作权限 148 | export interface Permission { 149 | type?: number; // PermissionType 按钮的权限类型 150 | specify_role_ids?: string[]; // SpecifyRoleIDs 身份组 151 | specify_user_ids?: string[]; // SpecifyUserIDs 指定 UserID 152 | } 153 | -------------------------------------------------------------------------------- /src/types/openapi/v1/mute.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= Mute 禁言接口 ============= 5 | */ 6 | export interface MuteAPI { 7 | muteMember: (guildID: string, userID: string, options: MuteOptions) => Promise>; 8 | muteAll: (guildID: string, options: MuteOptions) => Promise>; 9 | muteMembers: (guildID: string, userIDList: Array, options: MuteOptions) => Promise>; 10 | } 11 | 12 | export interface MuteOptions { 13 | timeTo?: string; // 禁言到期时间戳,绝对时间戳,单位:秒 14 | seconds?: string; // 禁言多少秒(两个字段二选一,默认以 timeTo 为准) 15 | } 16 | -------------------------------------------------------------------------------- /src/types/openapi/v1/pins-message.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= PinsMessage 接口 ============= 5 | */ 6 | export interface PinsMessageAPI { 7 | pinsMessage: (channelID: string) => Promise>; 8 | putPinsMessage: (channelID: string, messageID: string) => Promise>; 9 | deletePinsMessage: (channelID: string, messageID: string) => Promise>; 10 | } 11 | 12 | export interface IPinsMessage { 13 | guild_id: string; 14 | channel_id: string; 15 | message_ids: string[]; 16 | } 17 | -------------------------------------------------------------------------------- /src/types/openapi/v1/reaction.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= Reaction 接口 ============= 5 | */ 6 | export interface ReactionAPI { 7 | postReaction: (channelID: string, reactionToCreate: ReactionObj) => Promise>; 8 | deleteReaction: (channelID: string, reactionToDelete: ReactionObj) => Promise>; 9 | getReactionUserList: ( 10 | channelID: string, 11 | reactionToDelete: ReactionObj, 12 | options: ReactionUserListObj, 13 | ) => Promise>; 14 | } 15 | 16 | export interface ReactionObj { 17 | message_id: string; 18 | emoji_type: number; 19 | emoji_id: string; 20 | } 21 | 22 | // 拉取表情表态用户列表分页入参 23 | export interface ReactionUserListObj { 24 | cookie: string; // 上次请求返回的cookie,第一次请求无需填写 25 | limit: number; // 每次拉取数量,默认20,最多50,只在第一次请求时设置 26 | } 27 | -------------------------------------------------------------------------------- /src/types/openapi/v1/role.ts: -------------------------------------------------------------------------------- 1 | import { RestyResponse } from 'resty-client'; 2 | 3 | /** 4 | * ============= Role 身份组接口 ============= 5 | */ 6 | export interface RoleAPI { 7 | roles: (guildID: string) => Promise>; 8 | postRole: (guildID: string, role: Omit, filter?: IRoleFilter) => Promise>; 9 | patchRole: ( 10 | guildID: string, 11 | roleID: string, 12 | role: IRole, 13 | filter?: IRoleFilter, 14 | ) => Promise>; 15 | deleteRole: (guildID: string, roleID: string) => Promise>; 16 | } 17 | 18 | // 所创建的频道身份组对象 19 | export interface IRole { 20 | id: string; // 身份组ID, 默认值可参考DefaultRoles 21 | name: string; // 名称 22 | color: number; // ARGB的HEX十六进制颜色值转换后的十进制数值 23 | hoist: number; // 是否在成员列表中单独展示: 0-否, 1-是 24 | number: number; // 人数 不会被修改,创建接口修改 25 | member_limit: number; // 成员上限 不会被修改,创建接口修改 26 | } 27 | 28 | // 指定创建、更新频道身份组涉及的字段 29 | export interface IRoleFilter { 30 | name?: number; 31 | color?: number; 32 | hoist?: number; 33 | } 34 | export interface GuildRoles { 35 | guild_id: string; // 频道ID 36 | roles: IRole[]; // 一组频道身份组对象 37 | role_num_limit: string; // 默认分组上限 38 | } 39 | 40 | // UpdateResult 创建,删除等行为的返回 41 | export interface UpdateRoleRes { 42 | role_id: string; // 身份组ID 43 | guild_id: string; // 频道ID 44 | role: IRole; // 所创建的频道身份组对象 45 | } 46 | -------------------------------------------------------------------------------- /src/types/openapi/v1/schedule.ts: -------------------------------------------------------------------------------- 1 | import { IMember } from './guild'; 2 | import { RestyResponse } from 'resty-client'; 3 | 4 | /** 5 | * ============= Schedule 日程接口 ============= 6 | */ 7 | export interface ScheduleAPI { 8 | // 获取日程信息 9 | schedule: (channelID: string, scheduleID: string) => Promise>; 10 | 11 | // 获取日程列表 12 | schedules: (channelID: string, since?: string) => Promise>; 13 | 14 | // 创建日程 15 | postSchedule: (channelID: string, schedule: ScheduleToCreate) => Promise>; 16 | 17 | // 修改日程 18 | patchSchedule: ( 19 | channelID: string, 20 | scheduleID: string, 21 | schedule: ScheduleToPatch, 22 | ) => Promise>; 23 | 24 | // 删除日程 25 | deleteSchedule: (channelID: string, scheduleID: string) => Promise>; 26 | } 27 | 28 | // 0 - 不提醒 29 | // 1 - 开始时提醒 30 | // 2 - 开始前5分钟提醒 31 | // 3 - 开始前15分钟提醒 32 | // 4 - 开始前30分钟提醒 33 | // 5 - 开始前60分钟提醒 34 | export type ScheduleRemindType = '0' | '1' | '2' | '3' | '4' | '5'; 35 | 36 | export interface ScheduleToCreate { 37 | name: string; // 日程名称 38 | description?: string; // 日程描述 39 | creator?: IMember; // 创建者 40 | start_timestamp: string; // 日程开始时间戳(ms) 41 | end_timestamp: string; // 日程结束时间戳(ms) 42 | jump_channel_id?: string; // 日程开始时跳转到的子频道 id 43 | remind_type: ScheduleRemindType; // 日程提醒类型 44 | } 45 | 46 | export interface ISchedule extends ScheduleToCreate { 47 | id: string; // 日程 id 48 | } 49 | 50 | export type ScheduleToPatch = Partial>; 51 | -------------------------------------------------------------------------------- /src/types/websocket-types.ts: -------------------------------------------------------------------------------- 1 | import { apiVersion } from '@src/openapi/v1/openapi'; 2 | import { getURL } from '@src/openapi/v1/resource'; 3 | import { buildUrl } from '@src/utils/utils'; 4 | 5 | // websocket建立成功回包 6 | export interface wsResData { 7 | op: number; // opcode ws的类型 8 | d?: { 9 | // 事件内容 10 | heartbeat_interval?: number; // 心跳时间间隔 11 | }; 12 | s: number; // 心跳的唯一标识 13 | t: string; // 事件类型 14 | id?: string; // 事件ID 15 | } 16 | 17 | // 发送心跳入参 18 | export interface HeartbeatParam { 19 | op: number; 20 | d: number; 21 | } 22 | 23 | // 事件分发类型 24 | export interface EventTypes { 25 | eventType: string; 26 | eventMsg?: object; 27 | } 28 | 29 | // 请求得到ws地址的参数 30 | export interface GetWsParam { 31 | appID: string; 32 | token: string; 33 | sandbox?: boolean; 34 | shards?: Array; 35 | intents?: Array; 36 | maxRetry?: number; 37 | } 38 | 39 | // 请求ws地址回包对象 40 | export interface WsAddressObj { 41 | url: string; 42 | shards: number; 43 | session_start_limit: { 44 | total: number; 45 | remaining: number; 46 | reset_after: number; 47 | max_concurrency: number; 48 | }; 49 | } 50 | 51 | // ws信息 52 | export interface WsDataInfo { 53 | data: WsAddressObj; 54 | } 55 | 56 | // 会话记录 57 | export interface SessionRecord { 58 | sessionID: string; 59 | seq: number; 60 | } 61 | 62 | // 心跳参数 63 | export enum OpCode { 64 | DISPATCH = 0, // 服务端进行消息推送 65 | HEARTBEAT = 1, // 客户端发送心跳 66 | IDENTIFY = 2, // 鉴权 67 | RESUME = 6, // 恢复连接 68 | RECONNECT = 7, // 服务端通知客户端重连 69 | INVALID_SESSION = 9, // 当identify或resume的时候,如果参数有错,服务端会返回该消息 70 | HELLO = 10, // 当客户端与网关建立ws连接之后,网关下发的第一条消息 71 | HEARTBEAT_ACK = 11, // 当发送心跳成功之后,就会收到该消息 72 | } 73 | 74 | // 可使用的intents事件类型 75 | export enum AvailableIntentsEventsEnum { 76 | GUILDS = 'GUILDS', 77 | GUILD_MEMBERS = 'GUILD_MEMBERS', 78 | GUILD_MESSAGES = 'GUILD_MESSAGES', 79 | GUILD_MESSAGE_REACTIONS = 'GUILD_MESSAGE_REACTIONS', 80 | DIRECT_MESSAGE = 'DIRECT_MESSAGE', 81 | FORUMS_EVENT = 'FORUMS_EVENT', 82 | AUDIO_ACTION = 'AUDIO_ACTION', 83 | PUBLIC_GUILD_MESSAGES = 'PUBLIC_GUILD_MESSAGES', 84 | MESSAGE_AUDIT = 'MESSAGE_AUDIT', 85 | INTERACTION = 'INTERACTION', 86 | } 87 | 88 | // OpenAPI传过来的事件类型 89 | export const WsEventType: { [key: string]: AvailableIntentsEventsEnum } = { 90 | // ======= GUILDS ====== 91 | GUILD_CREATE: AvailableIntentsEventsEnum.GUILDS, // 频道创建 92 | GUILD_UPDATE: AvailableIntentsEventsEnum.GUILDS, // 频道更新 93 | GUILD_DELETE: AvailableIntentsEventsEnum.GUILDS, // 频道删除 94 | CHANNEL_CREATE: AvailableIntentsEventsEnum.GUILDS, // 子频道创建 95 | CHANNEL_UPDATE: AvailableIntentsEventsEnum.GUILDS, // 子频道更新 96 | CHANNEL_DELETE: AvailableIntentsEventsEnum.GUILDS, // 子频道删除 97 | 98 | // ======= GUILD_MEMBERS ====== 99 | GUILD_MEMBER_ADD: AvailableIntentsEventsEnum.GUILD_MEMBERS, // 频道成员加入 100 | GUILD_MEMBER_UPDATE: AvailableIntentsEventsEnum.GUILD_MEMBERS, // 频道成员更新 101 | GUILD_MEMBER_REMOVE: AvailableIntentsEventsEnum.GUILD_MEMBERS, // 频道成员移除 102 | 103 | // ======= GUILD_MESSAGES ====== 104 | MESSAGE_CREATE: AvailableIntentsEventsEnum.GUILD_MESSAGES, // 机器人收到频道消息时触发 105 | MESSAGE_DELETE: AvailableIntentsEventsEnum.GUILD_MESSAGES, // 删除(撤回)消息事件 106 | 107 | // ======= GUILD_MESSAGE_REACTIONS ====== 108 | MESSAGE_REACTION_ADD: AvailableIntentsEventsEnum.GUILD_MESSAGE_REACTIONS, // 为消息添加表情表态 109 | MESSAGE_REACTION_REMOVE: AvailableIntentsEventsEnum.GUILD_MESSAGE_REACTIONS, // 为消息删除表情表态 110 | 111 | // ======= DIRECT_MESSAGE ====== 112 | DIRECT_MESSAGE_CREATE: AvailableIntentsEventsEnum.DIRECT_MESSAGE, // 当收到用户发给机器人的私信消息时 113 | DIRECT_MESSAGE_DELETE: AvailableIntentsEventsEnum.DIRECT_MESSAGE, // 删除(撤回)消息事件 114 | 115 | // ======= INTERACTION ====== 116 | INTERACTION_CREATE: AvailableIntentsEventsEnum.INTERACTION, // 互动事件创建时 117 | 118 | // ======= MESSAGE_AUDIT ====== 119 | MESSAGE_AUDIT_PASS: AvailableIntentsEventsEnum.MESSAGE_AUDIT, // 消息审核通过 120 | MESSAGE_AUDIT_REJECT: AvailableIntentsEventsEnum.MESSAGE_AUDIT, // 消息审核不通过 121 | 122 | // ======= FORUMS_EVENT ====== 123 | FORUM_THREAD_CREATE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户创建帖子时 124 | FORUM_THREAD_UPDATE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户更新帖子时 125 | FORUM_THREAD_DELETE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户删除帖子题时 126 | FORUM_POST_CREATE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户创建回帖时 127 | FORUM_POST_DELETE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户删除回帖时 128 | FORUM_REPLY_CREATE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户回复评论时 129 | FORUM_REPLY_DELETE: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户删除评论时 130 | FORUM_PUBLISH_AUDIT_RESULT: AvailableIntentsEventsEnum.FORUMS_EVENT, // 当用户发表审核通过时 131 | 132 | // ======= AUDIO_ACTION ====== 133 | AUDIO_START: AvailableIntentsEventsEnum.AUDIO_ACTION, // 音频开始播放 134 | AUDIO_FINISH: AvailableIntentsEventsEnum.AUDIO_ACTION, // 音频结束播放 135 | AUDIO_ON_MIC: AvailableIntentsEventsEnum.AUDIO_ACTION, // 机器人上麦 136 | AUDIO_OFF_MIC: AvailableIntentsEventsEnum.AUDIO_ACTION, // 机器人下麦 137 | 138 | // ======= PUBLIC_GUILD_MESSAGES ====== 139 | AT_MESSAGE_CREATE: AvailableIntentsEventsEnum.PUBLIC_GUILD_MESSAGES, // 机器人被@时触发 140 | PUBLIC_MESSAGE_DELETE: AvailableIntentsEventsEnum.PUBLIC_GUILD_MESSAGES, // 当频道的消息被删除时 141 | }; 142 | 143 | export const WSCodes = { 144 | 1000: 'WS_CLOSE_REQUESTED', 145 | 4004: 'TOKEN_INVALID', 146 | 4010: 'SHARDING_INVALID', 147 | 4011: 'SHARDING_REQUIRED', 148 | 4013: 'INVALID_INTENTS', 149 | 4014: 'DISALLOWED_INTENTS', 150 | }; 151 | 152 | // websocket错误码 153 | export const enum WebsocketCode { 154 | INVALID_OPCODE = 4001, // 无效的opcode 155 | INVALID_PAYLOAD = 4002, // 无效的payload 156 | ERROR_SEQ = 4007, // seq错误 157 | TOO_FAST_PAYLOAD = 4008, // 发送 payload 过快,请重新连接,并遵守连接后返回的频控信息 158 | EXPIRED = 4009, // 连接过期,请重连 159 | INVALID_SHARD = 4010, // 无效的shard 160 | TOO_MACH_GUILD = 4011, // 连接需要处理的guild过多,请进行合理分片 161 | INVALID_VERSION = 4012, // 无效的version 162 | INVALID_INTENTS = 4013, // 无效的intent 163 | DISALLOWED_INTENTS = 4014, // intent无权限 164 | ERROR = 4900, // 内部错误,请重连 165 | } 166 | 167 | // websocket错误原因 168 | export const WebsocketCloseReason = [ 169 | { 170 | code: 4001, 171 | reason: '无效的opcode', 172 | }, 173 | { 174 | code: 4002, 175 | reason: '无效的payload', 176 | }, 177 | { 178 | code: 4007, 179 | reason: 'seq错误', 180 | }, 181 | { 182 | code: 4008, 183 | reason: '发送 payload 过快,请重新连接,并遵守连接后返回的频控信息', 184 | resume: true, 185 | }, 186 | { 187 | code: 4009, 188 | reason: '连接过期,请重连', 189 | resume: true, 190 | }, 191 | { 192 | code: 4010, 193 | reason: '无效的shard', 194 | }, 195 | { 196 | code: 4011, 197 | reason: '连接需要处理的guild过多,请进行合理分片', 198 | }, 199 | { 200 | code: 4012, 201 | reason: '无效的version', 202 | }, 203 | { 204 | code: 4013, 205 | reason: '无效的intent', 206 | }, 207 | { 208 | code: 4014, 209 | reason: 'intent无权限', 210 | }, 211 | { 212 | code: 4900, 213 | reason: '内部错误,请重连', 214 | }, 215 | { 216 | code: 4914, 217 | reason: '机器人已下架,只允许连接沙箱环境,请断开连接,检验当前连接环境', 218 | }, 219 | { 220 | code: 4915, 221 | reason: '机器人已封禁,不允许连接,请断开连接,申请解封后再连接', 222 | }, 223 | ]; 224 | 225 | export type IntentEventsMapType = { 226 | [key in AvailableIntentsEventsEnum]: number; 227 | }; 228 | 229 | // 用户输入的intents类型 230 | export const IntentEvents: IntentEventsMapType = { 231 | GUILDS: 1 << 0, 232 | GUILD_MEMBERS: 1 << 1, 233 | GUILD_MESSAGES: 1 << 9, 234 | GUILD_MESSAGE_REACTIONS: 1 << 10, 235 | DIRECT_MESSAGE: 1 << 12, 236 | INTERACTION: 1 << 26, 237 | MESSAGE_AUDIT: 1 << 27, 238 | FORUMS_EVENT: 1 << 28, 239 | AUDIO_ACTION: 1 << 29, 240 | PUBLIC_GUILD_MESSAGES: 1 << 30, 241 | }; 242 | 243 | // intents 244 | export const Intents = { 245 | GUILDS: 0, 246 | GUILD_MEMBERS: 1, 247 | GUILD_BANS: 2, 248 | GUILD_EMOJIS: 3, 249 | GUILD_INTEGRATIONS: 4, 250 | GUILD_WEBHOOKS: 5, 251 | GUILD_INVITES: 6, 252 | GUILD_VOICE_STATES: 7, 253 | GUILD_PRESENCES: 8, 254 | GUILD_MESSAGES: 9, 255 | GUILD_MESSAGE_REACTIONS: 10, 256 | GUILD_MESSAGE_TYPING: 11, 257 | DIRECT_MESSAGES: 12, 258 | DIRECT_MESSAGE_REACTIONS: 13, 259 | DIRECT_MESSAGE_TYPING: 14, 260 | }; 261 | 262 | // Session事件 263 | export const SessionEvents = { 264 | CLOSED: 'CLOSED', 265 | READY: 'READY', // 已经可以通信 266 | ERROR: 'ERROR', // 会话错误 267 | INVALID_SESSION: 'INVALID_SESSION', 268 | RECONNECT: 'RECONNECT', // 服务端通知重新连接 269 | DISCONNECT: 'DISCONNECT', // 断线 270 | EVENT_WS: 'EVENT_WS', // 内部通信 271 | RESUMED: 'RESUMED', // 重连 272 | DEAD: 'DEAD', // 连接已死亡,请检查网络或重启 273 | }; 274 | 275 | // ws地址配置 276 | export const WsObjRequestOptions = (sandbox: boolean) => ({ 277 | method: 'GET' as const, 278 | url: buildUrl(getURL('wsInfo'), sandbox), 279 | headers: { 280 | Accept: '*/*', 281 | 'Accept-Encoding': 'utf-8', 282 | 'Accept-Language': 'zh-CN,zh;q=0.8', 283 | Connection: 'keep-alive', 284 | 'User-Agent': apiVersion, 285 | Authorization: '', 286 | }, 287 | }); 288 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | // 配置文件 2 | // 后台校验暂时用不到这块,所以给一个默认值 3 | export const Properties = { 4 | os: 'linux', 5 | browser: 'my_library', 6 | device: 'my_library', 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import log from 'loglevel'; 2 | log.setLevel('info'); 3 | log.setLevel('trace'); 4 | 5 | export const BotLogger = log; 6 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestHeaders } from 'axios'; 2 | import { version } from '../../package.json'; 3 | import { BotLogger } from '@src/utils/logger'; 4 | 5 | // 延迟 6 | export const delayTime = (ms: number) => { 7 | return new Promise((resolve) => { 8 | setTimeout(resolve, ms); 9 | }); 10 | }; 11 | 12 | // 随机延迟 13 | export const randomDelay = (min: number, max: number) => { 14 | let ms = Math.random() * (max - min) + min; 15 | ms = Math.ceil(ms); 16 | BotLogger.info(`delay for ${ms} ms ...`); 17 | return delayTime(ms); 18 | }; 19 | 20 | // 转为字符串 21 | export const resolveString = (data: any) => { 22 | if (typeof data === 'string') return data; 23 | if (Array.isArray(data)) return data.join('\n'); 24 | return String(data); 25 | }; 26 | 27 | // 转为对象 28 | export const toObject = (data: any) => { 29 | if (Buffer.isBuffer(data)) return JSON.parse(data.toString()); 30 | if (typeof data === 'object') return data; 31 | if (typeof data === 'string') return JSON.parse(data); 32 | // return String(data); 33 | }; 34 | 35 | export const has = (o: any, k: any) => Object.prototype.hasOwnProperty.call(o, k); 36 | 37 | // 获取number类型的10位时间戳 38 | export const getTimeStampNumber = () => Number(new Date().getTime().toString().substr(0, 10)); 39 | 40 | // 添加 User-Agent 41 | export const addUserAgent = (header: AxiosRequestHeaders) => { 42 | const sdkVersion = version; 43 | header['User-Agent'] = `BotNodeSDK/v${sdkVersion}`; 44 | }; 45 | // 添加 User-Agent 46 | export const addAuthorization = (header: AxiosRequestHeaders, appID: string, token: string) => { 47 | header['Authorization'] = `Bot ${appID}.${token}`; 48 | }; 49 | // 组装完整Url 50 | export const buildUrl = (path = '', isSandbox?: boolean) => { 51 | return `${isSandbox ? 'https://sandbox.api.sgroup.qq.com' : 'https://api.sgroup.qq.com'}${path}`; 52 | }; 53 | -------------------------------------------------------------------------------- /test/openapi/config.ts: -------------------------------------------------------------------------------- 1 | import { createWebsocket } from '@src/bot'; 2 | import { OpenAPI } from '@src/openapi/v1/openapi'; 3 | import { 4 | apiTestConfig, 5 | API_REQUEST_SUCCESS_CODE, 6 | API_REQUEST_SUCCESS_CODE_WITH_NO_CONTENT, 7 | apiGuildID, 8 | apiUserID, 9 | apiChannelID, 10 | apiSecretChannelID, 11 | apiSecretChannelUserID, 12 | API_ROLE_GOOLDEN_ADMIN_ID, 13 | } from '../../test-config/api-config'; 14 | 15 | // openapi测试使用的config 16 | const testConfig = { 17 | appID: '', 18 | token: '', 19 | timeout: 3000, 20 | intents: ['GUILDS', 'GUILD_MEMBERS', 'DIRECT_MESSAGE', 'AUDIO_ACTION', 'AT_MESSAGES'], 21 | shards: [0, 10], 22 | }; 23 | 24 | export const REQUEST_SUCCESS_CODE = 200; 25 | export const REQUEST_SUCCESS_CODE_WITH_NO_CONTENT = 204; 26 | 27 | export const guildID = ''; 28 | export const userID = ''; 29 | export const channelID = ''; 30 | 31 | export const ROLE_GOOLDEN_ADMIN_ID = ''; 32 | 33 | export const client = OpenAPI.newClient(apiTestConfig); 34 | 35 | export const newWsClient = () => createWebsocket(apiTestConfig); 36 | -------------------------------------------------------------------------------- /test/openapi/v1/audio.spec.ts: -------------------------------------------------------------------------------- 1 | import { client, channelID, REQUEST_SUCCESS_CODE } from '../config'; 2 | 3 | describe('audio测试', () => { 4 | enum AudioPlayStatus { 5 | START = 0, // 开始播放 6 | PAUSE = 1, // 暂停播放 7 | RESUME = 2, // 继续播放 8 | STOP = 3, // 停止播放 9 | } 10 | test('【 postAudio方法 】=== 开始播放操作', async () => { 11 | const audioControl = { 12 | audioUrl: '', 13 | text: '', 14 | status: AudioPlayStatus.START, 15 | } 16 | const res = await client.audioApi.postAudio(channelID, audioControl); 17 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 18 | }); 19 | 20 | test('【 postAudio方法 】=== 暂停播放操作', async () => { 21 | const audioControl = { 22 | audioUrl: '', 23 | text: '', 24 | status: AudioPlayStatus.PAUSE, 25 | } 26 | const res = await client.audioApi.postAudio(channelID, audioControl); 27 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 28 | }); 29 | 30 | test('【 postAudio方法 】=== 继续播放操作', async () => { 31 | const audioControl = { 32 | audioUrl: '', 33 | text: '', 34 | status: AudioPlayStatus.RESUME, 35 | } 36 | const res = await client.audioApi.postAudio(channelID, audioControl); 37 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 38 | }); 39 | 40 | test('【 postAudio方法 】=== 停止播放操作', async () => { 41 | const audioControl = { 42 | audioUrl: '', 43 | text: '', 44 | status: AudioPlayStatus.STOP, 45 | } 46 | const res = await client.audioApi.postAudio(channelID, audioControl); 47 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 48 | }); 49 | 50 | test('【 botOnMic方法 】=== 机器人上麦', async () => { 51 | const res = await client.audioApi.botOnMic(channelID); 52 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 53 | }); 54 | 55 | test('【 botOffMic方法 】=== 机器人下麦', async () => { 56 | const res = await client.audioApi.botOffMic(channelID); 57 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/openapi/v1/channel.spec.ts: -------------------------------------------------------------------------------- 1 | import { getTimeStampNumber } from '@src/utils/utils'; 2 | import { client, guildID, channelID, REQUEST_SUCCESS_CODE } from '../config'; 3 | 4 | describe('channel测试', () => { 5 | test('【 channel方法 】=== 获取子频道信息', async () => { 6 | const res = await client.channelApi.channel(channelID); 7 | expect(res?.data?.id).toStrictEqual(channelID); 8 | }); 9 | 10 | test('【 channels方法 】=== 获取频道下的子频道列表', async () => { 11 | const res = await client.channelApi.channels(guildID); 12 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 13 | }); 14 | 15 | // 后续可补全其他类型 16 | test('【 postChannel方法 】=== 创建、修改、删除子频道(文字)', async () => { 17 | // 1.1 创建子频道(position非零) 18 | const postChannelName = 'post_channel_name'; 19 | const patchChannelName = 'patch_channel_name'; 20 | const postChannelRes = await client.channelApi.postChannel(guildID, { 21 | name: postChannelName, 22 | type: 0, // 文字子频道 23 | position: getTimeStampNumber(), // 排序位置为1 24 | parent_id: '0', // 父亲频道id为0 25 | owner_id: '0', // 拥有者id 26 | sub_type: 0, // 闲聊子频道 27 | }); 28 | const postChannelId = postChannelRes?.data?.id; 29 | expect(postChannelRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 30 | expect(postChannelRes?.data?.name).toStrictEqual(postChannelName); 31 | 32 | // 1.2 创建子频道(position是零) 33 | const postChannelWithZeroPositionRes = await client.channelApi.postChannel(guildID, { 34 | name: postChannelName, 35 | type: 0, // 文字子频道 36 | position: 0, // 排序位置为1 37 | parent_id: '0', // 父亲频道id为0 38 | owner_id: '0', // 拥有者id 39 | sub_type: 0, // 闲聊子频道 40 | }); 41 | const postChannelWithZeroPositionId = postChannelWithZeroPositionRes?.data?.id; 42 | expect(postChannelWithZeroPositionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 43 | expect(postChannelWithZeroPositionRes?.data?.name).toStrictEqual(postChannelName); 44 | 45 | // 2.获取创建的自频道信息 46 | const newChannelInfo = (await client.channelApi.channel(postChannelId))?.data; 47 | 48 | // 3.1修改子频道(position非零) 49 | const patchChannelRes = await client.channelApi.patchChannel(postChannelId, { 50 | ...newChannelInfo, 51 | name: patchChannelName, 52 | }); 53 | expect(patchChannelRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 54 | expect(patchChannelRes?.data?.id).toStrictEqual(postChannelId); 55 | expect(patchChannelRes?.data?.name).toStrictEqual(patchChannelName); 56 | 57 | // 3.2修改子频道(position为0) 58 | const patchChannelWithZeroPositionRes = await client.channelApi.patchChannel(postChannelId, { 59 | ...newChannelInfo, 60 | position: 0, 61 | }); 62 | expect(patchChannelWithZeroPositionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 63 | expect(patchChannelWithZeroPositionRes?.data?.id).toStrictEqual(postChannelId); 64 | expect(patchChannelWithZeroPositionRes?.data?.position).not.toStrictEqual(postChannelRes?.data?.position); 65 | 66 | // 4 删除子频道 67 | const deleteChannelRes = await client.channelApi.deleteChannel(postChannelId); 68 | expect(deleteChannelRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 69 | expect(deleteChannelRes?.data?.id).toStrictEqual(postChannelId); 70 | 71 | const deleteChannelWithZeroPositionRes = await client.channelApi.deleteChannel(postChannelWithZeroPositionId); 72 | expect(deleteChannelWithZeroPositionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 73 | expect(deleteChannelWithZeroPositionRes?.data?.id).toStrictEqual(postChannelWithZeroPositionId); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/openapi/v1/channer-permissions.spec.ts: -------------------------------------------------------------------------------- 1 | import { UpdateChannelPermissions } from '@src/openapi/v1/channel-permissions'; 2 | import { 3 | client, 4 | secretChannelID, 5 | REQUEST_SUCCESS_CODE, 6 | secretChannelUserID, 7 | REQUEST_SUCCESS_CODE_WITH_NO_CONTENT, 8 | } from '../config'; 9 | 10 | const WITH_PERMISSION = '1'; 11 | const WITHOUT_PERMISSION = '0'; 12 | 13 | describe('channelPermissions测试', () => { 14 | test('【 channelPermissions方法 】=== 获取指定子频道的权限', async () => { 15 | const res = await client.channelPermissionsApi.channelPermissions(secretChannelID, secretChannelUserID); 16 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 17 | expect(res?.data?.channel_id).toStrictEqual(secretChannelID); 18 | }); 19 | 20 | // 必须是私密子频道(非所有人可访问) 21 | test('【 putChannelPermissions方法 】=== 修改指定子频道的权限(add,remove不同时为1)', async () => { 22 | // 1. 获取原有权限(测试账号原有权限为1 即可以看到私密频道) 23 | const originPermissionRes = await client.channelPermissionsApi.channelPermissions( 24 | secretChannelID, 25 | secretChannelUserID, 26 | ); 27 | expect(originPermissionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 28 | expect(originPermissionRes?.data?.permissions).toStrictEqual(WITH_PERMISSION); 29 | 30 | // 2. 移除权限 31 | const removePermissionRes = await client.channelPermissionsApi.putChannelPermissions( 32 | secretChannelID, 33 | secretChannelUserID, 34 | { 35 | remove: '1', 36 | add: '0', 37 | }, 38 | ); 39 | expect(removePermissionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 40 | 41 | // 3. 验证修改是否成功 42 | const permissionAfterRemoveRes = await client.channelPermissionsApi.channelPermissions( 43 | secretChannelID, 44 | secretChannelUserID, 45 | ); 46 | expect(permissionAfterRemoveRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 47 | expect(permissionAfterRemoveRes?.data?.permissions).toStrictEqual(WITHOUT_PERMISSION); 48 | 49 | // 4. 添加权限 50 | const addPermissionRes = await client.channelPermissionsApi.putChannelPermissions( 51 | secretChannelID, 52 | secretChannelUserID, 53 | { 54 | remove: '0', 55 | add: '1', 56 | }, 57 | ); 58 | expect(addPermissionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 59 | 60 | // 5. 验证修改是否成功 61 | const permissionAfterAddRes = await client.channelPermissionsApi.channelPermissions( 62 | secretChannelID, 63 | secretChannelUserID, 64 | ); 65 | expect(permissionAfterAddRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 66 | expect(permissionAfterAddRes?.data?.permissions).toStrictEqual(WITH_PERMISSION); 67 | }); 68 | 69 | // 必须是私密子频道(非所有人可访问) 70 | test('【 putChannelPermissions方法 】=== 修改指定子频道的权限(add、remove同时为1)', async () => { 71 | // 1. 获取原有权限(测试账号原有权限为1 即可以看到私密频道) 72 | const originPermissionRes = await client.channelPermissionsApi.channelPermissions( 73 | secretChannelID, 74 | secretChannelUserID, 75 | ); 76 | expect(originPermissionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 77 | expect(originPermissionRes?.data?.permissions).toStrictEqual(WITH_PERMISSION); 78 | 79 | // 2. 移除权限 add、remove同时为1(同时为1表现为移除) 80 | const removePermissionRes = await client.channelPermissionsApi.putChannelPermissions( 81 | secretChannelID, 82 | secretChannelUserID, 83 | { 84 | remove: '1', 85 | add: '1', 86 | }, 87 | ); 88 | expect(removePermissionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 89 | 90 | // 3. 验证修改是否成功 91 | const permissionAfterRemoveRes = await client.channelPermissionsApi.channelPermissions( 92 | secretChannelID, 93 | secretChannelUserID, 94 | ); 95 | expect(permissionAfterRemoveRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 96 | expect(permissionAfterRemoveRes?.data?.permissions).toStrictEqual(WITHOUT_PERMISSION); 97 | 98 | // 4. 添加权限 99 | const addPermissionRes = await client.channelPermissionsApi.putChannelPermissions( 100 | secretChannelID, 101 | secretChannelUserID, 102 | { 103 | remove: '0', 104 | add: '1', 105 | }, 106 | ); 107 | expect(addPermissionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 108 | 109 | // 5. 验证修改是否成功 110 | const permissionAfterAddRes = await client.channelPermissionsApi.channelPermissions( 111 | secretChannelID, 112 | secretChannelUserID, 113 | ); 114 | expect(permissionAfterAddRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 115 | expect(permissionAfterAddRes?.data?.permissions).toStrictEqual(WITH_PERMISSION); 116 | }); 117 | 118 | test('【 putChannelPermissions方法 】=== 参数错误情况', async () => { 119 | try { 120 | await client.channelPermissionsApi.putChannelPermissions( 121 | secretChannelID, 122 | secretChannelUserID, 123 | // @ts-ignore 124 | undefined, 125 | ); 126 | } catch (error) { 127 | expect(error.toString()).toEqual('Error: invalid parameter'); 128 | } 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/openapi/v1/direct-message.spec.ts: -------------------------------------------------------------------------------- 1 | import { MessageToCreate } from '@src/types'; 2 | import { client, userID, guildID, REQUEST_SUCCESS_CODE } from '../config'; 3 | 4 | describe('directMessage测试', () => { 5 | test('【 createDirectMessage、postDirectMessage方法 】=== 创建私信频道、发送私信', async () => { 6 | // 1.创建私信频道 7 | const postSessionRes = await client.directMessageApi.createDirectMessage({ 8 | recipient_id: userID, // 用户ID 9 | source_guild_id: guildID, // 频道ID 10 | }); 11 | expect(postSessionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 12 | 13 | // 2.发送消息 14 | const postMessage = 'test direct message'; 15 | const postMessageRes = await client.directMessageApi.postDirectMessage(postSessionRes.data, { 16 | content: postMessage, 17 | } as MessageToCreate); 18 | expect(postMessageRes?.data?.guild_id).toStrictEqual(postSessionRes?.data?.guild_id); 19 | expect(postMessageRes?.data?.channel_id).toStrictEqual(postSessionRes?.data?.channel_id); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/openapi/v1/guild.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | client, 3 | guildID, 4 | channelID, 5 | userID, 6 | REQUEST_SUCCESS_CODE, 7 | REQUEST_SUCCESS_CODE_WITH_NO_CONTENT } from '../config'; 8 | 9 | describe('guild测试', () => { 10 | test('【 guild方法 】=== 获取频道信息', async () => { 11 | const res = await client.guildApi.guild(guildID); 12 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 13 | expect(res?.data?.id).toStrictEqual(guildID); 14 | }); 15 | 16 | test('【 guildMember方法 】=== 获取某个成员的信息', async () => { 17 | const res = await client.guildApi.guildMember(guildID, userID); 18 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 19 | expect(res?.data?.user?.id).toStrictEqual(userID); 20 | }); 21 | 22 | test('【 guildMembers 】=== 获取频道成员列表(使用默认分页参数)', async () => { 23 | const res = await client.guildApi.guildMembers(guildID); 24 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 25 | expect(Array.isArray(res?.data)).toStrictEqual(true); 26 | }); 27 | 28 | test('【 guildMembers 】=== 获取频道成员列表(传入分页参数)', async () => { 29 | const limit = 2; 30 | const res = await client.guildApi.guildMembers(guildID, { after: '0', limit }); 31 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 32 | expect(res?.data?.length).toBeLessThanOrEqual(limit); 33 | }); 34 | 35 | test('【 deleteGuildMember 】=== 删除指定频道成员', async () => { 36 | const testUserId = '5283226123397135603'; 37 | const res = await client.guildApi.deleteGuildMember(guildID, testUserId); 38 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 39 | }); 40 | 41 | test('【 guildVoiceMembers 】=== 语音子频道内参与语音的频道成员列表', async () => { 42 | const res = await client.guildApi.guildVoiceMembers(channelID); 43 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 44 | expect(Array.isArray(res?.data)).toStrictEqual(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/openapi/v1/me.spec.ts: -------------------------------------------------------------------------------- 1 | import { client, REQUEST_SUCCESS_CODE } from '../config'; 2 | 3 | describe('me测试', () => { 4 | test('【 me方法 】=== 获取当前用户信息', async () => { 5 | const res = await client.meApi.me(); 6 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 7 | expect(res?.data?.id).not.toBeUndefined(); 8 | }); 9 | 10 | test('【 meGuilds方法 】=== 获取当前用户频道列表', async () => { 11 | const res = await client.meApi.meGuilds(); 12 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/openapi/v1/member.spec.ts: -------------------------------------------------------------------------------- 1 | import { userID, REQUEST_SUCCESS_CODE_WITH_NO_CONTENT } from './../config'; 2 | import { client, guildID, channelID, ROLE_GOOLDEN_ADMIN_ID } from '../config'; 3 | 4 | describe('member测试', () => { 5 | test('【 memberAddRole方法 】=== 增加、移除频道身份组成员', async () => { 6 | // 1.给用户金牌管理员权限 7 | const channelInfo = (await client.channelApi.channel(channelID)).data; 8 | const addRoleRes = await client.memberApi.memberAddRole(guildID, ROLE_GOOLDEN_ADMIN_ID, userID, channelInfo?.id); 9 | expect(addRoleRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 10 | 11 | // 2.验证是否给予权限成功 12 | const userInfo = (await client.guildApi.guildMember(guildID, userID)).data; 13 | expect(userInfo?.roles).toContain(ROLE_GOOLDEN_ADMIN_ID); 14 | 15 | // 3.删除金牌管理员权限 16 | const removeRoleRes = await client.memberApi.memberDeleteRole( 17 | guildID, 18 | ROLE_GOOLDEN_ADMIN_ID, 19 | userID, 20 | channelInfo?.id, 21 | ); 22 | expect(removeRoleRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 23 | 24 | // 4.验证是否删除成功 25 | const newUserInfo = (await client.guildApi.guildMember(guildID, userID)).data; 26 | expect(newUserInfo?.roles).not.toContain(ROLE_GOOLDEN_ADMIN_ID); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/openapi/v1/message.spec.ts: -------------------------------------------------------------------------------- 1 | import { MessagesPager, MessageToCreate } from '@src/types'; 2 | import { client, channelID, REQUEST_SUCCESS_CODE, newWsClient } from '../config'; 3 | 4 | describe('message测试', () => { 5 | test('【 messages方法 】=== 获取消息列表(pager默认)', async () => { 6 | const res = await client.messageApi.messages(channelID, {} as MessagesPager); 7 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 8 | }); 9 | 10 | test('【 messages方法 】=== 获取消息列表(pager指定)', async () => { 11 | const messageList = (await client.messageApi.messages(channelID, {} as MessagesPager)).data; 12 | const limit = '5'; 13 | const res = await client.messageApi.messages(channelID, { 14 | type: 'after', 15 | id: messageList[0].id, 16 | limit, 17 | }); 18 | expect(res?.data?.length).toBeLessThan(Number(limit)); 19 | }); 20 | 21 | test('【 messages方法 】=== 获取指定消息', async () => { 22 | const messageList = (await client.messageApi.messages(channelID, {} as MessagesPager)).data; 23 | const messageInfo = (await client.messageApi.message(channelID, messageList[0].id)).data; 24 | expect(messageInfo.message.id).toStrictEqual(messageList[0].id); 25 | }); 26 | 27 | // TODO:需要补全多种类型的消息发送] 28 | test('【 postMessage方法 】=== 发送消息(content)', async () => { 29 | // 1.建立ws连接 30 | const wsClient = newWsClient(); 31 | // 2.发送消息 32 | const textContent = 'test post message'; 33 | const res = await client.messageApi.postMessage(channelID, { 34 | content: textContent, 35 | } as MessageToCreate); 36 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 37 | expect(res?.data?.content).toStrictEqual(textContent); 38 | // 3.关闭连接 39 | wsClient.disconnect(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/openapi/v1/openapi.spec.ts: -------------------------------------------------------------------------------- 1 | import { versionMapping } from '@src/openapi/openapi'; 2 | import { apiVersion, OpenAPI, v1Setup } from '@src/openapi/v1/openapi'; 3 | 4 | describe('openapi测试', () => { 5 | test('【 v1Setup方法 】=== 注册全局api', () => { 6 | expect(Object.keys(versionMapping).length).toBe(0); 7 | v1Setup(); 8 | expect(versionMapping[apiVersion]).toEqual(OpenAPI); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/openapi/v1/reaction.spec.ts: -------------------------------------------------------------------------------- 1 | import { client, channelID, REQUEST_SUCCESS_CODE_WITH_NO_CONTENT } from '../config'; 2 | 3 | describe('reaction测试', () => { 4 | const messageID = '1' 5 | const emojiType = 1; 6 | const emojiID = '1'; 7 | const cookie = ''; 8 | const limit = 20; 9 | 10 | test('【 postReaction方法 】=== 发表表情表态', async () => { 11 | // 发表表情表态 12 | const params = { 13 | message_id: messageID, 14 | emoji_type: emojiType, 15 | emoji_id: emojiID 16 | } 17 | const reactionRes = (await client.reactionApi.postReaction(channelID, params)).data; 18 | expect(reactionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 19 | }); 20 | 21 | test('【 deleteReaction方法 】=== 删除表情表态', async () => { 22 | // 删除表情表态 23 | const params = { 24 | message_id: messageID, 25 | emoji_type: emojiType, 26 | emoji_id: emojiID 27 | } 28 | const reactionRes = (await client.reactionApi.deleteReaction(channelID, params)).data; 29 | expect(reactionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 30 | }); 31 | 32 | test('【 getReactionUserList方法 】=== 拉取表情表态用户列表', async () => { 33 | // 拉取表情表态用户列表 34 | const params = { 35 | message_id: messageID, 36 | emoji_type: emojiType, 37 | emoji_id: emojiID 38 | } 39 | const options = { 40 | cookie, 41 | limit 42 | } 43 | const reactionRes = (await client.reactionApi.getReactionUserList(channelID, params, options)).data; 44 | expect(reactionRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/openapi/v1/resource.spec.ts: -------------------------------------------------------------------------------- 1 | import { getURL } from '@src/openapi/v1/resource'; 2 | 3 | describe('resource测试', () => { 4 | // 可以将resource中的变量导出 在此使用 5 | test('【 getURL方法 】=== 获取HTTP请求URL', async () => { 6 | const endpoint = 'gatewayBotURI'; 7 | const sandboxString = 'sandbox'; 8 | const res = getURL(endpoint, true); 9 | expect(res).toContain(sandboxString); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/openapi/v1/roles.spec.ts: -------------------------------------------------------------------------------- 1 | import { defaultColor } from '@src/openapi/v1/role'; 2 | import { client, REQUEST_SUCCESS_CODE, guildID, REQUEST_SUCCESS_CODE_WITH_NO_CONTENT } from '../config'; 3 | 4 | describe('roles测试', () => { 5 | test('【 roles方法 】=== 获取频道身份组列表', async () => { 6 | const res = await client.roleApi.roles(guildID); 7 | expect(res?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 8 | expect(res?.data?.guild_id).toStrictEqual(guildID); 9 | }); 10 | 11 | test('【 roles方法 】=== 创建、修改、删除频道身份组列表', async () => { 12 | // 1 原始的列表 13 | const originRoleRes = await client.roleApi.roles(guildID); 14 | const originRoleList = originRoleRes.data.roles; 15 | 16 | // ==== 使用传入color ==== 17 | 18 | // 2.1 新增身份组(color自定义) 19 | const postRoleName = 'post_role_name'; 20 | const postRoleRes = await client.roleApi.postRole(guildID, { 21 | ...originRoleList[0], 22 | name: postRoleName, 23 | }); 24 | 25 | // 2.2 验证新增结果 status、roleid 26 | expect(postRoleRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 27 | expect(postRoleRes?.data.role_id).toBeTruthy(); 28 | const postRoleId = postRoleRes.data.role_id; 29 | 30 | // 2.3 修改身份组 31 | const patchRoleName = 'patch_role_nme'; 32 | const patchRes = await client.roleApi.patchRole(guildID, postRoleId, { 33 | ...postRoleRes?.data?.role, 34 | name: patchRoleName, 35 | }); 36 | 37 | // 2.4 验证修改结果 38 | expect(patchRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 39 | expect(patchRes.data.role.name).toStrictEqual(patchRoleName); 40 | 41 | // 2.5 删除新增的身份组 42 | const deleteRoleRes = await client.roleApi.deleteRole(guildID, postRoleId); 43 | expect(deleteRoleRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 44 | 45 | // ==== 使用默认color ==== 46 | 47 | // 3.1 新增身份组(color默认) 48 | const postRoleWithDefaultColorRes = await client.roleApi.postRole(guildID, { 49 | ...originRoleList[0], 50 | name: postRoleName, 51 | color: 0, 52 | }); 53 | 54 | // 3.2 验证新增结果 status、roleid 55 | expect(postRoleWithDefaultColorRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 56 | expect(postRoleWithDefaultColorRes?.data.role_id).toBeTruthy(); 57 | const postRoleWithDefaultColorId = postRoleWithDefaultColorRes.data.role_id; 58 | 59 | // 3.3 修改身份组(color默认) 60 | const patchWithDefaultColorRes = await client.roleApi.patchRole(guildID, postRoleWithDefaultColorId, { 61 | ...postRoleWithDefaultColorRes?.data?.role, 62 | name: patchRoleName, 63 | color: 0, 64 | }); 65 | 66 | // 3.4 验证修改结果 67 | expect(patchWithDefaultColorRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE); 68 | expect(patchWithDefaultColorRes.data.role.name).toStrictEqual(patchRoleName); 69 | expect(patchWithDefaultColorRes.data.role.color).toStrictEqual(defaultColor); 70 | 71 | // 3.5 删除新增的身份组 72 | const deleteRoleWithDefaultColorRes = await client.roleApi.deleteRole(guildID, postRoleWithDefaultColorId); 73 | expect(deleteRoleWithDefaultColorRes?.status).toStrictEqual(REQUEST_SUCCESS_CODE_WITH_NO_CONTENT); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/utils/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { getTimeStampNumber } from '@src/utils/utils'; 2 | 3 | describe('utils测试', () => { 4 | test('【 getTimeStampNumber方法 】=== 获取number类型的10位时间戳', () => { 5 | const res = getTimeStampNumber(); 6 | expect(res.toString().length).toBe(10); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/websocket/websocket.spec.ts: -------------------------------------------------------------------------------- 1 | import { Ws } from '@src/client/websocket/websocket'; 2 | import { apiTestConfig } from '../../test-config/api-config'; 3 | 4 | describe('websocket测试', () => { 5 | const config = { 6 | BotAppID: '', 7 | BotToken: '', 8 | shards: [0, 10], 9 | }; 10 | const event = null; 11 | const ws = new Ws(apiTestConfig, event); 12 | 13 | test('【 checkShards方法 】=== shards不合法', () => { 14 | const shardsArr = null; 15 | const res = ws.checkShards(shardsArr as unknown as Array); 16 | expect(res).toStrictEqual(console.log('shards 不存在')); 17 | }); 18 | 19 | test('【 checkShards方法 】=== shards不存在', () => { 20 | const shardsArr = undefined; 21 | const res = ws.checkShards(shardsArr as unknown as Array); 22 | expect(res).toStrictEqual(console.log('shards 不存在')); 23 | }); 24 | 25 | test('【 checkShards方法 】=== shards错误', () => { 26 | const shardsArr = 'test'; 27 | const res = ws.checkShards(shardsArr as unknown as Array); 28 | expect(res).toStrictEqual(console.log('shards 错误')); 29 | }); 30 | 31 | test('【 checkShards方法 】=== shards类型错误', () => { 32 | const shardsArr = 'test'; 33 | const res = ws.checkShards(shardsArr as unknown as Array); 34 | expect(res).toStrictEqual(console.log('shards 错误')); 35 | }); 36 | 37 | test('【 checkShards方法 】=== shards入参错误,数组两个元素相等', () => { 38 | const shardsArr = [0, 0]; 39 | const res = ws.checkShards(shardsArr); 40 | expect(res).toStrictEqual(console.log('shards 错误')); 41 | }); 42 | 43 | test('【 checkShards方法 】=== shards入参错误,数组第一个元素比第二个元素大', () => { 44 | const shardsArr = [4, 0]; 45 | const res = ws.checkShards(shardsArr); 46 | expect(res).toStrictEqual(console.log('shards 错误')); 47 | }); 48 | 49 | test('【 checkShards方法 】=== 正确', () => { 50 | const shardsArr = [0, 4]; 51 | const res = ws.checkShards(shardsArr); 52 | expect(res).toStrictEqual([0, 4]); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "strict": true, 5 | "lib": ["esnext"], 6 | "typeRoots": ["src/types"], 7 | "types": ["node", "@types/jest"], 8 | "target": "esnext", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noImplicitAny": true, 12 | "allowSyntheticDefaultImports": true, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "importHelpers": true, 16 | "noEmit": true, 17 | "allowJs": true, 18 | "paths": { 19 | "@src/*": ["src/*"] 20 | } 21 | }, 22 | "settings": { 23 | "import/resolver": { 24 | "node": { 25 | "extensions": [".ts", "js"] 26 | } 27 | } 28 | }, 29 | "include": ["src", "test"], 30 | "exclude": ["node_modules", "example"] 31 | } 32 | --------------------------------------------------------------------------------