├── .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 | ## 参与共建 [](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 | 
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 |
--------------------------------------------------------------------------------