Compare commits

..

81 Commits

Author SHA1 Message Date
手瓜一十雪
6ca1ac21e4 feat: support 29927 2024-11-21 14:55:34 +08:00
Mlikiowa
381ee1c30e release: v4.1.13 2024-11-21 06:43:58 +00:00
手瓜一十雪
902fe907bd refactor: 持续重构project结构与定义 2024-11-21 14:36:06 +08:00
手瓜一十雪
bbb4ad7d95 rename: project 2024-11-21 14:30:21 +08:00
手瓜一十雪
24bc9f35b2 fix: NTGroupRequestOperateTypes 2024-11-21 14:28:04 +08:00
手瓜一十雪
52c68a3bfb fix: NTGroupRequestOperateTypes 2024-11-21 14:27:11 +08:00
手瓜一十雪
d982bcdad5 fix: typo 2024-11-21 14:21:46 +08:00
手瓜一十雪
b8165242f0 feat: NTSex 2024-11-21 14:21:14 +08:00
手瓜一十雪
7ce95bca04 feat: NTGroupMemberRole 2024-11-21 14:15:22 +08:00
手瓜一十雪
cd212abd5f fix: TipGroupElementType 2024-11-21 14:10:52 +08:00
手瓜一十雪
e5b063accb feat: MemberAddShowType 2024-11-21 13:44:21 +08:00
手瓜一十雪
eeef5409dc fix: PicSubType 2024-11-21 13:10:49 +08:00
手瓜一十雪
2bf8d8f791 fix 标准类型 2024-11-21 13:06:13 +08:00
手瓜一十雪
56e62392a6 chore: webui backend readme 2024-11-21 12:24:53 +08:00
手瓜一十雪
2ecf04c78c chore: LICENSE 2024-11-21 12:23:51 +08:00
手瓜一十雪
a19358da5b refactor: 项目结构 2024-11-21 11:52:50 +08:00
手瓜一十雪
a5d4998933 refactor: 通过@实现定位 2024-11-21 11:39:44 +08:00
手瓜一十雪
8edbe54456 fix: index 2024-11-21 11:25:06 +08:00
手瓜一十雪
e898915d01 rename: 使用@搜寻 2024-11-21 11:16:25 +08:00
手瓜一十雪
b2075130d9 style: lint 2024-11-21 11:09:23 +08:00
手瓜一十雪
02e39b5714 fix: typo 2024-11-21 11:06:03 +08:00
手瓜一十雪
de64b03054 refactor: 旧代码移除 2024-11-21 11:01:35 +08:00
手瓜一十雪
fa70eec3d8 refactor: core类型refactor结束 2024-11-21 10:47:12 +08:00
手瓜一十雪
583ec10c7c fix: 规范化类型 2024-11-21 10:43:05 +08:00
手瓜一十雪
38a098c77d refactor: type 2024-11-21 10:36:08 +08:00
pk5ls20
d17674d06e fix: #553
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 3m49s
Build Action / Build-Shell (push) Failing after 3m21s
2024-11-20 20:58:36 +08:00
pk5ls20
0b839258aa fix: ci 2024-11-20 19:51:29 +08:00
pk5ls20
50e207cf6f chore: remove useless log 2024-11-20 17:30:50 +08:00
pk5ls20
5d2d8c7123 fix: #551
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 3m37s
Build Action / Build-Shell (push) Failing after 2m58s
2024-11-20 17:00:01 +08:00
pk5ls20
23702f412c chore: link 2024-11-20 16:23:04 +08:00
pk5ls20
31e94792c4 Merge pull request #548 from huankong233/main 2024-11-20 14:45:28 +08:00
huankong233
249afdce81 revent: 对手动拆分chunk进行回滚 2024-11-20 14:15:03 +08:00
huankong233
ee8f381341 feat: 增强webui对老设备的支持 2024-11-20 14:02:34 +08:00
Mlikiowa
83f3df76cd release: v4.1.12 2024-11-20 04:22:12 +00:00
手瓜一十雪
16195ca52b fix: old version handle 2024-11-20 12:21:45 +08:00
Mlikiowa
d5f492775e release: v4.1.11 2024-11-20 02:20:35 +00:00
手瓜一十雪
1f273a8799 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-20 10:20:00 +08:00
手瓜一十雪
f44f6fd1e9 fix: 文件后清除 2024-11-20 10:19:43 +08:00
Mlikiowa
21ca13789e release: v4.1.9 2024-11-20 02:18:14 +00:00
手瓜一十雪
648faedca6 fix: #544 2024-11-20 10:16:41 +08:00
Mlikiowa
3a6748ae37 release: v4.1.8
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 13s
Build Action / Build-Shell (push) Failing after 3m22s
2024-11-19 10:45:26 +00:00
手瓜一十雪
4d4b1ad26c fix: #543 2024-11-19 18:44:56 +08:00
手瓜一十雪
e42fbea918 feat: 扩展rkey支持 2024-11-19 17:58:43 +08:00
Mlikiowa
48b648b0fb release: v4.17
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 2m22s
Build Action / Build-Shell (push) Failing after 2m23s
2024-11-19 08:30:06 +00:00
手瓜一十雪
68e86b07c7 fix: #543 2024-11-19 16:29:33 +08:00
手瓜一十雪
12cb500818 refactor: rename OB11BaseEvent 2024-11-19 12:55:42 +08:00
手瓜一十雪
9ffaab178a refactor: Action 2024-11-19 12:49:51 +08:00
pk5ls20
d4fbbd6711 fix: #539
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 2m33s
Build Action / Build-Shell (push) Failing after 2m17s
2024-11-19 01:12:49 +08:00
pk5ls20
ded53cd348 fix: link
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 3m48s
Build Action / Build-Shell (push) Failing after 2m49s
2024-11-18 23:22:25 +08:00
手瓜一十雪
be9e80c87b fix: 清除无效链接 2024-11-18 20:40:43 +08:00
Mlikiowa
e9fe6f28cc release: v4.1.6 2024-11-18 11:51:39 +00:00
手瓜一十雪
0b8bf739e9 fix: event 2024-11-18 19:51:08 +08:00
手瓜一十雪
0222664db8 fix: type
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 14m48s
Build Action / Build-Shell (push) Failing after 14m43s
2024-11-17 16:02:25 +08:00
手瓜一十雪
a88792e452 docs: 没有参考故移除 2024-11-17 16:01:44 +08:00
手瓜一十雪
ad45400742 docs: 调整更新速度与Packet重构 2024-11-17 15:57:21 +08:00
手瓜一十雪
53e5ba03be fix 2024-11-17 15:56:48 +08:00
手瓜一十雪
b587d6b91d fix: (SetGroupSign) BaseAction-->GetPacketStatusDepends 2024-11-17 15:37:10 +08:00
手瓜一十雪
5e750d4ee9 feat: uploadQunAlbum未测试
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 2m40s
Build Action / Build-Shell (push) Failing after 2m38s
2024-11-17 13:39:57 +08:00
Mlikiowa
50fb32f81c release: v4.1.5 2024-11-17 03:39:17 +00:00
手瓜一十雪
6c46cdd947 fix: error 2024-11-17 11:33:01 +08:00
手瓜一十雪
372452fbee fix: 消息上报 2024-11-17 11:29:27 +08:00
手瓜一十雪
417ef5d335 Revert "fix"
This reverts commit 9c534f8afd.
2024-11-17 11:21:48 +08:00
手瓜一十雪
9c534f8afd fix 2024-11-17 11:12:14 +08:00
pk5ls20
ecd426bb80 refactor: webui network 2024-11-17 08:17:09 +08:00
pk5ls20
f74ef273de fix: workflow
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 3m25s
Build Action / Build-Shell (push) Failing after 2m52s
2024-11-17 06:24:58 +08:00
pk5ls20
f913e0b027 chore: workflow 2024-11-17 06:23:33 +08:00
pk5ls20
f7268c30ca chore: revert todo 2024-11-17 05:28:46 +08:00
pk5ls20
0f5ef03d63 chore: try todo x2 2024-11-17 05:21:35 +08:00
pk5ls20
745276d0f0 chore: try todo 2024-11-17 05:16:18 +08:00
pk5ls20
2e108a4bd6 feat: error stack 2024-11-17 04:43:29 +08:00
pk5ls20
666da80ef5 feat: version display 2024-11-17 03:43:09 +08:00
pk5ls20
cc73104d62 chore: eslint 2024-11-17 03:35:20 +08:00
手瓜一十雪
3c10b82bab Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 11s
Build Action / Build-Shell (push) Failing after 14s
2024-11-16 20:35:31 +08:00
手瓜一十雪
9a65dae6a2 fix: #531 2024-11-16 20:32:52 +08:00
Mlikiowa
f26cd8cdc9 release: v4.1.3 2024-11-16 12:22:06 +00:00
手瓜一十雪
eeec905df0 fix: 反向ws 2024-11-16 20:21:38 +08:00
手瓜一十雪
0c6aac7f66 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-16 20:20:07 +08:00
手瓜一十雪
86d22db141 feat: remove hasBeenClosed 2024-11-16 20:15:02 +08:00
Mlikiowa
48a5d0eef3 release: v4.1.2 2024-11-16 12:14:28 +00:00
手瓜一十雪
bda174bed4 fix: 异常 2024-11-16 20:13:36 +08:00
Mlikiowa
caf98b8655 release: v4.1.1 2024-11-16 11:26:41 +00:00
207 changed files with 2681 additions and 2873 deletions

View File

@@ -1,8 +1,7 @@
name: "Build Action" name: "Build Action"
on: on:
push: push:
branches: pull_request:
- main
workflow_dispatch: workflow_dispatch:
permissions: write-all permissions: write-all
@@ -11,60 +10,38 @@ jobs:
Build-LiteLoader: Build-LiteLoader:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone Main Repository - name: Clone Main Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: - name: Use Node.js 20.X
repository: 'NapNeko/NapCatQQ' uses: actions/setup-node@v4
submodules: true with:
ref: main node-version: 20.x
token: ${{ secrets.NAPCAT_BUILD }} - name: Build NapCat.Framework
- name: Use Node.js 20.X run: |
uses: actions/setup-node@v4 npm i && cd napcat.webui && npm i && cd .. || exit 1
with: npm run build:framework && npm run depend || exit 1
node-version: 20.x
- name: Build NuCat Framework
run: |
npm i
cd napcat.webui
npm i
cd ..
npm run build:framework
cd dist
npm i --omit=dev
rm package-lock.json rm package-lock.json
cd .. - name: Upload Artifact
- name: Upload Artifact uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v4 with:
with: name: NapCat.Framework
name: NapCat.Framework path: dist
path: dist
Build-Shell: Build-Shell:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone Main Repository - name: Clone Main Repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: - name: Use Node.js 20.X
repository: 'NapNeko/NapCatQQ' uses: actions/setup-node@v4
submodules: true with:
ref: main node-version: 20.x
token: ${{ secrets.NAPCAT_BUILD }} - name: Build NapCat.Shell
- name: Use Node.js 20.X run: |
uses: actions/setup-node@v4 npm i && cd napcat.webui && npm i && cd .. || exit 1
with: npm run build:shell && npm run depend || exit 1
node-version: 20.x rm package-lock.json
- name: Build NuCat LiteLoader - name: Upload Artifact
run: | uses: actions/upload-artifact@v4
npm i with:
cd napcat.webui name: NapCat.Shell
npm i path: dist
cd ..
npm run build:shell
cd dist
npm i --omit=dev
rm package-lock.json
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.Shell
path: dist

480
LICENSE
View File

@@ -1,343 +1,201 @@
GNU GENERAL PUBLIC Without Social media promotion LICENSE Apache License
Version 2, June 1991 Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (C) 1989, 1991 Free Software Foundation, Inc., TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble 1. Definitions.
The licenses for most software are designed to take away your "License" shall mean the terms and conditions for use, reproduction,
freedom to share and change it. By contrast, the GNU General Public and distribution as defined by Sections 1 through 9 of this document.
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not "Licensor" shall mean the copyright owner or entity authorized by
price. Our General Public Licenses are designed to make sure that you the copyright owner that is granting the License.
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid "Legal Entity" shall mean the union of the acting entity and all
anyone to deny you these rights or to ask you to surrender the rights. other entities that control, are controlled by, or are under common
These restrictions translate to certain responsibilities for you if you control with that entity. For the purposes of this definition,
distribute copies of the software, or if you modify it. "control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
For example, if you distribute copies of such a program, whether "You" (or "Your") shall mean an individual or Legal Entity
gratis or for a fee, you must give the recipients all the rights that exercising permissions granted by this License.
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and "Source" form shall mean the preferred form for making modifications,
(2) offer you this license which gives you legal permission to copy, including but not limited to software source code, documentation
distribute and/or modify the software. source, and configuration files.
Also, for each author's protection and ours, we want to make certain "Object" form shall mean any form resulting from mechanical
that everyone understands that there is no warranty for this free transformation or translation of a Source form, including but
software. If the software is modified by someone else and passed on, we not limited to compiled object code, generated documentation,
want its recipients to know that what they have is not the original, so and conversions to other media types.
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software "Work" shall mean the work of authorship, whether in Source or
patents. We wish to avoid the danger that redistributors of a free Object form, made available under the License, as indicated by a
program will individually obtain patent licenses, in effect making the copyright notice that is included in or attached to the work
program proprietary. To prevent this, we have made it clear that any (an example is provided in the Appendix below).
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and "Derivative Works" shall mean any work, whether in Source or Object
modification follow. form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
GNU GENERAL PUBLIC LICENSE "Contribution" shall mean any work of authorship, including
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
0. This License applies to any program or other work which contains "Contributor" shall mean Licensor and any individual or Legal Entity
a notice placed by the copyright holder saying it may be distributed on behalf of whom a Contribution has been received by Licensor and
under the terms of this General Public License. The "Program", below, subsequently incorporated within the Work.
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not 2. Grant of Copyright License. Subject to the terms and conditions of
covered by this License; they are outside its scope. The act of this License, each Contributor hereby grants to You a perpetual,
running the Program is not restricted, and the output from the Program worldwide, non-exclusive, no-charge, royalty-free, irrevocable
is covered only if its contents constitute a work based on the copyright license to reproduce, prepare Derivative Works of,
Program (independent of having been made by running the Program). publicly display, publicly perform, sublicense, and distribute the
Whether that is true depends on what the Program does. Work and such Derivative Works in Source or Object form.
1. You may copy and distribute verbatim copies of the Program's 3. Grant of Patent License. Subject to the terms and conditions of
source code as you receive it, in any medium, provided that you this License, each Contributor hereby grants to You a perpetual,
conspicuously and appropriately publish on each copy an appropriate worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright notice and disclaimer of warranty; keep intact all the (except as stated in this section) patent license to make, have made,
notices that refer to this License and to the absence of any warranty; use, offer to sell, sell, import, and otherwise transfer the Work,
and give any other recipients of the Program a copy of this License where such license applies only to those patent claims licensable
along with the Program. by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
You may charge a fee for the physical act of transferring a copy, and 4. Redistribution. You may reproduce and distribute copies of the
you may at your option offer warranty protection in exchange for a fee. Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
2. You may modify your copy or copies of the Program or any portion (a) You must give any other recipients of the Work or
of it, thus forming a work based on the Program, and copy and Derivative Works a copy of this License; and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices (b) You must cause any modified files to carry prominent notices
stating that you changed the files and the date of any change. stating that You changed the files; and
b) You must cause any work that you distribute or publish, that in (c) You must retain, in the Source form of any Derivative Works
whole or in part contains or is derived from the Program or any that You distribute, all copyright, patent, trademark, and
part thereof, to be licensed as a whole at no charge to all third attribution notices from the Source form of the Work,
parties under the terms of this License. excluding those notices that do not pertain to any part of
the Derivative Works; and
c) If the modified program normally reads commands interactively (d) If the Work includes a "NOTICE" text file as part of its
when run, you must cause it, when started running for such distribution, then any Derivative Works that You distribute must
interactive use in the most ordinary way, to print or display an include a readable copy of the attribution notices contained
announcement including an appropriate copyright notice and a within such NOTICE file, excluding those notices that do not
notice that there is no warranty (or else, saying that you provide pertain to any part of the Derivative Works, in at least one
a warranty) and that users may redistribute the program under of the following places: within a NOTICE text file distributed
these conditions, and telling the user how to view a copy of this as part of the Derivative Works; within the Source form or
License. (Exception: if the Program itself is interactive but documentation, if provided along with the Derivative Works; or,
does not normally print such an announcement, your work based on within a display generated by the Derivative Works, if and
the Program is not required to print an announcement.) wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
dYou may use this software in accordance with the above terms, You may add Your own copyright statement to Your modifications and
but you are not allowed to promote this project or your projects may provide additional or different license terms and conditions
based on this project on any public social media. for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
These requirements apply to the modified work as a whole. If 5. Submission of Contributions. Unless You explicitly state otherwise,
identifiable sections of that work are not derived from the Program, any Contribution intentionally submitted for inclusion in the Work
and can be reasonably considered independent and separate works in by You to the Licensor shall be under the terms and conditions of
themselves, then this License, and its terms, do not apply to those this License, without any additional terms or conditions.
sections when you distribute them as separate works. But when you Notwithstanding the above, nothing herein shall supersede or modify
distribute the same sections as part of a whole which is a work based the terms of any separate license agreement you may have executed
on the Program, the distribution of the whole must be on the terms of with Licensor regarding such Contributions.
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest 6. Trademarks. This License does not grant permission to use the trade
your rights to work written entirely by you; rather, the intent is to names, trademarks, service marks, or product names of the Licensor,
exercise the right to control the distribution of derivative or except as required for reasonable and customary use in describing the
collective works based on the Program. origin of the Work and reproducing the content of the NOTICE file.
In addition, mere aggregation of another work not based on the Program 7. Disclaimer of Warranty. Unless required by applicable law or
with the Program (or with a work based on the Program) on a volume of agreed to in writing, Licensor provides the Work (and each
a storage or distribution medium does not bring the other work under Contributor provides its Contributions) on an "AS IS" BASIS,
the scope of this License. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
3. You may copy and distribute the Program (or a work based on it, 8. Limitation of Liability. In no event and under no legal theory,
under Section 2) in object code or executable form under the terms of whether in tort (including negligence), contract, or otherwise,
Sections 1 and 2 above provided that you also do one of the following: unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
a) Accompany it with the complete corresponding machine-readable 9. Accepting Warranty or Additional Liability. While redistributing
source code, which must be distributed under the terms of Sections the Work or Derivative Works thereof, You may choose to offer,
1 and 2 above on a medium customarily used for software interchange; or, and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
b) Accompany it with a written offer, valid for at least three END OF TERMS AND CONDITIONS
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer APPENDIX: How to apply the Apache License to your work.
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for To apply the Apache License to your work, attach the following
making modifications to it. For an executable work, complete source boilerplate notice, with the fields enclosed by brackets "[]"
code means all the source code for all modules it contains, plus any replaced with your own identifying information. (Don't include
associated interface definition files, plus the scripts used to the brackets!) The text should be enclosed in the appropriate
control compilation and installation of the executable. However, as a comment syntax for the file format. We also recommend that a
special exception, the source code distributed need not include file or class name and description of purpose be included on the
anything that is normally distributed (in either source or binary same "printed page" as the copyright notice for easier
form) with the major components (compiler, kernel, and so on) of the identification within third-party archives.
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering Copyright [yyyy] [name of copyright owner]
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program Licensed under the Apache License, Version 2.0 (the "License");
except as expressly provided under this License. Any attempt you may not use this file except in compliance with the License.
otherwise to copy, modify, sublicense or distribute the Program is You may obtain a copy of the License at
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not http://www.apache.org/licenses/LICENSE-2.0
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Unless required by applicable law or agreed to in writing, software
Program), the recipient automatically receives a license from the distributed under the License is distributed on an "AS IS" BASIS,
original licensor to copy, distribute or modify the Program subject to WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
these terms and conditions. You may not impose any further See the License for the specific language governing permissions and
restrictions on the recipients' exercise of the rights granted herein. limitations under the License.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -8,7 +8,7 @@
## 欢迎回家 ## 欢迎回家
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现 NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 碎碎叨叨 ## 特性介绍
- [x] **安装简单**:就算是笨蛋也能使用 - [x] **安装简单**:就算是笨蛋也能使用
- [x] **性能友好**:就算是低内存也能使用 - [x] **性能友好**:就算是低内存也能使用
- [x] **接口丰富**:就算是没有也能使用 - [x] **接口丰富**:就算是没有也能使用
@@ -26,19 +26,17 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
[Cloudflare.HKServer](https://napcat.napneko.icu/) [Cloudflare.HKServer](https://napcat.napneko.icu/)
[Cloudflare.Pages](https://napneko.pages.dev/) [Github.IO](https://napneko.github.io/)
[Server.China](https://napneko.com/) [Cloudflare.Pages](https://napneko.pages.dev/)
[Server.Other](https://napcat.cyou/) [Server.Other](https://napcat.cyou/)
[Github.IO](https://napneko.github.io/)
## 回家旅途 ## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS) [QQ Group](https://qm.qq.com/q/NWP25OeV0c)
## 感谢他们 ## 感谢他们
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权 感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi 感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi
@@ -46,6 +44,14 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
不过最最重要的 还是需要感谢屏幕前的你哦~ 不过最最重要的 还是需要感谢屏幕前的你哦~
--- ---
## 延缓Native模块与NapCat对新版QQ适配
为未来持续与高效的使用Native模块 模块代码转为完全非Git仓库的本地保存源码 并进行相关重构
同时为了保证稳定 NapCat 本体通常会在3 Week+的周期进行新版本适配
因此此时推荐使用release指定版本
## 开源附加 ## 开源附加
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。** 任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ", "name": "NapCatQQ",
"slug": "NapCat.Framework", "slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现", "description": "高性能的 OneBot 11 协议实现",
"version": "4.1.0", "version": "4.1.13",
"icon": "./logo.png", "icon": "./logo.png",
"authors": [ "authors": [
{ {

View File

@@ -1,17 +1,18 @@
{ {
"name": "napcat.webui", "name": "napcat.webui",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"webui:lint": "eslint . --fix", "webui:lint": "eslint --fix src/**/*.{js,ts,vue}",
"webui:dev": "vite", "webui:dev": "vite",
"webui:build": "vue-tsc -b && vite build", "webui:build": "vite build",
"webui:preview": "vite preview" "webui:preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"tdesign-icons-vue-next": "^0.3.3",
"tdesign-vue-next": "^1.10.3", "tdesign-vue-next": "^1.10.3",
"vue": "^3.5.12", "vue": "^3.5.12",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
@@ -20,10 +21,12 @@
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.14.0", "@eslint/js": "^9.14.0",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@vitejs/plugin-legacy": "^5.4.3",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "^5.1.4",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.31.0", "eslint-plugin-vue": "^9.31.0",
"globals": "^15.12.0", "globals": "^15.12.0",
"terser": "^5.36.0",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^5.4.10", "vite": "^5.4.10",
"vue-tsc": "^2.1.8" "vue-tsc": "^2.1.8"

View File

@@ -13,9 +13,11 @@
<t-list-item class="list-item"> <t-list-item class="list-item">
<span class="item-label">版本信息:</span> <span class="item-label">版本信息:</span>
<span class="item-content"> <span class="item-content">
<t-tag class="tag-item" theme="success"> WebUi: 1.0.0 </t-tag> <t-tag class="tag-item" theme="success"> WebUi: {{ pkg.version }} </t-tag>
<t-tag class="tag-item" theme="success"> NapCat: 4.?.? </t-tag> <t-tag class="tag-item" theme="success"> NapCat: {{ napCatVersion }} </t-tag>
<t-tag class="tag-item" theme="success"> Tdesign: 1.10.3 </t-tag> <t-tag class="tag-item" theme="success">
TDesign: {{ pkg.dependencies['tdesign-vue-next'] }}
</t-tag>
</span> </span>
</t-list-item> </t-list-item>
</t-list> </t-list>
@@ -24,7 +26,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import pkg from '../../package.json';
import { napCatVersion } from '../../../src/common/version';
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,52 +1,59 @@
<template> <template>
<t-space class="full-space"> <t-space class="full-space">
<template v-if="clientPanelData.length > 0"> <template v-if="clientPanelData.length > 0">
<t-tabs v-model="activeTab" :addable="true" theme="card" @add="showAddTabDialog" @remove="removeTab" class="full-tabs"> <t-tabs
<t-tab-panel v-model="activeTab"
v-for="(config, idx) in clientPanelData" :addable="true"
:key="idx" theme="card"
:label="config.name" @add="showAddTabDialog"
:removable="true" @remove="removeTab"
:value="idx" class="full-tabs"
class="full-tab-panel" >
> <t-tab-panel
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" /> v-for="(config, idx) in clientPanelData"
<div class="button-container"> :key="idx"
<t-button @click="saveConfig" style="width: 100px; height: 40px;">保存</t-button> :label="config.name"
</div> :removable="true"
</t-tab-panel> :value="idx"
</t-tabs> class="full-tab-panel"
</template> >
<template v-else> <component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" /> <div class="button-container">
</template> <t-button @click="saveConfig" style="width: 100px; height: 40px">保存</t-button>
<t-dialog </div>
v-model:visible="isDialogVisible" </t-tab-panel>
header="添加网络配置" </t-tabs>
@close="isDialogVisible = false" </template>
@confirm="addTab" <template v-else>
> <EmptyStateComponent :showAddTabDialog="showAddTabDialog" />
<t-form ref="form" :model="newTab"> </template>
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name"> <t-dialog
<t-input v-model="newTab.name" /> v-model:visible="isDialogVisible"
</t-form-item> header="添加网络配置"
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type"> @close="isDialogVisible = false"
<t-select v-model="newTab.type"> @confirm="addTab"
<t-option value="httpServers">HTTP 服务器</t-option> >
<t-option value="httpClients">HTTP 客户端</t-option> <t-form ref="form" :model="newTab">
<t-option value="websocketServers">WebSocket 服务器</t-option> <t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
<t-option value="websocketClients">WebSocket 客户端</t-option> <t-input v-model="newTab.name" />
</t-select> </t-form-item>
</t-form-item> <t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
</t-form> <t-select v-model="newTab.type">
</t-dialog> <t-option value="httpServers">HTTP 服务器</t-option>
<t-option value="httpClients">HTTP 客户端</t-option>
<t-option value="websocketServers">WebSocket 服务器</t-option>
<t-option value="websocketClients">WebSocket 客户端</t-option>
</t-select>
</t-form-item>
</t-form>
</t-dialog>
</t-space> </t-space>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted, reactive, Reactive } from 'vue'; import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
import { import {
httpServerDefaultConfigs, httpServerDefaultConfigs,
httpClientDefaultConfigs, httpClientDefaultConfigs,
websocketServerDefaultConfigs, websocketServerDefaultConfigs,
@@ -58,187 +65,185 @@
NetworkConfig, NetworkConfig,
OneBotConfig, OneBotConfig,
mergeOneBotConfigs, mergeOneBotConfigs,
} from '../../../src/onebot/config/config'; } from '../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell'; import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue'; import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue'; import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue'; import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue'; import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue'; import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients'; type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig; type ComponentUnion =
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
const componentMap: Record<
ConfigKey,
| typeof HttpServerComponent | typeof HttpServerComponent
| typeof HttpClientComponent | typeof HttpClientComponent
| typeof WebsocketServerComponent | typeof WebsocketServerComponent
| typeof WebsocketClientComponent | typeof WebsocketClientComponent;
> = {
const componentMap: Record<ConfigKey, ComponentUnion> = {
httpServers: HttpServerComponent, httpServers: HttpServerComponent,
httpClients: HttpClientComponent, httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent, websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent, websocketClients: WebsocketClientComponent,
}; };
interface ClientPanel { const defaultConfigMap: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
interface ConfigMap {
httpServers: HttpServerConfig;
httpClients: HttpClientConfig;
websocketServers: WebsocketServerConfig;
websocketClients: WebsocketClientConfig;
}
interface ClientPanel<K extends ConfigKey = ConfigKey> {
name: string; name: string;
key: ConfigKey; key: K;
data: Ref<ConfigUnion>; data: ConfigMap[K];
} }
type ComponentKey = keyof typeof componentMap; const activeTab = ref<number>(0);
const isDialogVisible = ref(false);
// TODO: store these state in global store (aka pinia) const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' });
const activeTab = ref<number>(0); const clientPanelData: Ref<ClientPanel[]> = ref([]);
const isDialogVisible = ref(false);
const newTab = ref<{ name: string; type: ComponentKey }>({ name: '', type: 'httpServers' }); const getComponent = (type: ConfigKey) => {
const clientPanelData: Reactive<Array<ClientPanel>> = reactive([]);
const getComponent = (type: ComponentKey) => {
return componentMap[type]; return componentMap[type];
}; };
const getOB11Config = async (): Promise<OneBotConfig | undefined> => { const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth'); const storedCredential = localStorage.getItem('auth');
if (!storedCredential) { if (!storedCredential) {
console.error('No stored credential found'); console.error('No stored credential found');
return; return;
} }
const loginManager = new QQLoginManager(storedCredential); const loginManager = new QQLoginManager(storedCredential);
return await loginManager.GetOB11Config(); return await loginManager.GetOB11Config();
}; };
const setOB11Config = async (config: OneBotConfig): Promise<boolean> => { const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
const storedCredential = localStorage.getItem('auth'); const storedCredential = localStorage.getItem('auth');
if (!storedCredential) { if (!storedCredential) {
console.error('No stored credential found'); console.error('No stored credential found');
return false; return false;
} }
const loginManager = new QQLoginManager(storedCredential); const loginManager = new QQLoginManager(storedCredential);
return await loginManager.SetOB11Config(config); return await loginManager.SetOB11Config(config);
}; };
const addToPanel = <T extends ConfigUnion>(configs: T[], key: ConfigKey) => { const addToPanel = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => {
configs.forEach((config) => clientPanelData.push({ name: config.name, data: config, key: key })); configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key }));
}; };
const addConfigDataToPanel = (data: NetworkConfig) => { const addConfigDataToPanel = (data: NetworkConfig) => {
Object.entries(data).forEach(([key, configs]) => { (Object.keys(data) as ConfigKey[]).forEach((key) => {
if (key in defaultConfigs) { addToPanel(data[key], key);
addToPanel(configs as ConfigUnion[], key as ConfigKey);
}
}); });
}; };
const parsePanelData = (): NetworkConfig => { const parsePanelData = (): NetworkConfig => {
return { const result: NetworkConfig = {
websocketClients: clientPanelData httpServers: [],
.filter((panel) => panel.key === 'websocketClients') httpClients: [],
.map((panel) => panel.data as WebsocketClientConfig), websocketServers: [],
websocketServers: clientPanelData websocketClients: [],
.filter((panel) => panel.key === 'websocketServers')
.map((panel) => panel.data as WebsocketServerConfig),
httpClients: clientPanelData
.filter((panel) => panel.key === 'httpClients')
.map((panel) => panel.data as HttpClientConfig),
httpServers: clientPanelData
.filter((panel) => panel.key === 'httpServers')
.map((panel) => panel.data as HttpServerConfig),
}; };
}; clientPanelData.value.forEach((panel) => {
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
const loadConfig = async () => { });
return result;
};
const loadConfig = async () => {
try { try {
const userConfig = await getOB11Config(); const userConfig = await getOB11Config();
if (!userConfig) return; if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig); const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network); addConfigDataToPanel(mergedConfig.network);
} catch (error) { } catch (error) {
console.error('Error loading config:', error); console.error('Error loading config:', error);
} }
}; };
// It's better to "saveConfig" instead of using deep watch const saveConfig = async () => {
const saveConfig = async () => {
const config = parsePanelData(); const config = parsePanelData();
const userConfig = await getOB11Config(); const userConfig = await getOB11Config();
if (!userConfig) return; if (!userConfig) {
await MessagePlugin.error('无法获取配置!');
return;
}
userConfig.network = config; userConfig.network = config;
const success = await setOB11Config(userConfig); const success = await setOB11Config(userConfig);
if (success) { if (success) {
MessagePlugin.success('配置保存成功'); await MessagePlugin.success('配置保存成功');
} else { } else {
MessagePlugin.error('配置保存失败'); await MessagePlugin.error('配置保存失败');
} }
}; };
const showAddTabDialog = () => { const showAddTabDialog = () => {
newTab.value = { name: '', type: 'httpServers' }; newTab.value = { name: '', type: 'httpServers' };
isDialogVisible.value = true; isDialogVisible.value = true;
}; };
const addTab = async () => { const addTab = async () => {
const { name, type } = newTab.value; const { name, type } = newTab.value;
if (clientPanelData.some(panel => panel.name === name)) { if (clientPanelData.value.some((panel) => panel.name === name)) {
MessagePlugin.error('选项卡名称已存在'); await MessagePlugin.error('选项卡名称已存在');
return; return;
} }
const defaultConfig = structuredClone(defaultConfigs[type]); const defaultConfig = structuredClone(defaultConfigMap[type]);
defaultConfig.name = name; defaultConfig.name = name;
clientPanelData.push({ name, data: defaultConfig, key: type }); clientPanelData.value.push({ name, data: defaultConfig, key: type });
isDialogVisible.value = false; isDialogVisible.value = false;
await nextTick(); await nextTick();
activeTab.value = clientPanelData.length - 1; activeTab.value = clientPanelData.value.length - 1;
MessagePlugin.success('选项卡添加成功'); await MessagePlugin.success('选项卡添加成功');
}; };
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => { const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
clientPanelData.splice(payload.index, 1); clientPanelData.value.splice(payload.index, 1);
activeTab.value = Math.max(0, activeTab.value - 1); activeTab.value = Math.max(0, activeTab.value - 1);
await saveConfig(); await saveConfig();
}; };
onMounted(() => { onMounted(() => {
loadConfig(); loadConfig();
}); });
</script> </script>
<style scoped> <style scoped>
.full-space { .full-space {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
justify-content: flex-start; justify-content: flex-start;
} }
.full-tabs { .full-tabs {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.full-tab-panel { .full-tab-panel {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.button-container { .button-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 20px; margin-top: 20px;
} }
</style> </style>

View File

@@ -131,4 +131,4 @@ onMounted(() => {
margin-left: 20px; margin-left: 20px;
} }
} }
</style> </style>

View File

@@ -19,4 +19,4 @@ defineProps<{ showAddTabDialog: () => void }>();
height: 100%; height: 100%;
text-align: center; text-align: center;
} }
</style> </style>

View File

@@ -36,14 +36,17 @@ const props = defineProps<{
const messageFormatOptions = ref([ const messageFormatOptions = ref([
{ label: 'Array', value: 'array' }, { label: 'Array', value: 'array' },
{ label: 'String', value: 'string' } { label: 'String', value: 'string' },
]); ]);
watch(() => props.config.messagePostFormat, (newValue) => { watch(
if (newValue !== 'array' && newValue !== 'string') { () => props.config.messagePostFormat,
props.config.messagePostFormat = 'array'; (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
} }
}); );
</script> </script>
<style scoped> <style scoped>
@@ -62,4 +65,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
} }
</style> </style>

View File

@@ -42,14 +42,17 @@ const props = defineProps<{
const messageFormatOptions = ref([ const messageFormatOptions = ref([
{ label: 'Array', value: 'array' }, { label: 'Array', value: 'array' },
{ label: 'String', value: 'string' } { label: 'String', value: 'string' },
]); ]);
watch(() => props.config.messagePostFormat, (newValue) => { watch(
if (newValue !== 'array' && newValue !== 'string') { () => props.config.messagePostFormat,
props.config.messagePostFormat = 'array'; (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
} }
}); );
</script> </script>
<style scoped> <style scoped>
@@ -68,4 +71,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
} }
</style> </style>

View File

@@ -39,14 +39,17 @@ const props = defineProps<{
const messageFormatOptions = ref([ const messageFormatOptions = ref([
{ label: 'Array', value: 'array' }, { label: 'Array', value: 'array' },
{ label: 'String', value: 'string' } { label: 'String', value: 'string' },
]); ]);
watch(() => props.config.messagePostFormat, (newValue) => { watch(
if (newValue !== 'array' && newValue !== 'string') { () => props.config.messagePostFormat,
props.config.messagePostFormat = 'array'; (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
} }
}); );
</script> </script>
<style scoped> <style scoped>
@@ -65,4 +68,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
} }
</style> </style>

View File

@@ -45,14 +45,17 @@ const props = defineProps<{
const messageFormatOptions = ref([ const messageFormatOptions = ref([
{ label: 'Array', value: 'array' }, { label: 'Array', value: 'array' },
{ label: 'String', value: 'string' } { label: 'String', value: 'string' },
]); ]);
watch(() => props.config.messagePostFormat, (newValue) => { watch(
if (newValue !== 'array' && newValue !== 'string') { () => props.config.messagePostFormat,
props.config.messagePostFormat = 'array'; (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
} }
}); );
</script> </script>
<style scoped> <style scoped>
@@ -71,4 +74,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
} }
</style> </style>

View File

@@ -1,267 +0,0 @@
<template>
<t-space class="full-space">
<t-tabs v-model="activeTab" :addable="true" theme="card" @add="showAddTabDialog" @remove="removeTab" class="full-tabs">
<t-tab-panel
v-for="(config, idx) in clientPanelData"
:key="idx"
:label="config.name"
:removable="true"
:value="idx"
class="full-tab-panel"
>
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
<div class="button-container">
<t-button @click="saveConfig" style="width: 100px; height: 40px;">保存</t-button>
</div>
</t-tab-panel>
</t-tabs>
<t-dialog
v-model:visible="isDialogVisible"
header="添加新选项卡"
@close="isDialogVisible = false"
@confirm="addTab"
>
<t-form ref="form" :model="newTab">
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
<t-input v-model="newTab.name" />
</t-form-item>
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
<t-select v-model="newTab.type">
<t-option value="httpServers">HTTP 服务器</t-option>
<t-option value="httpClients">HTTP 客户端</t-option>
<t-option value="websocketServers">WebSocket 服务器</t-option>
<t-option value="websocketClients">WebSocket 客户端</t-option>
</t-select>
</t-form-item>
</t-form>
</t-dialog>
</t-space>
</template>
<script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted, reactive, Reactive } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import {
httpServerDefaultConfigs,
httpClientDefaultConfigs,
websocketServerDefaultConfigs,
websocketClientDefaultConfigs,
HttpClientConfig,
HttpServerConfig,
WebsocketClientConfig,
WebsocketServerConfig,
NetworkConfig,
OneBotConfig,
mergeOneBotConfigs,
} from '../../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
const componentMap: Record<
ConfigKey,
| typeof HttpServerComponent
| typeof HttpClientComponent
| typeof WebsocketServerComponent
| typeof WebsocketClientComponent
> = {
httpServers: HttpServerComponent,
httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent,
};
interface ClientPanel {
name: string;
key: ConfigKey;
data: Ref<ConfigUnion>;
}
type ComponentKey = keyof typeof componentMap;
// TODO: store these state in global store (aka pinia)
const activeTab = ref<number>(0);
const isDialogVisible = ref(false);
const newTab = ref<{ name: string; type: ComponentKey }>({ name: '', type: 'httpServers' });
const clientPanelData: Reactive<Array<ClientPanel>> = reactive([]);
const getComponent = (type: ComponentKey) => {
return componentMap[type];
};
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
console.error('No stored credential found');
return;
}
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.GetOB11Config();
};
const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
console.error('No stored credential found');
return false;
}
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.SetOB11Config(config);
};
const addToPanel = <T extends ConfigUnion>(configs: T[], key: ConfigKey) => {
configs.forEach((config) => clientPanelData.push({ name: config.name, data: config, key: key }));
};
const addConfigDataToPanel = (data: NetworkConfig) => {
Object.entries(data).forEach(([key, configs]) => {
if (key in defaultConfigs) {
addToPanel(configs as ConfigUnion[], key as ConfigKey);
}
});
};
const parsePanelData = (): NetworkConfig => {
return {
websocketClients: clientPanelData
.filter((panel) => panel.key === 'websocketClients')
.map((panel) => panel.data as WebsocketClientConfig),
websocketServers: clientPanelData
.filter((panel) => panel.key === 'websocketServers')
.map((panel) => panel.data as WebsocketServerConfig),
httpClients: clientPanelData
.filter((panel) => panel.key === 'httpClients')
.map((panel) => panel.data as HttpClientConfig),
httpServers: clientPanelData
.filter((panel) => panel.key === 'httpServers')
.map((panel) => panel.data as HttpServerConfig),
};
};
const loadConfig = async () => {
try {
const userConfig = await getOB11Config();
if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network);
} catch (error) {
console.error('Error loading config:', error);
}
};
// It's better to "saveConfig" instead of using deep watch
const saveConfig = async () => {
const config = parsePanelData();
const userConfig = await getOB11Config();
if (!userConfig) return;
userConfig.network = config;
const success = await setOB11Config(userConfig);
if (success) {
MessagePlugin.success('配置保存成功');
} else {
MessagePlugin.error('配置保存失败');
}
};
const showAddTabDialog = () => {
newTab.value = { name: '', type: 'httpServers' };
isDialogVisible.value = true;
};
const addTab = async () => {
const { name, type } = newTab.value;
if (clientPanelData.some(panel => panel.name === name)) {
MessagePlugin.error('选项卡名称已存在');
return;
}
const defaultConfig = structuredClone(defaultConfigs[type]);
defaultConfig.name = name;
clientPanelData.push({ name, data: defaultConfig, key: type });
isDialogVisible.value = false;
await nextTick();
activeTab.value = clientPanelData.length - 1;
MessagePlugin.success('选项卡添加成功');
};
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
clientPanelData.splice(payload.index, 1);
activeTab.value = Math.max(0, activeTab.value - 1);
await saveConfig();
};
onMounted(() => {
loadConfig();
});
</script>
<style scoped>
.full-space {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.full-tabs {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.full-tab-panel {
flex: 1;
display: flex;
flex-direction: column;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>
<style scoped>
.full-space {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.full-tabs {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.full-tab-panel {
flex: 1;
display: flex;
flex-direction: column;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>

View File

@@ -1,10 +1,17 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import legacy from '@vitejs/plugin-legacy';
import path from 'path'; import path from 'path';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [
vue(),
legacy({
targets: ['defaults', 'not IE 11'],
modernPolyfills: ['web.structured-clone'],
}),
],
base: './', base: './',
resolve: { resolve: {
alias: { alias: {
@@ -17,14 +24,18 @@ export default defineConfig({
}, },
}, },
build: { build: {
chunkSizeWarningLimit: 4000,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks(id) { chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks(id: string) {
if (id.includes('node_modules')) { if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString(); return id.toString().split('node_modules/')[1].split('/')[0].toString();
} }
} },
} },
} },
} },
}); });

View File

@@ -2,10 +2,10 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "4.1.0", "version": "4.1.13",
"scripts": { "scripts": {
"build:framework": "npm run build:webui && vite build --mode framework", "build:framework": "npm run build:webui && vite build --mode framework || exit 1",
"build:shell": "npm run build:webui && vite build --mode shell", "build:shell": "npm run build:webui && vite build --mode shell || exit 1",
"build:webui": "cd napcat.webui && vite build", "build:webui": "cd napcat.webui && vite build",
"dev:framework": "vite build --mode framework", "dev:framework": "vite build --mode framework",
"dev:shell": "vite build --mode shell", "dev:shell": "vite build --mode shell",

View File

@@ -4,7 +4,7 @@ import path from 'node:path';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm'; import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
import { LogWrapper } from './log'; import { LogWrapper } from '@/common/log';
import { EncodeArgs } from "@/common/audio-worker"; import { EncodeArgs } from "@/common/audio-worker";
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000]; const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];

View File

@@ -234,25 +234,33 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
this.createListenerFunction(ListenerMainName); this.createListenerFunction(ListenerMainName);
this.createEventFunction(serviceAndMethod)!(...(args)) const eventResult = this.createEventFunction(serviceAndMethod)!(...(args));
.then((eventResult: any) => {
retEvent = eventResult; const eventRetHandle = (eventData: any) => {
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) { retEvent = eventData;
clearTimeout(timeoutRef); if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
reject( clearTimeout(timeoutRef);
new Error( reject(
'EventChecker Failed: NTEvent serviceAndMethod:' + new Error(
serviceAndMethod + 'EventChecker Failed: NTEvent serviceAndMethod:' +
' ListenerName:' + serviceAndMethod +
listenerAndMethod + ' ListenerName:' +
' EventRet:\n' + listenerAndMethod +
JSON.stringify(retEvent, null, 4) + ' EventRet:\n' +
'\n', JSON.stringify(retEvent, null, 4) +
), '\n',
); ),
} );
}
};
if (eventResult instanceof Promise) {
eventResult.then((eventResult: any) => {
eventRetHandle(eventResult);
}) })
.catch(reject); .catch(reject);
} else {
eventRetHandle(eventResult);
}
}, },
); );
} }

View File

@@ -4,7 +4,20 @@ import crypto, { randomUUID } from 'crypto';
import util from 'util'; import util from 'util';
import path from 'node:path'; import path from 'node:path';
import * as fileType from 'file-type'; import * as fileType from 'file-type';
import { solveProblem } from './helper'; import { solveProblem } from '@/common/helper';
export interface HttpDownloadOptions {
url: string;
headers?: Record<string, string> | string;
}
type Uri2LocalRes = {
success: boolean,
errMsg: string,
fileName: string,
ext: string,
path: string
}
export function isGIF(path: string) { export function isGIF(path: string) {
const buffer = Buffer.alloc(4); const buffer = Buffer.alloc(4);
@@ -15,7 +28,7 @@ export function isGIF(path: string) {
} }
// 定义一个异步函数来检查文件是否存在 // 定义一个异步函数来检查文件是否存在
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> { export function checkFileExist(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const startTime = Date.now(); const startTime = Date.now();
@@ -34,7 +47,7 @@ export function checkFileReceived(path: string, timeout: number = 3000): Promise
} }
// 定义一个异步函数来检查文件是否存在 // 定义一个异步函数来检查文件是否存在
export async function checkFileReceived2(path: string, timeout: number = 3000): Promise<void> { export async function checkFileExistV2(path: string, timeout: number = 3000): Promise<void> {
// 使用 Promise.race 来同时进行文件状态检查和超时计时 // 使用 Promise.race 来同时进行文件状态检查和超时计时
// Promise.race 会返回第一个解决resolve或拒绝reject的 Promise // Promise.race 会返回第一个解决resolve或拒绝reject的 Promise
await Promise.race([ await Promise.race([
@@ -75,18 +88,13 @@ export async function file2base64(path: string) {
data: '', data: '',
}; };
try { try {
// 读取文件内容
// if (!fs.existsSync(path)){
// path = path.replace("\\Ori\\", "\\Thumb\\");
// }
try { try {
await checkFileReceived(path, 5000); await checkFileExist(path, 5000);
} catch (e: any) { } catch (e: any) {
result.err = e.toString(); result.err = e.toString();
return result; return result;
} }
const data = await readFile(path); const data = await readFile(path);
// 转换为Base64编码
result.data = data.toString('base64'); result.data = data.toString('base64');
} catch (err: any) { } catch (err: any) {
result.err = err.toString(); result.err = err.toString();
@@ -118,13 +126,7 @@ export function calculateFileMD5(filePath: string): Promise<string> {
}); });
} }
export interface HttpDownloadOptions {
url: string;
headers?: Record<string, string> | string;
}
async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> { async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
// const chunks: Buffer[] = [];
let url: string; let url: string;
let headers: Record<string, string> = { let headers: Record<string, string> = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
@@ -166,14 +168,6 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
return Buffer.from(buffer); return Buffer.from(buffer);
} }
type Uri2LocalRes = {
success: boolean,
errMsg: string,
fileName: string,
ext: string,
path: string
}
export async function checkFileV2(filePath: string) { export async function checkFileV2(filePath: string) {
try { try {
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext; const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
@@ -242,7 +236,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
//解析Http和Https协议 //解析Http和Https协议
if (UriType == FileUriType.Unknown) { if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' }; return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
} }
//解析File协议和本地文件 //解析File协议和本地文件
if (UriType == FileUriType.Local) { if (UriType == FileUriType.Local) {
@@ -289,5 +283,5 @@ export async function uri2local(dir: string, uri: string, filename: string | und
} }
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath }; return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
} }
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' }; return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
} }

View File

@@ -2,7 +2,7 @@ import winston, { format, transports } from 'winston';
import { truncateString } from '@/common/helper'; import { truncateString } from '@/common/helper';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import { AtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core'; import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
export enum LogLevel { export enum LogLevel {
DEBUG = 'debug', DEBUG = 'debug',
@@ -36,7 +36,7 @@ export class LogWrapper {
this.logger = winston.createLogger({ this.logger = winston.createLogger({
level: 'debug', level: 'debug',
format: format.combine( format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.timestamp({ format: 'MM-DD HH:mm:ss' }),
format.printf(({ timestamp, level, message, ...meta }) => { format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : ''; const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`; return `${timestamp} [${level}] ${userInfo}${message}`;
@@ -61,7 +61,7 @@ export class LogWrapper {
] ]
}); });
this.setLogSelfInfo({ nick: '', uin: '', uid: '' }); this.setLogSelfInfo({ nick: '', uid: '' });
this.cleanOldLogs(logDir); this.cleanOldLogs(logDir);
} }
@@ -111,8 +111,8 @@ export class LogWrapper {
}); });
} }
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) { setLogSelfInfo(selfInfo: { nick: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`; const userInfo = `${selfInfo.nick}`;
this.logger.defaultMeta = { userInfo }; this.logger.defaultMeta = { userInfo };
} }
@@ -270,12 +270,12 @@ function msgElementToText(element: MessageElement, msg: RawMessage, recursiveLev
} }
function textElementToText(textElement: any): string { function textElementToText(textElement: any): string {
if (textElement.atType === AtType.notAt) { if (textElement.atType === NTMsgAtType.ATTYPEUNKNOWN) {
const originalContentLines = textElement.content.split('\n'); const originalContentLines = textElement.content.split('\n');
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`; return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
} else if (textElement.atType === AtType.atAll) { } else if (textElement.atType === NTMsgAtType.ATTYPEALL) {
return `@全体成员`; return `@全体成员`;
} else if (textElement.atType === AtType.atUser) { } else if (textElement.atType === NTMsgAtType.ATTYPEONE) {
return `${textElement.content} (${textElement.atUid})`; return `${textElement.content} (${textElement.atUid})`;
} }
return ''; return '';

View File

@@ -1,4 +1,4 @@
import { LogWrapper } from './log'; import { LogWrapper } from '@/common/log';
export function proxyHandlerOf(logger: LogWrapper) { export function proxyHandlerOf(logger: LogWrapper) {
return { return {

View File

@@ -2,7 +2,7 @@ import fs from 'node:fs';
import { systemPlatform } from '@/common/system'; import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper'; import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
import AppidTable from '@/core/external/appid.json'; import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log'; import { LogWrapper } from '@/common/log';
import { getMajorPath } from '@/core'; import { getMajorPath } from '@/core';
export class QQBasicInfoWrapper { export class QQBasicInfoWrapper {

View File

@@ -1 +1 @@
export const napCatVersion = '4.1.0'; export const napCatVersion = '4.1.13';

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import { MsfChangeReasonType, MsfStatusType } from "../entities/adapter"; import { MsfChangeReasonType, MsfStatusType } from "../types/adapter";
export class NodeIDependsAdapter { export class NodeIDependsAdapter {
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) { onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {

View File

@@ -1,4 +1,4 @@
import { InstanceContext, NapCatCore } from '..'; import { InstanceContext, NapCatCore } from '@/core';
export class NTQQCollectionApi { export class NTQQCollectionApi {
context: InstanceContext; context: InstanceContext;

View File

@@ -5,13 +5,14 @@ import {
IMAGE_HTTP_HOST_NT, IMAGE_HTTP_HOST_NT,
Peer, Peer,
PicElement, PicElement,
PicSubType,
PicType, PicType,
RawMessage, RawMessage,
SendFileElement, SendFileElement,
SendPicElement, SendPicElement,
SendPttElement, SendPttElement,
SendVideoElement, SendVideoElement,
} from '@/core/entities'; } from '@/core/types';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import fsPromises from 'fs/promises'; import fsPromises from 'fs/promises';
@@ -36,7 +37,11 @@ export class NTQQFileApi {
constructor(context: InstanceContext, core: NapCatCore) { constructor(context: InstanceContext, core: NapCatCore) {
this.context = context; this.context = context;
this.core = core; this.core = core;
this.rkeyManager = new RkeyManager(['https://llob.linyuchen.net/rkey', 'http://napcat-sign.wumiao.wang:2082/rkey'], this.context.logger); this.rkeyManager = new RkeyManager([
'https://rkey.napneko.icu/rkeys'
],
this.context.logger
);
} }
async copyFile(filePath: string, destPath: string) { async copyFile(filePath: string, destPath: string) {
@@ -102,12 +107,12 @@ export class NTQQFileApi {
fileName: fileName || _fileName, fileName: fileName || _fileName,
folderId: folderId, folderId: folderId,
filePath: path, filePath: path,
fileSize: (fileSize).toString(), fileSize: fileSize.toString(),
}, },
}; };
} }
async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> { async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType); const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
if (fileSize === 0) { if (fileSize === 0) {
throw new Error('文件异常大小为0'); throw new Error('文件异常大小为0');
@@ -125,7 +130,7 @@ export class NTQQFileApi {
fileName: fileName, fileName: fileName,
sourcePath: path, sourcePath: path,
original: true, original: true,
picType: isGIF(picPath) ? PicType.gif : PicType.jpg, picType: isGIF(picPath) ? PicType.NEWPIC_GIF : PicType.NEWPIC_JPEG,
picSubType: subType, picSubType: subType,
fileUuid: '', fileUuid: '',
fileSubId: '', fileSubId: '',
@@ -138,7 +143,8 @@ export class NTQQFileApi {
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> { async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
const logger = this.core.context.logger; const logger = this.core.context.logger;
let videoInfo = { let videoInfo = {
width: 1920, height: 1080, width: 1920,
height: 1080,
time: 15, time: 15,
format: 'mp4', format: 'mp4',
size: 0, size: 0,
@@ -300,18 +306,18 @@ export class NTQQFileApi {
element.elementType === ElementType.FILE element.elementType === ElementType.FILE
) { ) {
switch (element.elementType) { switch (element.elementType) {
case ElementType.PIC: case ElementType.PIC:
element.picElement!.sourcePath = elementResults[elementIndex]; element.picElement!.sourcePath = elementResults[elementIndex];
break; break;
case ElementType.VIDEO: case ElementType.VIDEO:
element.videoElement!.filePath = elementResults[elementIndex]; element.videoElement!.filePath = elementResults[elementIndex];
break; break;
case ElementType.PTT: case ElementType.PTT:
element.pttElement!.filePath = elementResults[elementIndex]; element.pttElement!.filePath = elementResults[elementIndex];
break; break;
case ElementType.FILE: case ElementType.FILE:
element.fileElement!.filePath = elementResults[elementIndex]; element.fileElement!.filePath = elementResults[elementIndex];
break; break;
} }
elementIndex++; elementIndex++;
} }
@@ -320,7 +326,7 @@ export class NTQQFileApi {
} }
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
// 用于下载收到的消息中的图片等 // 用于下载文件
if (sourcePath && fs.existsSync(sourcePath)) { if (sourcePath && fs.existsSync(sourcePath)) {
if (force) { if (force) {
try { try {
@@ -412,15 +418,17 @@ export class NTQQFileApi {
} }
const url: string = element.originImageUrl ?? ''; const url: string = element.originImageUrl ?? '';
const md5HexStr = element.md5HexStr; const md5HexStr = element.md5HexStr;
const fileMd5 = element.md5HexStr; const fileMd5 = element.md5HexStr;
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
if (url) { const imageAppid = parsedUrl.searchParams.get('appid');
const parsedUrl = new URL(IMAGE_HTTP_HOST + url); const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
const imageFileId = parsedUrl.searchParams.get('fileid');
if (url && isNTV2 && imageFileId) {
const rkeyData = await this.getRkeyData(); const rkeyData = await this.getRkeyData();
return this.getImageUrlFromParsedUrl(parsedUrl, rkeyData); return this.getImageUrlFromParsedUrl(imageFileId, imageAppid, rkeyData);
} }
return this.getImageUrlFromMd5(fileMd5, md5HexStr); return this.getImageUrlFromMd5(fileMd5, md5HexStr);
} }
@@ -462,19 +470,12 @@ export class NTQQFileApi {
return rkeyData; return rkeyData;
} }
private getImageUrlFromParsedUrl(parsedUrl: URL, rkeyData: any): string { private getImageUrlFromParsedUrl(imageFileId: string, appid: string, rkeyData: any): string {
const imageAppid = parsedUrl.searchParams.get('appid'); const rkey = appid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid); if (rkeyData.online_rkey) {
const imageFileId = parsedUrl.searchParams.get('fileid'); return IMAGE_HTTP_HOST_NT + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}`;
if (isNTV2 && rkeyData.online_rkey) {
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST_NT + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
} else if (isNTV2 && imageFileId) {
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
return IMAGE_HTTP_HOST + `/download?appid=${imageAppid}&fileid=${imageFileId}&rkey=${rkey}`;
} }
return IMAGE_HTTP_HOST + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}`;
return '';
} }
private getImageUrlFromMd5(fileMd5: string | undefined, md5HexStr: string | undefined): string { private getImageUrlFromMd5(fileMd5: string | undefined, md5HexStr: string | undefined): string {

View File

@@ -1,4 +1,4 @@
import { FriendV2 } from '@/core/entities'; import { FriendV2 } from '@/core/types';
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core'; import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
import { LimitedHashTable } from '@/common/message-unique'; import { LimitedHashTable } from '@/common/message-unique';
@@ -24,7 +24,7 @@ export class NTQQFriendApi {
); );
} }
async getBuddyV2(refresh = false): Promise<FriendV2[]> { async getBuddy(refresh = false): Promise<FriendV2[]> {
return Array.from((await this.getBuddyV2SimpleInfoMap(refresh)).values()); return Array.from((await this.getBuddyV2SimpleInfoMap(refresh)).values());
} }
@@ -58,7 +58,7 @@ export class NTQQFriendApi {
categoryName: category.categroyName, categoryName: category.categroyName,
categoryMbCount: category.categroyMbCount, categoryMbCount: category.categroyMbCount,
onlineCount: category.onlineCount, onlineCount: category.onlineCount,
buddyList: category.buddyUids.map(uid => data.get(uid)!).filter(value => value), buddyList: category.buddyUids.map(uid => data.get(uid)).filter(value => !!value),
})); }));
} }

View File

@@ -2,8 +2,8 @@ import {
GeneralCallResult, GeneralCallResult,
Group, Group,
GroupMember, GroupMember,
GroupMemberRole, NTGroupMemberRole,
GroupRequestOperateTypes, NTGroupRequestOperateTypes,
InstanceContext, InstanceContext,
KickMemberV2Req, KickMemberV2Req,
MemberExtSourceType, MemberExtSourceType,
@@ -147,14 +147,11 @@ export class NTQQGroupApi {
if (!members) { if (!members) {
try { try {
members = await this.getGroupMembers(groupCodeStr); members = await this.getGroupMembers(groupCodeStr);
// 更新群成员列表
this.groupMemberCache.set(groupCodeStr, members); this.groupMemberCache.set(groupCodeStr, members);
} catch (e) { } catch (e) {
return null; return null;
} }
} }
// log('getGroupMember', members);
function getMember() { function getMember() {
let member: GroupMember | undefined; let member: GroupMember | undefined;
if (isNumeric(memberUinOrUidStr)) { if (isNumeric(memberUinOrUidStr)) {
@@ -367,7 +364,6 @@ export class NTQQGroupApi {
} }
} }
this.context.session.getGroupService().destroyMemberListScene(sceneId); this.context.session.getGroupService().destroyMemberListScene(sceneId);
//console.log('GetGroupMembersV3 len :', result.result.infos.size, resMode2?.infos.size, groupQQ);
return { return {
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]), infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish, finish: result.result.finish,
@@ -421,7 +417,7 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().uploadGroupBulletinPic(GroupCode, _Pskey, imageurl); return this.context.session.getGroupService().uploadGroupBulletinPic(GroupCode, _Pskey, imageurl);
} }
async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) { async handleGroupRequest(flag: string, operateType: NTGroupRequestOperateTypes, reason?: string) {
const flagitem = flag.split('|'); const flagitem = flag.split('|');
const groupCode = flagitem[0]; const groupCode = flagitem[0];
const seq = flagitem[1]; const seq = flagitem[1];
@@ -430,7 +426,7 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().operateSysNotify( return this.context.session.getGroupService().operateSysNotify(
false, false,
{ {
operateType: operateType, // 2 拒绝 operateType: operateType,
targetMsg: { targetMsg: {
seq: seq, // 通知序列号 seq: seq, // 通知序列号
type: type, type: type,
@@ -461,7 +457,7 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName); return this.context.session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName);
} }
async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { async setMemberRole(groupQQ: string, memberUid: string, role: NTGroupMemberRole) {
return this.context.session.getGroupService().modifyMemberRole(groupQQ, memberUid, role); return this.context.session.getGroupService().modifyMemberRole(groupQQ, memberUid, role);
} }

View File

@@ -1,14 +1,9 @@
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/entities'; import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/types';
import { InstanceContext, NapCatCore } from '@/core'; import { GroupFileInfoUpdateItem, InstanceContext, NapCatCore } from '@/core';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi { export class NTQQMsgApi {
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
}
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
context: InstanceContext; context: InstanceContext;
core: NapCatCore; core: NapCatCore;
@@ -17,7 +12,10 @@ export class NTQQMsgApi {
this.context = context; this.context = context;
this.core = core; this.core = core;
} }
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
// https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType 可以用过特殊方式拉取
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
}
async getAioFirstViewLatestMsgs(peer: Peer, MsgCount: number) { async getAioFirstViewLatestMsgs(peer: Peer, MsgCount: number) {
return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount); return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount);
} }
@@ -25,9 +23,11 @@ export class NTQQMsgApi {
async sendShowInputStatusReq(peer: Peer, eventType: number) { async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid); return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
} }
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) { async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time); return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
} }
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) { async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa //注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count); return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
@@ -111,7 +111,7 @@ export class NTQQMsgApi {
pageLimit: 1, pageLimit: 1,
}); });
} }
// 客户端还在用别慌 // 客户端还在用别慌
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) { async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder); return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
} }
@@ -136,19 +136,29 @@ export class NTQQMsgApi {
} }
async getGroupFileList(GroupCode: string, params: GetFileListParam) { async getGroupFileList(GroupCode: string, params: GetFileListParam) {
const [, groupFileListResult] = await this.core.eventWrapper.callNormalEventV2( const item: GroupFileInfoUpdateItem[] = [];
'NodeIKernelRichMediaService/getGroupFileList', let index = params.startIndex;
'NodeIKernelMsgListener/onGroupFileInfoUpdate', while (true) {
[ params.startIndex = index;
GroupCode, const [, groupFileListResult] = await this.core.eventWrapper.callNormalEventV2(
params, 'NodeIKernelRichMediaService/getGroupFileList',
], 'NodeIKernelMsgListener/onGroupFileInfoUpdate',
() => true, [
() => true, // 应当通过 groupFileListResult 判断 GroupCode,
1, params,
5000, ],
); () => true,
return groupFileListResult.item; () => true, // 应当通过 groupFileListResult 判断
1,
5000,
);
if (!groupFileListResult?.item?.length) break;
item.push(...groupFileListResult.item);
if (groupFileListResult.isEnd) break;
if (item.length === params.fileCount) break;
index = groupFileListResult.nextIndex;
}
return item;
} }
async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) { async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {

View File

@@ -1,4 +1,4 @@
import { ModifyProfileParams, User, UserDetailSource } from '@/core/entities'; import { ModifyProfileParams, User, UserDetailSource } from '@/core/types';
import { RequestUtil } from '@/common/request'; import { RequestUtil } from '@/common/request';
import { InstanceContext, NapCatCore, ProfileBizType } from '..'; import { InstanceContext, NapCatCore, ProfileBizType } from '..';
import { solveAsyncProblem } from '@/common/helper'; import { solveAsyncProblem } from '@/common/helper';

View File

@@ -8,6 +8,9 @@ import {
WebHonorType, WebHonorType,
} from '@/core'; } from '@/core';
import { NapCatCore } from '..'; import { NapCatCore } from '..';
import { createReadStream, readFileSync, statSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { basename } from 'node:path';
export class NTQQWebApi { export class NTQQWebApi {
context: InstanceContext; context: InstanceContext;
@@ -303,4 +306,110 @@ export class NTQQWebApi {
} }
return (hash & 0x7FFFFFFF).toString(); return (hash & 0x7FFFFFFF).toString();
} }
async createQunAlbumSession(gc: string, sAlbumID: string, sAlbumName: string, path: string, skey: string, pskey: string, uin: string) {
const img = readFileSync(path);
const img_md5 = createHash('md5').update(img).digest('hex');
const img_size = img.length;
const img_name = basename(path);
const time = Math.floor(Date.now() / 1000);
const GTK = this.getBknFromSKey(pskey);
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
const body = {
control_req: [{
uin: uin,
token: {
type: 4,
data: pskey,
appid: 5
},
appid: "qun",
checksum: img_md5,
check_type: 0,
file_len: img_size,
env: {
refer: "qzone",
deviceInfo: "h5"
},
model: 0,
biz_req: {
sPicTitle: img_name,
sPicDesc: "",
sAlbumName: sAlbumName,
sAlbumID: sAlbumID,
iAlbumTypeID: 0,
iBitmap: 0,
iUploadType: 0,
iUpPicType: 0,
iBatchID: time,
sPicPath: "",
iPicWidth: 0,
iPicHight: 0,
iWaterType: 0,
iDistinctUse: 0,
iNeedFeeds: 1,
iUploadTime: time,
mapExt: {
appid: "qun",
userid: gc
}
},
session: "",
asy_upload: 0,
cmd: "FileUpload"
}]
};
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileBatchControl/${img_md5}?g_tk=${GTK}`;
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
"Cookie": cookie,
"Content-Type": "application/json"
});
return post;
}
async uploadQunAlbumSlice(path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) {
const img_size = statSync(path).size;
const img_name = basename(path);
let seq = 0;
let offset = 0;
const GTK = this.getBknFromSKey(pskey);
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
const stream = createReadStream(path, { highWaterMark: slice_size });
for await (const chunk of stream) {
const end = Math.min(offset + chunk.length, img_size);
const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
const formData = await RequestUtil.createFormData(boundary, path);
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileUpload?seq=${seq}&retry=0&offset=${offset}&end=${end}&total=${img_size}&type=form&g_tk=${GTK}`;
const body = {
uin: uin,
appid: "qun",
session: session,
offset: offset,
data: formData,
checksum: "",
check_type: 0,
retry: 0,
seq: seq,
end: end,
cmd: "FileUpload",
slice_size: slice_size,
"biz_req.iUploadType": 0
};
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
"Cookie": cookie,
"Content-Type": `multipart/form-data; boundary=${boundary}`
});
offset += chunk.length;
seq++;
}
}
async uploadQunAlbum(path: string, albumId: string, group: string, skey: string, pskey: string, uin: string) {
const session = (await this.createQunAlbumSession(group, albumId, group, path, skey, pskey, uin) as { data: { session: string } }).data.session;
return await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 1024 * 1024);
}
} }

View File

@@ -1,65 +0,0 @@
import { ChatType } from './msg';
export interface CacheScanResult {
result: number;
size: [ // 单位为字节
string, // 系统总存储空间
string, // 系统可用存储空间
string, // 系统已用存储空间
string, // QQ总大小
string, // 「聊天与文件」大小
string, // 未知
string, // 「缓存数据」大小
string, // 「其他数据」大小
string, // 未知
];
}
export interface ChatCacheList {
pageCount: number;
infos: ChatCacheListItem[];
}
export interface ChatCacheListItem {
chatType: ChatType;
basicChatCacheInfo: ChatCacheListItemBasic;
guildChatCacheInfo: unknown[]; // work: 没用过频道所以不知道这里边的详细内容
}
export interface ChatCacheListItemBasic {
chatSize: string;
chatTime: string;
uid: string;
uin: string;
remarkName: string;
nickName: string;
chatType?: ChatType;
isChecked?: boolean;
}
export enum CacheFileType {
IMAGE = 0,
VIDEO = 1,
AUDIO = 2,
DOCUMENT = 3,
OTHER = 4,
}
export interface CacheFileList {
infos: CacheFileListItem[],
}
export interface CacheFileListItem {
fileSize: string;
fileTime: string;
fileKey: string;
elementId: string;
elementIdStr: string;
fileType: CacheFileType;
path: string;
fileName: string;
senderId: string;
previewPath: string;
senderName: string;
isChecked?: boolean;
}

View File

@@ -1,867 +0,0 @@
import { GroupMemberRole } from '@/core';
export interface Peer {
chatType: ChatType;
peerUid: string; // 如果是群聊uid为群号私聊uid就是加密的字符串
guildId?: string;
}
export interface KickedOffLineInfo {
appId: number;
instanceId: number;
sameDevice: boolean;
tipsDesc: string;
tipsTitle: string;
kickedType: number;
securityKickedType: number;
}
export interface GetFileListParam {
sortType: number;
fileCount: number;
startIndex: number;
sortOrder: number;
showOnlinedocFolder: number;
folderId?: string;
}
export enum ElementType {
UNKNOWN = 0,
TEXT = 1,
PIC = 2,
FILE = 3,
PTT = 4,
VIDEO = 5,
FACE = 6,
REPLY = 7,
GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等
WALLET = 9,
ARK = 10,
MFACE = 11,
LIVEGIFT = 12,
STRUCTLONGMSG = 13,
MARKDOWN = 14,
GIPHY = 15,
MULTIFORWARD = 16,
INLINEKEYBOARD = 17,
INTEXTGIFT = 18,
CALENDAR = 19,
YOLOGAMERESULT = 20,
AVRECORD = 21,
FEED = 22,
TOFURECORD = 23,
ACEBUBBLE = 24,
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
}
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
type ElementBase<
K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
> = {
[P in K]:
S[P] extends Array<infer U>
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
: S[P] extends keyof NonNullable<ElementFullBase[P]>
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
: NonNullable<ElementFullBase[P]>;
};
export interface SendElementBase<ET extends ElementType> {
elementType: ET;
elementId: string;
extBufForUI?: string;
}
export interface ActionBarElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface RecommendedMsgElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export type SendRecommendedMsgElement = SendElementBase<ElementType.RECOMMENDEDMSG> & ElementBase<'recommendedMsgElement'>;
export interface InlineKeyboardButton {
id: string;
label: string;
visitedLabel: string;
unsupportTips: string;
data: string;
specifyRoleIds: string[];
specifyTinyids: string[];
style: number;
type: number;
clickLimit: number;
atBotShowChannelList: boolean;
permissionType: number;
}
export interface InlineKeyboardRow {
buttons: InlineKeyboardButton[];
}
export interface TofuElementContent {
color: string;
tittle: string;
}
export interface TaskTopMsgElement {
msgTitle: string;
msgSummary: string;
iconUrl: string;
topMsgType: number;
}
export enum NTMsgType {
KMSGTYPEARKSTRUCT = 11,
KMSGTYPEFACEBUBBLE = 24,
KMSGTYPEFILE = 3,
KMSGTYPEGIFT = 14,
KMSGTYPEGIPHY = 13,
KMSGTYPEGRAYTIPS = 5,
KMSGTYPEMIX = 2,
KMSGTYPEMULTIMSGFORWARD = 8,
KMSGTYPENULL = 1,
KMSGTYPEONLINEFILE = 21,
KMSGTYPEONLINEFOLDER = 27,
KMSGTYPEPROLOGUE = 29,
KMSGTYPEPTT = 6,
KMSGTYPEREPLY = 9,
KMSGTYPESHARELOCATION = 25,
KMSGTYPESTRUCT = 4,
KMSGTYPESTRUCTLONGMSG = 12,
KMSGTYPETEXTGIFT = 15,
KMSGTYPEUNKNOWN = 0,
KMSGTYPEVIDEO = 7,
KMSGTYPEWALLET = 10
}
export type SendTaskTopMsgElement = SendElementBase<ElementType.TASKTOPMSG> & ElementBase<'taskTopMsgElement'>;
export interface TofuRecordElement {
type: number;
busiid: string;
busiuuid: string;
descriptionContent: string;
contentlist: TofuElementContent[],
background: string;
icon: string;
uinlist: string[],
uidlist: string[],
busiExtra: string;
updateTime: string;
dependedmsgid: string;
msgtime: string;
onscreennotify: boolean;
}
export type SendTofuRecordElement = SendElementBase<ElementType.TOFURECORD> & ElementBase<'tofuRecordElement'>;
export interface FaceBubbleElement {
faceCount: number;
faceSummary: string;
faceFlag: number;
content: string;
oldVersionStr: string;
faceType: number;
others: string;
yellowFaceInfo: {
index: number;
buf: string;
compatibleText: string;
text: string;
};
}
export type SendFaceBubbleElement = SendElementBase<ElementType.FACEBUBBLE> & ElementBase<'faceBubbleElement'>;
export interface AvRecordElement {
type: number;
time: string;
text: string;
mainType: number;
hasRead: boolean;
extraType: number;
}
export type SendAvRecordElement = SendElementBase<ElementType.AVRECORD> & ElementBase<'avRecordElement'>;
export interface YoloUserInfo {
uid: string;
result: number;
rank: number;
bizId: string;
}
export type SendInlineKeyboardElement = SendElementBase<ElementType.INLINEKEYBOARD> & ElementBase<'inlineKeyboardElement'>;
export interface YoloGameResultElement {
UserInfo: YoloUserInfo[];
}
export type SendYoloGameResultElement = SendElementBase<ElementType.YOLOGAMERESULT> & ElementBase<'yoloGameResultElement'>;
export interface GiphyElement {
id: string;
isClip: boolean;
width: number;
height: number;
}
export type SendGiphyElement = SendElementBase<ElementType.GIPHY> & ElementBase<'giphyElement'>;
export type SendWalletElement = SendElementBase<ElementType.UNKNOWN> & ElementBase<'walletElement'>;
export interface CalendarElement {
summary: string;
msg: string;
expireTimeMs: string;
schemaType: number;
schema: string;
}
export type SendCalendarElement = SendElementBase<ElementType.CALENDAR> & ElementBase<'calendarElement'>;
export type SendLiveGiftElement = SendElementBase<ElementType.LIVEGIFT> & ElementBase<'liveGiftElement'>;
export type SendTextElement = SendElementBase<ElementType.TEXT> & ElementBase<'textElement'>;
export type SendPttElement = SendElementBase<ElementType.PTT> & ElementBase<'pttElement', {
pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType',
'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText']
}>;
export enum PicType {
gif = 2000,
jpg = 1000
}
export enum PicSubType {
normal = 0, // 普通图片,大图
face = 1 // 表情包小图
}
export enum NTMsgAtType {
ATTYPEALL = 1,
ATTYPECATEGORY = 512,
ATTYPECHANNEL = 16,
ATTYPEME = 4,
ATTYPEONE = 2,
ATTYPEONLINE = 64,
ATTYPEROLE = 8,
ATTYPESUMMON = 32,
ATTYPESUMMONONLINE = 128,
ATTYPESUMMONROLE = 256,
ATTYPEUNKNOWN = 0
}
export type SendPicElement = SendElementBase<ElementType.PIC> & ElementBase<'picElement'>;
export interface ReplyElement {
sourceMsgIdInRecords?: string;
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUidStr?: string;
replyMsgTime?: string;
replyMsgClientSeq?: string;
}
export type SendReplyElement = SendElementBase<ElementType.REPLY> & ElementBase<'replyElement'>;
export type SendFaceElement = SendElementBase<ElementType.FACE> & ElementBase<'faceElement'>;
export type SendMarketFaceElement = SendElementBase<ElementType.MFACE> & ElementBase<'marketFaceElement'>;
export type SendStructLongMsgElement = SendElementBase<ElementType.STRUCTLONGMSG> & ElementBase<'structLongMsgElement'>;
export interface StructLongMsgElement {
xmlContent: string;
resId: string;
}
export type SendActionBarElement = SendElementBase<ElementType.ACTIONBAR> & ElementBase<'actionBarElement'>;
export interface ShareLocationElement {
text: string;
ext: string;
}
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
export interface FileElement {
fileMd5?: string;
fileName: string;
filePath: string;
fileSize: string;
picHeight?: number;
picWidth?: number;
folderId?: string;
picThumbPath?: Map<number, string>;
file10MMd5?: string;
fileSha?: string;
fileSha3?: string;
fileUuid?: string;
fileSubId?: string;
thumbFileSize?: number;
fileBizId?: number;
}
export type SendFileElement = SendElementBase<ElementType.FILE> & ElementBase<'fileElement'>;
export type SendVideoElement = SendElementBase<ElementType.VIDEO> & ElementBase<'videoElement'>;
export type SendArkElement = SendElementBase<ElementType.ARK> & ElementBase<'arkElement'>;
export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & ElementBase<'markdownElement'>;
export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;
export interface TextElement {
content: string;
atType: number;
atUid: string;
atTinyId: string;
atNtUid: string;
}
export interface MessageElement {
elementType: ElementType,
elementId: string,
extBufForUI?: string, //"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarketFaceElement,
replyElement?: ReplyElement,
picElement?: PicElement,
pttElement?: PttElement,
videoElement?: VideoElement,
grayTipElement?: GrayTipElement,
arkElement?: ArkElement,
fileElement?: FileElement,
liveGiftElement?: null,
markdownElement?: MarkdownElement,
structLongMsgElement?: StructLongMsgElement,
multiForwardMsgElement?: MultiForwardMsgElement,
giphyElement?: GiphyElement,
walletElement?: null,
inlineKeyboardElement?: InlineKeyboardElement,
textGiftElement?: null,//????
calendarElement?: CalendarElement,
yoloGameResultElement?: YoloGameResultElement,
avRecordElement?: AvRecordElement,
structMsgElement?: null,
faceBubbleElement?: FaceBubbleElement,
shareLocationElement?: ShareLocationElement,
tofuRecordElement?: TofuRecordElement,
taskTopMsgElement?: TaskTopMsgElement,
recommendedMsgElement?: RecommendedMsgElement,
actionBarElement?: ActionBarElement
}
export enum AtType {
notAt = 0,
atAll = 1,
atUser = 2
}
export enum MsgSourceType {
K_DOWN_SOURCETYPE_AIOINNER = 1,
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
K_DOWN_SOURCETYPE_HISTORY = 3,
K_DOWN_SOURCETYPE_UNKNOWN = 0
}
// 来自Android分析
export enum ChatType {
KCHATTYPEADELIE = 42,
KCHATTYPEBUDDYNOTIFY = 5,
KCHATTYPEC2C = 1,
KCHATTYPECIRCLE = 113,
KCHATTYPEDATALINE = 8,
KCHATTYPEDATALINEMQQ = 134,
KCHATTYPEDISC = 3,
KCHATTYPEFAV = 41,
KCHATTYPEGAMEMESSAGE = 105,
KCHATTYPEGAMEMESSAGEFOLDER = 116,
KCHATTYPEGROUP = 2,
KCHATTYPEGROUPBLESS = 133,
KCHATTYPEGROUPGUILD = 9,
KCHATTYPEGROUPHELPER = 7,
KCHATTYPEGROUPNOTIFY = 6,
KCHATTYPEGUILD = 4,
KCHATTYPEGUILDMETA = 16,
KCHATTYPEMATCHFRIEND = 104,
KCHATTYPEMATCHFRIENDFOLDER = 109,
KCHATTYPENEARBY = 106,
KCHATTYPENEARBYASSISTANT = 107,
KCHATTYPENEARBYFOLDER = 110,
KCHATTYPENEARBYHELLOFOLDER = 112,
KCHATTYPENEARBYINTERACT = 108,
KCHATTYPEQQNOTIFY = 132,
KCHATTYPERELATEACCOUNT = 131,
KCHATTYPESERVICEASSISTANT = 118,
KCHATTYPESERVICEASSISTANTSUB = 201,
KCHATTYPESQUAREPUBLIC = 115,
KCHATTYPESUBSCRIBEFOLDER = 30,
KCHATTYPETEMPADDRESSBOOK = 111,
KCHATTYPETEMPBUSSINESSCRM = 102,
KCHATTYPETEMPC2CFROMGROUP = 100,
KCHATTYPETEMPC2CFROMUNKNOWN = 99,
KCHATTYPETEMPFRIENDVERIFY = 101,
KCHATTYPETEMPNEARBYPRO = 119,
KCHATTYPETEMPPUBLICACCOUNT = 103,
KCHATTYPETEMPWPA = 117,
KCHATTYPEUNKNOWN = 0,
KCHATTYPEWEIYUN = 40,
}
export interface PttElement {
canConvert2Text: boolean;
duration: number; // 秒数
fileBizId: null;
fileId: number; // 0
fileName: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr"
filePath: string; // "/Users//Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr"
fileSize: string; // "4261"
fileSubId: string; // "0"
fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
formatType: number; // 1
invalidState: number; // 0
md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6"
playState: number; // 0
progress: number; // 0
text: string; // ""
transferStatus: number; // 0
translateStatus: number; // 0
voiceChangeType: number; // 0
voiceType: number; // 0
waveAmplitudes: number[];
autoConvertText: number;
}
export interface ArkElement {
bytesData: string;
linkInfo: null;
subElementType: null;
}
export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn';
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';
export interface PicElement {
md5HexStr?: string;
filePath?: string;
fileSize: number | string;//number
picWidth: number;
picHeight: number;
fileName: string;
sourcePath: string;
original: boolean;
picType: PicType;
picSubType?: PicSubType;
fileUuid: string;
fileSubId: string;
thumbFileSize: number;
summary: string;
thumbPath: Map<number, string>;
originImageMd5?: string;
originImageUrl?: string; // http url, 没有hosthost是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
}
export enum NTGrayTipElementSubTypeV2 {
GRAYTIP_ELEMENT_SUBTYPE_AIOOP = 15,
GRAYTIP_ELEMENT_SUBTYPE_BLOCK = 14,
GRAYTIP_ELEMENT_SUBTYPE_BUDDY = 5,
GRAYTIP_ELEMENT_SUBTYPE_BUDDYNOTIFY = 9,
GRAYTIP_ELEMENT_SUBTYPE_EMOJIREPLY = 3,
GRAYTIP_ELEMENT_SUBTYPE_ESSENCE = 7,
GRAYTIP_ELEMENT_SUBTYPE_FEED = 6,
GRAYTIP_ELEMENT_SUBTYPE_FEEDCHANNELMSG = 11,
GRAYTIP_ELEMENT_SUBTYPE_FILE = 10,
GRAYTIP_ELEMENT_SUBTYPE_GROUP = 4,
GRAYTIP_ELEMENT_SUBTYPE_GROUPNOTIFY = 8,
GRAYTIP_ELEMENT_SUBTYPE_JSON = 17,
GRAYTIP_ELEMENT_SUBTYPE_LOCALMSG = 13,
GRAYTIP_ELEMENT_SUBTYPE_PROCLAMATION = 2,
GRAYTIP_ELEMENT_SUBTYPE_REVOKE = 1,
GRAYTIP_ELEMENT_SUBTYPE_UNKNOWN = 0,
GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16,
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
}
export interface GrayTipElement {
subElementType: NTGrayTipElementSubTypeV2;
revokeElement: {
operatorRole: string;
operatorUid: string;
operatorNick: string;
operatorRemark: string;
operatorMemRemark?: string;
wording: string; // 自定义的撤回提示语
};
aioOpGrayTipElement: TipAioOpGrayTipElement;
groupElement: TipGroupElement;
xmlElement: {
content: string;
templId: string;
};
jsonGrayTipElement: {
busiId?: number;
jsonStr: string;
};
}
export enum FaceType {
normal = 1, // 小黄脸
normal2 = 2, // 新小黄脸, 从faceIndex 222开始
dice = 3, // 骰子
poke = 5 // 拍一拍
}
export enum FaceIndex {
dice = 358,
RPS = 359 // 石头剪刀布
}
export interface FaceElement {
faceIndex: number;
faceType: FaceType;
faceText?: string;
packId?: string;
stickerId?: string;
sourceType?: number;
stickerType?: number;
resultId?: string;
surpriseId?: string;
randomType?: number;
}
export interface MarketFaceElement {
emojiPackageId: number;
faceName: string;
emojiId: string;
key: string;
}
export interface VideoElement {
filePath: string;
fileName: string;
videoMd5?: string;
thumbMd5?: string;
fileTime?: number; // second
thumbSize?: number; // byte
fileFormat?: viedo_type; // 2表示mp4 参考下面条目
fileSize?: string; // byte
thumbWidth?: number;
thumbHeight?: number;
busiType?: 0; //
subBusiType?: 0; // 未知
thumbPath?: Map<number, any>;
transferStatus?: 0; // 未知
progress?: 0; // 下载进度?
invalidState?: 0; // 未知
fileUuid?: string; // 可以用于下载链接?
fileSubId?: string;
fileBizId?: null;
originVideoMd5?: string;
import_rich_media_context?: null;
sourceVideoCodecFormat?: number;
}
// export enum busiType{
// public static final int CREATOR_SHARE_ADV_XWORLD = 21;
// public static final int MINI_APP_MINI_GAME = 11;
// public static final int OFFICIAL_ACCOUNT_ADV = 4;
// public static final int OFFICIAL_ACCOUNT_ADV_GAME = 8;
// public static final int OFFICIAL_ACCOUNT_ADV_SHOP = 9;
// public static final int OFFICIAL_ACCOUNT_ADV_VIP = 7;
// public static final int OFFICIAL_ACCOUNT_LAYER_MASK_ADV = 14;
// public static final int OFFICIAL_ACCOUNT_SPORT = 13;
// public static final int OFFICIAL_ACCOUNT_TIAN_QI = 10;
// public static final int PC_QQTAB_ADV = 18;
// public static final int QIQIAOBAN_SDK = 15;
// public static final int QQ_CPS = 16;
// public static final int QQ_WALLET_CPS = 17;
// public static final int QZONE_FEEDS = 0;
// public static final int QZONE_PHOTO_TAIL = 2;
// public static final int QZONE_VIDEO_LAYER = 1;
// public static final int REWARD_GIFT_ADV = 6;
// public static final int REWARD_GROUPGIFT_ADV = 12;
// public static final int REWARD_PERSONAL_ADV = 5;
// public static final int WEISEE_OFFICIAL_ACCOUNT = 3;
// public static final int X_WORLD_CREATOR_ADV = 20;
// public static final int X_WORLD_QZONE_LAYER = 22;
// public static final int X_WORLD_VIDEO_ADV = 19;
// }
// export enum CategoryBusiType {
// _KCateBusiTypeDefault = 0,
// _kCateBusiTypeFaceCluster = 1,
// _kCateBusiTypeLabelCluster = 4,
// _kCateBusiTypeMonthCluster = 16,
// _kCateBusiTypePoiCluster = 2,
// _kCateBusiTypeYearCluster = 8,
// }
export enum viedo_type {
VIDEO_FORMAT_AFS = 7,
VIDEO_FORMAT_AVI = 1,
VIDEO_FORMAT_MKV = 4,
VIDEO_FORMAT_MOD = 9,
VIDEO_FORMAT_MOV = 8,
VIDEO_FORMAT_MP4 = 2,
VIDEO_FORMAT_MTS = 11,
VIDEO_FORMAT_RM = 6,
VIDEO_FORMAT_RMVB = 5,
VIDEO_FORMAT_TS = 10,
VIDEO_FORMAT_WMV = 3,
}
export interface MarkdownElement {
content: string;
}
export interface InlineKeyboardElementRowButton {
id: string;
label: string;
visitedLabel: string;
style: 1; // 未知
type: 2; // 未知
clickLimit: 0; // 未知
unsupportTips: string;
data: string;
atBotShowChannelList: boolean;
permissionType: number;
specifyRoleIds: [];
specifyTinyids: [];
isReply: false;
anchor: 0;
enter: false;
subscribeDataTemplateIds: [];
}
export interface InlineKeyboardElement {
rows: [{
buttons: InlineKeyboardElementRowButton[]
}],
botAppid: string;
}
export interface TipAioOpGrayTipElement { // 这是什么提示来着?
operateType: number;
peerUid: string;
fromGrpCodeOfTmpChat: string;
}
export enum TipGroupElementType {
memberIncrease = 1,
kicked = 3, // 被移出群
ban = 8
}
// public final class MemberAddShowType {
// public static final int KOTHERADD = 0;
// public static final int KOTHERADDBYOTHERQRCODE = 2;
// public static final int KOTHERADDBYYOURQRCODE = 3;
// public static final int KOTHERINVITEOTHER = 5;
// public static final int KOTHERINVITEYOU = 6;
// public static final int KYOUADD = 1;
// public static final int KYOUADDBYOTHERQRCODE = 4;
// public static final int KYOUALREADYMEMBER = 8;
// public static final int KYOUINVITEOTHER = 7;
// }
export interface TipGroupElement {
type: TipGroupElementType; // 1是表示有人加入群; 自己加入群也会收到这个
role: 0; // 暂时不知
groupName: string; // 暂时获取不到
memberUid: string;
memberNick: string;
memberRemark: string;
adminUid: string;
adminNick: string;
adminRemark: string;
createGroup: null;
memberAdd?: {
showType: 1;
otherAdd: null;
otherAddByOtherQRCode: null;
otherAddByYourQRCode: null;
youAddByOtherQRCode: null;
otherInviteOther: null;
otherInviteYou: null;
youInviteOther: null
};
shutUp?: {
curTime: string;
duration: string; // 禁言时间,秒
admin: {
uid: string;
card: string;
name: string;
role: GroupMemberRole
};
member: {
uid: string
card: string;
name: string;
role: GroupMemberRole
}
};
}
export interface MultiForwardMsgElement {
xmlContent: string; // xml格式的消息内容
resId: string;
fileName: string;
}
export enum SendStatusType {
KSEND_STATUS_FAILED = 0,
KSEND_STATUS_SENDING = 1,
KSEND_STATUS_SUCCESS = 2,
KSEND_STATUS_SUCCESS_NOSEQ = 3
}
export interface RawMessage {
parentMsgPeer: Peer;
parentMsgIdList: string[];
/**
* 扩展字段,与 Ob11 msg ID 有关
*/
id?: number;
guildId: string;
msgRandom: string;
msgId: string;
/**
* 消息时间戳(秒)
*/
msgTime: string;
msgSeq: string;
msgType: NTMsgType;
subMsgType: number;
senderUid: string;
/**
* 发送者 QQ 号
*/
senderUin: string;
/**
* 群号 / 用户 UID
*/
peerUid: string;
/**
* 群号 / 用户 QQ 号
*/
peerUin: string;
/**
* 好友备注(如果是好友消息)
*/
remark?: string;
/**
* 群名(如果是群消息)
*/
peerName: string;
/**
* 发送者昵称(如果是好友消息)
*/
sendNickName: string;
/**
* 发送者好友备注(如果是群消息并且有发送者好友)
*/
sendRemarkName: string;
/**
* 发送者群名片(如果是群消息)
*/
sendMemberName?: string;
chatType: ChatType;
/**
* 消息状态,别人发的 2 是已撤回,自己发的 2 是已发送
*/
sendStatus?: SendStatusType;
/**
* 撤回时间,"0" 是没有撤回
*/
recallTime: string;
records: RawMessage[];
elements: MessageElement[];
sourceType: MsgSourceType;
isOnlineMsg: boolean;
}
export interface QueryMsgsParams {
chatInfo: Peer;
filterMsgType: [];
filterSendersUid: string[];
filterMsgFromTime: string;
filterMsgToTime: string;
pageLimit: number;
isReverseOrder: boolean;
isIncludeCurrent: boolean;
}
export interface TmpChatInfoApi {
errMsg: string;
result: number;
tmpChatInfo?: TmpChatInfo;
}
export interface TmpChatInfo {
chatType: number;
fromNick: string;
groupCode: string;
peerUid: string;
sessionType: number;
sig: string;
}
export interface MsgReqType {
peer: Peer,
byType: number,
msgId: string,
msgSeq: string,
msgTime: string,
clientSeq: string,
cnt: number,
queryOrder: boolean,
includeSelf: boolean,
includeDeleteMsg: boolean,
extraCnt: number
}
//getMsgsIncludeSelf Peer必须 byType 1
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3

View File

@@ -86,5 +86,9 @@
"6.9.59-29456": { "6.9.59-29456": {
"appid": 537249961, "appid": 537249961,
"qua": "V1_MAC_NQ_6.9.59_29456_GW_B" "qua": "V1_MAC_NQ_6.9.59_29456_GW_B"
},
"9.9.16-29927": {
"appid": 537255812,
"qua": "V1_WIN_NQ_9.9.16_29927_GW_B"
} }
} }

View File

@@ -82,5 +82,9 @@
"6.9.59-29456-arm64": { "6.9.59-29456-arm64": {
"send": "4005FE8", "send": "4005FE8",
"recv": "4008800" "recv": "4008800"
},
"9.9.16-29927-x64": {
"send": "3869C50",
"recv": "386E084"
} }
} }

View File

@@ -1,4 +1,4 @@
// work:further refactor in NapCat.Packet v2 // TODO: further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core"; import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const LikeDetail = { const LikeDetail = {

View File

@@ -1,4 +1,4 @@
// work:further refactor in NapCat.Packet v2 // TODO: further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core"; import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const BodyInner = { const BodyInner = {

View File

@@ -24,14 +24,14 @@ import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import { hostname, systemName, systemVersion } from '@/common/system'; import { hostname, systemName, systemVersion } from '@/common/system';
import { NTEventWrapper } from '@/common/event'; import { NTEventWrapper } from '@/common/event';
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities'; import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/types';
import { NapCatConfigLoader } from '@/core/helper/config'; import { NapCatConfigLoader } from '@/core/helper/config';
import os from 'node:os'; import os from 'node:os';
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners'; import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler'; import { proxiedListenerOf } from '@/common/proxy-handler';
import { NTQQPacketApi } from './apis/packet'; import { NTQQPacketApi } from './apis/packet';
export * from './wrapper'; export * from './wrapper';
export * from './entities'; export * from './types';
export * from './services'; export * from './services';
export * from './listeners'; export * from './listeners';
@@ -120,7 +120,7 @@ export class NapCatCore {
if (!fs.existsSync(this.NapCatTempPath)) { if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true }); fs.mkdirSync(this.NapCatTempPath, { recursive: true });
} }
//遍历this.apis[i].initApi 如果存在该函数进行async 调用 //遍历this.apis[i].initApi 如果存在该函数进行async 调用
for (const apiKey in this.apis) { for (const apiKey in this.apis) {
const api = this.apis[apiKey as keyof StableNTApiWrapper]; const api = this.apis[apiKey as keyof StableNTApiWrapper];
if ('initApi' in api && typeof api.initApi === 'function') { if ('initApi' in api && typeof api.initApi === 'function') {
@@ -210,7 +210,7 @@ export class NapCatCore {
}); });
}; };
groupListener.onMemberListChange = (arg) => { groupListener.onMemberListChange = (arg) => {
// work:应该加一个内部自己维护的成员变动callback用于判断成员变化通知 // TODO: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0]; const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) { if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!; const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;

View File

@@ -1,4 +1,4 @@
import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities'; import { BuddyCategoryType, FriendRequestNotify } from '@/core/types';
export type OnBuddyChangeParams = BuddyCategoryType[]; export type OnBuddyChangeParams = BuddyCategoryType[];

View File

@@ -1,4 +1,4 @@
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities'; import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/types';
export class NodeIKernelGroupListener { export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): any { } onGroupListInited(listEmpty: boolean): any { }

View File

@@ -1,4 +1,4 @@
import { ChatType, KickedOffLineInfo, RawMessage } from '@/core/entities'; import { ChatType, KickedOffLineInfo, RawMessage } from '@/core/types';
import { CommonFileInfo } from '@/core'; import { CommonFileInfo } from '@/core';
export interface OnRichMediaDownloadCompleteParams { export interface OnRichMediaDownloadCompleteParams {
@@ -29,50 +29,10 @@ export interface GroupFileInfoUpdateParamType {
retMsg: string; retMsg: string;
clientWording: string; clientWording: string;
isEnd: boolean; isEnd: boolean;
item: Array<{ item: Array<GroupFileInfoUpdateItem>;
peerId: string; allFileCount: number;
type: number; nextIndex: number;
folderInfo?: { reqId: number;
folderId: string;
parentFolderId: string;
folderName: string;
createTime: number;
modifyTime: number;
createUin: string;
creatorName: string;
totalFileCount: number;
modifyUin: string;
modifyName: string;
usedSpace: string;
},
fileInfo?: {
fileModelId: string;
fileId: string;
fileName: string;
fileSize: string;
busId: number;
uploadedSize: string;
uploadTime: number;
deadTime: number;
modifyTime: number;
downloadTimes: number;
sha: string;
sha3: string;
md5: string;
uploaderLocalPath: string;
uploaderName: string;
uploaderUin: string;
parentFolderId: string;
localPath: string;
transStatus: number;
transType: number;
elementId: string;
isFolder: boolean;
},
}>;
allFileCount: string;
nextIndex: string;
reqId: string;
} }
// { // {
@@ -83,6 +43,49 @@ export interface GroupFileInfoUpdateParamType {
// fromNick: '拾xxxx, // fromNick: '拾xxxx,
// sig: '0x' // sig: '0x'
// } // }
export interface GroupFileInfoUpdateItem {
peerId: string;
type: number;
folderInfo?: {
folderId: string;
parentFolderId: string;
folderName: string;
createTime: number;
modifyTime: number;
createUin: string;
creatorName: string;
totalFileCount: number;
modifyUin: string;
modifyName: string;
usedSpace: string;
},
fileInfo?: {
fileModelId: string;
fileId: string;
fileName: string;
fileSize: string;
busId: number;
uploadedSize: string;
uploadTime: number;
deadTime: number;
modifyTime: number;
downloadTimes: number;
sha: string;
sha3: string;
md5: string;
uploaderLocalPath: string;
uploaderName: string;
uploaderUin: string;
parentFolderId: string;
localPath: string;
transStatus: number;
transType: number;
elementId: string;
isFolder: boolean;
},
}
export interface TempOnRecvParams { export interface TempOnRecvParams {
sessionType: number,//1 sessionType: number,//1
chatType: ChatType,//100 chatType: ChatType,//100

View File

@@ -1,4 +1,4 @@
import { User, UserDetailInfoListenerArg } from '@/core/entities'; import { User, UserDetailInfoListenerArg } from '@/core/types';
export class NodeIKernelProfileListener { export class NodeIKernelProfileListener {
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void { onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {

View File

@@ -24,7 +24,7 @@ export class PacketClientSession {
return this.context.operation; return this.context.operation;
} }
// work: global message element adapter (? // TODO: global message element adapter (?
get msgConverter() { get msgConverter() {
return this.context.msgConverter; return this.context.msgConverter;
} }

View File

@@ -1,7 +1,7 @@
import { LogLevel, LogWrapper } from "@/common/log"; import { LogLevel, LogWrapper } from "@/common/log";
import { PacketContext } from "@/core/packet/context/packetContext"; import { PacketContext } from "@/core/packet/context/packetContext";
// work: check bind? // TODO: check bind?
export class PacketLogger { export class PacketLogger {
private readonly napLogger: LogWrapper; private readonly napLogger: LogWrapper;

View File

@@ -78,8 +78,12 @@ export class PacketHighwayContext {
ip: int32ip2str(addr.ip), ip: int32ip2str(addr.ip),
port: addr.port port: addr.port
}); });
this.hwClient.changeServer(int32ip2str(addr.ip), addr.port);
} }
} }
if (this.sig.serverAddr.length === 0) {
this.logger.warn('[Highway PrepareUpload] server addr is empty!');
}
} }
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> { async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {

View File

@@ -76,7 +76,7 @@ export type rawMsgWithSendMsg = {
msg: PacketSendMsgElement[] msg: PacketSendMsgElement[]
} }
// work:make it become adapter? // TODO: make it become adapter?
export class PacketMsgConverter { export class PacketMsgConverter {
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters { private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
return SupportedElementTypes.includes(type); return SupportedElementTypes.includes(type);
@@ -116,7 +116,7 @@ export class PacketMsgConverter {
[ElementType.MARKDOWN]: (element) => { [ElementType.MARKDOWN]: (element) => {
return new PacketMsgMarkDownElement(element as SendMarkdownElement); return new PacketMsgMarkDownElement(element as SendMarkdownElement);
}, },
// work:check this logic, move it in arkElement? // TODO: check this logic, move it in arkElement?
[ElementType.STRUCTLONGMSG]: (element) => { [ElementType.STRUCTLONGMSG]: (element) => {
return new PacketMultiMsgElement(element as SendStructLongMsgElement); return new PacketMultiMsgElement(element as SendStructLongMsgElement);
} }

View File

@@ -14,7 +14,7 @@ import {
GroupFileExtra GroupFileExtra
} from "@/core/packet/transformer/proto"; } from "@/core/packet/transformer/proto";
import { import {
AtType, NTMsgAtType,
PicType, PicType,
SendArkElement, SendArkElement,
SendFaceElement, SendFaceElement,
@@ -32,7 +32,7 @@ import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message"; import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
// raw <-> packet // raw <-> packet
// work:SendStructLongMsgElement // TODO: SendStructLongMsgElement
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> { export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
protected constructor(rawElement: T) { protected constructor(rawElement: T) {
} }
@@ -82,7 +82,7 @@ export class PacketMsgAtElement extends PacketMsgTextElement {
constructor(element: SendTextElement) { constructor(element: SendTextElement) {
super(element); super(element);
this.targetUid = element.textElement.atNtUid; this.targetUid = element.textElement.atNtUid;
this.atAll = element.textElement.atType === AtType.atAll; this.atAll = element.textElement.atType === NTMsgAtType.ATTYPEALL;
} }
buildElement(): NapProtoEncodeStructType<typeof Elem>[] { buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
@@ -118,7 +118,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
this.targetUin = +(element.replyElement.senderUin ?? 0); this.targetUin = +(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? ''; this.targetUid = element.replyElement.senderUidStr ?? '';
this.time = +(element.replyElement.replyMsgTime ?? 0); this.time = +(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // work:in replyElement.sourceMsgTextElems this.elems = []; // TODO: in replyElement.sourceMsgTextElems
} }
get isGroupReply(): boolean { get isGroupReply(): boolean {
@@ -131,7 +131,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq], origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq],
senderUin: BigInt(this.targetUin), senderUin: BigInt(this.targetUin),
time: this.time, time: this.time,
elems: [], // work:in replyElement.sourceMsgTextElems elems: [], // TODO: in replyElement.sourceMsgTextElems
pbReserve: { pbReserve: {
messageId: this.messageId, messageId: this.messageId,
}, },
@@ -346,9 +346,9 @@ export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
constructor(element: SendPttElement) { constructor(element: SendPttElement) {
super(element); super(element);
this.filePath = element.pttElement.filePath; this.filePath = element.pttElement.filePath;
this.fileSize = +element.pttElement.fileSize; // work:cc this.fileSize = +element.pttElement.fileSize; // TODO: cc
this.fileMd5 = element.pttElement.md5HexStr; this.fileMd5 = element.pttElement.md5HexStr;
this.fileDuration = Math.round(element.pttElement.duration); // work:cc this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc
} }
get valid(): boolean { get valid(): boolean {

View File

@@ -25,7 +25,7 @@ class DownloadOfflineFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0
return OidbBase.build(0xE37, 800, body, false, false); return OidbBase.build(0xE37, 800, body, false, false);
} }
// work:check // TODO:check
parse(data: Buffer) { parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body; const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37Response).decode(oidbBody); return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37Response).decode(oidbBody);

View File

@@ -16,7 +16,7 @@ class FetchSessionKey extends PacketTransformer<typeof proto.HttpConn0x6ff_501Re
field4: 1, field4: 1,
field6: 3, field6: 3,
serviceTypes: [1, 5, 10, 21], serviceTypes: [1, 5, 10, 21],
// tgt: "", // work:do we really need tgt? seems not // tgt: "", // TODO: do we really need tgt? seems not
field9: 2, field9: 2,
field10: 9, field10: 9,
field11: 8, field11: 8,

View File

@@ -16,7 +16,7 @@ class UploadGroupFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0x6D6
appId: 4, appId: 4,
busId: 102, busId: 102,
entrance: 6, entrance: 6,
targetDirectory: '/', // work: targetDirectory: '/', // TODO:
fileName: file.fileName, fileName: file.fileName,
localDirectory: `/${file.fileName}`, localDirectory: `/${file.fileName}`,
fileSize: BigInt(file.fileSize), fileSize: BigInt(file.fileSize),

View File

@@ -40,7 +40,7 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
fileName: img.name, fileName: img.name,
type: { type: {
type: 1, type: 1,
picFormat: img.picType, //work:extend NapCat imgType /cc @MliKiowa picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0, videoFormat: 0,
voiceFormat: 0, voiceFormat: 0,
}, },
@@ -59,7 +59,7 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
extBizInfo: { extBizInfo: {
pic: { pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'), bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // work: textSummary: "Nya~", // TODO:
}, },
video: { video: {
bytesPbReserve: Buffer.alloc(0), bytesPbReserve: Buffer.alloc(0),

View File

@@ -40,7 +40,7 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
fileName: img.name, fileName: img.name,
type: { type: {
type: 1, type: 1,
picFormat: img.picType, //work:extend NapCat imgType /cc @MliKiowa picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0, videoFormat: 0,
voiceFormat: 0, voiceFormat: 0,
}, },
@@ -59,7 +59,7 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
extBizInfo: { extBizInfo: {
pic: { pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'), bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // work: textSummary: "Nya~", // TODO:
}, },
video: { video: {
bytesPbReserve: Buffer.alloc(0), bytesPbReserve: Buffer.alloc(0),

View File

@@ -1,6 +1,6 @@
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
import { NodeIKernelBuddyListener } from '@/core/listeners'; import { NodeIKernelBuddyListener } from '@/core/listeners';
import { BuddyListReqType } from '../entities/user'; import { BuddyListReqType } from '../types/user';
export interface NodeIKernelBuddyService { export interface NodeIKernelBuddyService {
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & { getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {

View File

@@ -4,11 +4,11 @@ import {
GroupExtParam, GroupExtParam,
GroupInfoSource, GroupInfoSource,
GroupMember, GroupMember,
GroupMemberRole, NTGroupMemberRole,
GroupNotifyMsgType, GroupNotifyMsgType,
GroupRequestOperateTypes, NTGroupRequestOperateTypes,
KickMemberV2Req, KickMemberV2Req,
} from '@/core/entities'; } from '@/core/types';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
@@ -137,7 +137,7 @@ export interface NodeIKernelGroupService {
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>; kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>;
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void; modifyMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void;
modifyMemberCardName(groupCode: string, uid: string, cardName: string): void; modifyMemberCardName(groupCode: string, uid: string, cardName: string): void;
@@ -198,9 +198,9 @@ export interface NodeIKernelGroupService {
operateSysNotify( operateSysNotify(
doubt: boolean, doubt: boolean,
operateMsg: { operateMsg: {
operateType: GroupRequestOperateTypes, // 2 拒绝 operateType: NTGroupRequestOperateTypes,
targetMsg: { targetMsg: {
seq: string, // 通知序列号 seq: string,
type: GroupNotifyMsgType, type: GroupNotifyMsgType,
groupCode: string, groupCode: string,
postscript: string postscript: string

View File

@@ -1,7 +1,7 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities'; import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/types';
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener'; import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../entities/msg'; import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../types/msg';
export interface NodeIKernelMsgService { export interface NodeIKernelMsgService {

View File

@@ -1,7 +1,7 @@
import { ChatType, Peer } from '../entities'; import { ChatType, Peer } from '@/core/types';
import { NodeIKernelRecentContactListener } from '../listeners/NodeIKernelRecentContactListener'; import { NodeIKernelRecentContactListener } from '../listeners/NodeIKernelRecentContactListener';
import { GeneralCallResult } from './common'; import { GeneralCallResult } from './common';
import { FSABRecentContactParams } from '../entities/contact'; import { FSABRecentContactParams } from '../types/contact';
export interface NodeIKernelRecentContactService { export interface NodeIKernelRecentContactService {
setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments

View File

@@ -1,4 +1,4 @@
import { GetFileListParam, MessageElement, Peer } from '../entities'; import { GetFileListParam, MessageElement, Peer } from '@/core/types';
import { GeneralCallResult } from './common'; import { GeneralCallResult } from './common';
export enum UrlFileDownloadType { export enum UrlFileDownloadType {

View File

@@ -1,4 +1,4 @@
import { ChatType } from '../entities'; import { ChatType } from '@/core/types';
import { GeneralCallResult } from './common'; import { GeneralCallResult } from './common';
export interface NodeIKernelSearchService { export interface NodeIKernelSearchService {

View File

@@ -1,4 +1,4 @@
import { MessageElement, Peer } from '../entities'; import { MessageElement, Peer } from '@/core/types';
export interface NodeIkernelTestPerformanceService { export interface NodeIkernelTestPerformanceService {

68
src/core/types/cache.ts Normal file
View File

@@ -0,0 +1,68 @@
import { ChatType } from './msg';
/**
* 聊天缓存列表
*/
export interface ChatCacheList {
pageCount: number; // 页数
infos: ChatCacheListItem[]; // 聊天缓存项列表
}
/**
* 聊天缓存列表项
*/
export interface ChatCacheListItem {
chatType: ChatType; // 聊天类型
basicChatCacheInfo: ChatCacheListItemBasic; // 基本聊天缓存信息
guildChatCacheInfo: unknown[]; // 公会聊天缓存信息
}
/**
* 基本聊天缓存信息
*/
export interface ChatCacheListItemBasic {
chatSize: string; // 聊天大小
chatTime: string; // 聊天时间
uid: string; // 用户ID
uin: string; // 用户号码
remarkName: string; // 备注名
nickName: string; // 昵称
chatType?: ChatType; // 聊天类型(可选)
isChecked?: boolean; // 是否已检查(可选)
}
/**
* 缓存文件类型枚举
*/
export enum CacheFileType {
IMAGE = 0, // 图片
VIDEO = 1, // 视频
AUDIO = 2, // 音频
DOCUMENT = 3, // 文档
OTHER = 4, // 其他
}
/**
* 缓存文件列表
*/
export interface CacheFileList {
infos: CacheFileListItem[]; // 缓存文件项列表
}
/**
* 缓存文件列表项
*/
export interface CacheFileListItem {
fileSize: string; // 文件大小
fileTime: string; // 文件时间
fileKey: string; // 文件键
elementId: string; // 元素ID
elementIdStr: string; // 元素ID字符串
fileType: CacheFileType; // 文件类型
path: string; // 路径
fileName: string; // 文件名
senderId: string; // 发送者ID
previewPath: string; // 预览路径
senderName: string; // 发送者名称
isChecked?: boolean; // 是否已检查(可选)
}

View File

@@ -0,0 +1,2 @@
export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn';
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn';

View File

@@ -1,4 +1,3 @@
export interface FSABRecentContactParams { export interface FSABRecentContactParams {
anchorPointContact: { anchorPointContact: {
contactId: string; contactId: string;

348
src/core/types/element.ts Normal file
View File

@@ -0,0 +1,348 @@
import { ElementType, FaceType, MessageElement, NTGrayTipElementSubTypeV2, PicSubType, PicType, TipAioOpGrayTipElement, TipGroupElement, NTVideoType } from "./msg";
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
export interface SendElementBase<ET extends ElementType> {
elementType: ET;
elementId: string;
extBufForUI?: string;
}
type ElementBase<
K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
> = {
[P in K]:
S[P] extends Array<infer U>
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
: S[P] extends keyof NonNullable<ElementFullBase[P]>
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
: NonNullable<ElementFullBase[P]>;
};
export interface TextElement {
content: string;
atType: number;
atUid: string;
atTinyId: string;
atNtUid: string;
}
export interface FaceElement {
faceIndex: number;
faceType: FaceType;
faceText?: string;
packId?: string;
stickerId?: string;
sourceType?: number;
stickerType?: number;
resultId?: string;
surpriseId?: string;
randomType?: number;
}
export interface GrayTipElement {
subElementType: NTGrayTipElementSubTypeV2;
revokeElement: {
operatorRole: string;
operatorUid: string;
operatorNick: string;
operatorRemark: string;
operatorMemRemark?: string;
wording: string; // 自定义的撤回提示语
};
aioOpGrayTipElement: TipAioOpGrayTipElement;
groupElement: TipGroupElement;
xmlElement: {
content: string;
templId: string;
};
jsonGrayTipElement: {
busiId?: number;
jsonStr: string;
};
}
export interface ArkElement {
bytesData: string;
linkInfo: null;
subElementType: null;
}
export interface MarketFaceElement {
emojiPackageId: number;
faceName: string;
emojiId: string;
key: string;
}
export interface VideoElement {
filePath: string;
fileName: string;
videoMd5?: string;
thumbMd5?: string;
fileTime?: number; // second
thumbSize?: number; // byte
fileFormat?: NTVideoType; // 2表示mp4 参考下面条目
fileSize?: string; // byte
thumbWidth?: number;
thumbHeight?: number;
busiType?: 0; //
subBusiType?: 0; // 未知
thumbPath?: Map<number, any>;
transferStatus?: 0; // 未知
progress?: 0; // 下载进度?
invalidState?: 0; // 未知
fileUuid?: string; // 可以用于下载链接?
fileSubId?: string;
fileBizId?: null;
originVideoMd5?: string;
import_rich_media_context?: null;
sourceVideoCodecFormat?: number;
}
export interface PicElement {
md5HexStr?: string;
filePath?: string;
fileSize: number | string;//number
picWidth: number;
picHeight: number;
fileName: string;
sourcePath: string;
original: boolean;
picType: PicType;
picSubType?: PicSubType;
fileUuid: string;
fileSubId: string;
thumbFileSize: number;
summary: string;
thumbPath: Map<number, string>;
originImageMd5?: string;
originImageUrl?: string;
}
export interface InlineKeyboardButton {
id: string;
label: string;
visitedLabel: string;
unsupportTips: string;
data: string;
specifyRoleIds: string[];
specifyTinyids: string[];
style: number;
type: number;
clickLimit: number;
atBotShowChannelList: boolean;
permissionType: number;
}
// 非element
interface InlineKeyboardRow {
buttons: InlineKeyboardButton[];
}
// 非element
interface TofuElementContent {
color: string;
tittle: string;
}
export interface ActionBarElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface RecommendedMsgElement {
rows: InlineKeyboardRow[];
botAppid: string;
}
export interface TofuRecordElement {
type: number;
busiid: string;
busiuuid: string;
descriptionContent: string;
contentlist: TofuElementContent[],
background: string;
icon: string;
uinlist: string[],
uidlist: string[],
busiExtra: string;
updateTime: string;
dependedmsgid: string;
msgtime: string;
onscreennotify: boolean;
}
export interface FileElement {
fileMd5?: string;
fileName: string;
filePath: string;
fileSize: string;
picHeight?: number;
picWidth?: number;
folderId?: string;
picThumbPath?: Map<number, string>;
file10MMd5?: string;
fileSha?: string;
fileSha3?: string;
fileUuid?: string;
fileSubId?: string;
thumbFileSize?: number;
fileBizId?: number;
}
export interface ShareLocationElement {
text: string;
ext: string;
}
export interface StructLongMsgElement {
xmlContent: string;
resId: string;
}
export interface ReplyElement {
sourceMsgIdInRecords?: string;
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUidStr?: string;
replyMsgTime?: string;
replyMsgClientSeq?: string;
}
export interface CalendarElement {
summary: string;
msg: string;
expireTimeMs: string;
schemaType: number;
schema: string;
}
export interface GiphyElement {
id: string;
isClip: boolean;
width: number;
height: number;
}
export interface AvRecordElement {
type: number;
time: string;
text: string;
mainType: number;
hasRead: boolean;
extraType: number;
}
// 非element
interface YoloUserInfo {
uid: string;
result: number;
rank: number;
bizId: string;
}
export interface YoloGameResultElement {
UserInfo: YoloUserInfo[];
}
export interface FaceBubbleElement {
faceCount: number;
faceSummary: string;
faceFlag: number;
content: string;
oldVersionStr: string;
faceType: number;
others: string;
yellowFaceInfo: {
index: number;
buf: string;
compatibleText: string;
text: string;
};
}
export interface TaskTopMsgElement {
msgTitle: string;
msgSummary: string;
iconUrl: string;
topMsgType: number;
}
export interface PttElement {
canConvert2Text: boolean;
duration: number;
fileBizId: null;
fileId: number;
fileName: string;
filePath: string;
fileSize: string;
fileSubId: string;
fileUuid: string; // FileId
formatType: number; // Todo 已定义 但是未替换
invalidState: number;
md5HexStr: string;
playState: number;
progress: number; //进度
text: string;
transferStatus: number;
translateStatus: number;
voiceChangeType: number;
voiceType: number;
waveAmplitudes: number[];
autoConvertText: number;
}
export type SendRecommendedMsgElement = SendElementBase<ElementType.RECOMMENDEDMSG> & ElementBase<'recommendedMsgElement'>;
export type SendTaskTopMsgElement = SendElementBase<ElementType.TASKTOPMSG> & ElementBase<'taskTopMsgElement'>;
export type SendTofuRecordElement = SendElementBase<ElementType.TOFURECORD> & ElementBase<'tofuRecordElement'>;
export type SendFaceBubbleElement = SendElementBase<ElementType.FACEBUBBLE> & ElementBase<'faceBubbleElement'>;
export type SendAvRecordElement = SendElementBase<ElementType.AVRECORD> & ElementBase<'avRecordElement'>;
export type SendInlineKeyboardElement = SendElementBase<ElementType.INLINEKEYBOARD> & ElementBase<'inlineKeyboardElement'>;
export type SendYoloGameResultElement = SendElementBase<ElementType.YOLOGAMERESULT> & ElementBase<'yoloGameResultElement'>;
export type SendGiphyElement = SendElementBase<ElementType.GIPHY> & ElementBase<'giphyElement'>;
export type SendWalletElement = SendElementBase<ElementType.UNKNOWN> & ElementBase<'walletElement'>;
export type SendCalendarElement = SendElementBase<ElementType.CALENDAR> & ElementBase<'calendarElement'>;
export type SendLiveGiftElement = SendElementBase<ElementType.LIVEGIFT> & ElementBase<'liveGiftElement'>;
export type SendTextElement = SendElementBase<ElementType.TEXT> & ElementBase<'textElement'>;
export type SendReplyElement = SendElementBase<ElementType.REPLY> & ElementBase<'replyElement'>;
export type SendFaceElement = SendElementBase<ElementType.FACE> & ElementBase<'faceElement'>;
export type SendMarketFaceElement = SendElementBase<ElementType.MFACE> & ElementBase<'marketFaceElement'>;
export type SendStructLongMsgElement = SendElementBase<ElementType.STRUCTLONGMSG> & ElementBase<'structLongMsgElement'>;
export type SendPicElement = SendElementBase<ElementType.PIC> & ElementBase<'picElement'>;
export type SendPttElement = SendElementBase<ElementType.PTT> & ElementBase<'pttElement', {
pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType',
'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText']
}>;
export type SendFileElement = SendElementBase<ElementType.FILE> & ElementBase<'fileElement'>;
export type SendVideoElement = SendElementBase<ElementType.VIDEO> & ElementBase<'videoElement'>;
export type SendArkElement = SendElementBase<ElementType.ARK> & ElementBase<'arkElement'>;
export type SendMarkdownElement = SendElementBase<ElementType.MARKDOWN> & ElementBase<'markdownElement'>;
export type SendShareLocationElement = SendElementBase<ElementType.SHARELOCATION> & ElementBase<'shareLocationElement'>;
export type SendMessageElement = SendTextElement | SendPttElement |
SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement |
SendVideoElement | SendArkElement | SendMarkdownElement | SendShareLocationElement;

View File

@@ -1,12 +1,13 @@
import { QQLevel, Sex } from './user'; import { QQLevel, NTSex } from './user';
export interface KickMemberInfo { export interface KickMemberInfo {
optFlag: number, optFlag: number;
optOperate: number, optOperate: number;
optMemberUid: string, optMemberUid: string;
optBytesMsg: string, optBytesMsg: string;
} }
//getGroupDetailInfo GroupCode,GroupInfoSource
// 获取群详细信息的来源类型
export enum GroupInfoSource { export enum GroupInfoSource {
KUNSPECIFIED, KUNSPECIFIED,
KBIGDATACARD, KBIGDATACARD,
@@ -16,6 +17,7 @@ export enum GroupInfoSource {
KRECENTCONTACT, KRECENTCONTACT,
KMOREPANEL KMOREPANEL
} }
export interface GroupExt0xEF0InfoFilter { export interface GroupExt0xEF0InfoFilter {
bindGuildId: number; bindGuildId: number;
blacklistExpireTime: number; blacklistExpireTime: number;
@@ -52,18 +54,20 @@ export interface GroupExt0xEF0InfoFilter {
} }
export interface KickMemberV2Req { export interface KickMemberV2Req {
groupCode: string, groupCode: string;
kickFlag: number, kickFlag: number;
kickList: Array<KickMemberInfo>, kickList: Array<KickMemberInfo>;
kickListUids: Array<string>, kickListUids: Array<string>;
kickMsg: string kickMsg: string;
} }
// 数据来源类型
export enum DataSource { export enum DataSource {
LOCAL, LOCAL,
REMOTE REMOTE
} }
// 群列表更新类型
export enum GroupListUpdateType { export enum GroupListUpdateType {
REFRESHALL, REFRESHALL,
GETALL, GETALL,
@@ -80,42 +84,42 @@ export interface GroupMemberCache {
} }
export interface Group { export interface Group {
groupCode: string, groupCode: string;
createTime?: string,//高版本才有 createTime?: string;
maxMember: number, maxMember: number;
memberCount: number, memberCount: number;
groupName: string, groupName: string;
groupStatus: number, groupStatus: number;
memberRole: number, memberRole: number;
isTop: boolean, isTop: boolean;
toppedTimestamp: string, toppedTimestamp: string;
privilegeFlag: number, //65760 privilegeFlag: number;
isConf: boolean, isConf: boolean;
hasModifyConfGroupFace: boolean, hasModifyConfGroupFace: boolean;
hasModifyConfGroupName: boolean, hasModifyConfGroupName: boolean;
remarkName: string, remarkName: string;
hasMemo: boolean, hasMemo: boolean;
groupShutupExpireTime: string, //"0", groupShutupExpireTime: string;
personShutupExpireTime: string, //"0", personShutupExpireTime: string;
discussToGroupUin: string, //"0", discussToGroupUin: string;
discussToGroupMaxMsgSeq: number, discussToGroupMaxMsgSeq: number;
discussToGroupTime: number, discussToGroupTime: number;
groupFlagExt: number, //1073938496, groupFlagExt: number;
authGroupType: number, //0, authGroupType: number;
groupCreditLevel: number, //0, groupCreditLevel: number;
groupFlagExt3: number, //0, groupFlagExt3: number;
groupOwnerId: { groupOwnerId: {
memberUin: string, //"0", memberUin: string;
memberUid: string, //"u_fbf8N7aeuZEnUiJAbQ9R8Q" memberUid: string;
} };
} }
export enum NTGroupMemberRole {
export enum GroupMemberRole { KUNSPECIFIED = 0,
normal = 2, KSTRANGER = 1,
admin = 3, KMEMBER = 2,
owner = 4 KADMIN = 3,
KOWNER = 4
} }
export interface GroupMember { export interface GroupMember {
memberRealLevel: number | undefined; memberRealLevel: number | undefined;
memberSpecialTitle?: string; memberSpecialTitle?: string;
@@ -126,15 +130,15 @@ export interface GroupMember {
nick: string; nick: string;
qid: string; qid: string;
remark: string; remark: string;
role: GroupMemberRole; // 群主:4, 管理员:3群员:2 role: NTGroupMemberRole;
shutUpTime: number; // 禁言时间,单位是什么暂时不清楚 shutUpTime: number; // 禁言时间(S)
uid: string; // 加密的字符串 uid: string;
uin: string; // QQ号 uin: string;
isRobot: boolean; isRobot: boolean;
sex?: Sex; sex?: NTSex;
age?: number; age?: number;
qqLevel?: QQLevel; qqLevel?: QQLevel;
isChangeRole: boolean; isChangeRole: boolean;
joinTime: string; joinTime: string;
lastSpeakTime: string; lastSpeakTime: string;
} }

View File

@@ -5,4 +5,6 @@ export * from './notify';
export * from './cache'; export * from './cache';
export * from './system'; export * from './system';
export * from './webapi'; export * from './webapi';
export * from './sign'; export * from './sign';
export * from './element';
export * from './constant';

535
src/core/types/msg.ts Normal file
View File

@@ -0,0 +1,535 @@
import { NTGroupMemberRole } from '@/core';
import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBubbleElement, FaceElement, FileElement, GiphyElement, GrayTipElement, MarketFaceElement, PicElement, PttElement, RecommendedMsgElement, ReplyElement, ShareLocationElement, StructLongMsgElement, TaskTopMsgElement, TextElement, TofuRecordElement, VideoElement, YoloGameResultElement } from './element';
/**
* 表示对等方的信息
*/
export interface Peer {
chatType: ChatType; // 聊天类型
peerUid: string; // 对等方的唯一标识符
guildId?: string; // 可选的频道ID
}
/**
* 表示被踢下线的信息
*/
export interface KickedOffLineInfo {
appId: number; // 应用ID
instanceId: number; // 实例ID
sameDevice: boolean; // 是否为同一设备
tipsDesc: string; // 提示描述
tipsTitle: string; // 提示标题
kickedType: number; // 被踢类型
securityKickedType: number; // 安全踢出类型
}
/**
* 获取文件列表的参数
*/
export interface GetFileListParam {
sortType: number;
fileCount: number;
startIndex: number;
sortOrder: number;
showOnlinedocFolder: number;
folderId?: string;
}
/**
* 消息元素类型枚举
*/
export enum ElementType {
UNKNOWN = 0,
TEXT = 1,
PIC = 2,
FILE = 3,
PTT = 4,
VIDEO = 5,
FACE = 6,
REPLY = 7,
GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等
WALLET = 9,
ARK = 10,
MFACE = 11,
LIVEGIFT = 12,
STRUCTLONGMSG = 13,
MARKDOWN = 14,
GIPHY = 15,
MULTIFORWARD = 16,
INLINEKEYBOARD = 17,
INTEXTGIFT = 18,
CALENDAR = 19,
YOLOGAMERESULT = 20,
AVRECORD = 21,
FEED = 22,
TOFURECORD = 23,
ACEBUBBLE = 24,
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
}
/**
* 消息类型枚举
*/
export enum NTMsgType {
KMSGTYPEARKSTRUCT = 11,
KMSGTYPEFACEBUBBLE = 24,
KMSGTYPEFILE = 3,
KMSGTYPEGIFT = 14,
KMSGTYPEGIPHY = 13,
KMSGTYPEGRAYTIPS = 5,
KMSGTYPEMIX = 2,
KMSGTYPEMULTIMSGFORWARD = 8,
KMSGTYPENULL = 1,
KMSGTYPEONLINEFILE = 21,
KMSGTYPEONLINEFOLDER = 27,
KMSGTYPEPROLOGUE = 29,
KMSGTYPEPTT = 6,
KMSGTYPEREPLY = 9,
KMSGTYPESHARELOCATION = 25,
KMSGTYPESTRUCT = 4,
KMSGTYPESTRUCTLONGMSG = 12,
KMSGTYPETEXTGIFT = 15,
KMSGTYPEUNKNOWN = 0,
KMSGTYPEVIDEO = 7,
KMSGTYPEWALLET = 10
}
/**
* 图片类型枚举
*/
export enum PicType {
NEWPIC_APNG = 2001,
NEWPIC_BMP = 1005,
NEWPIC_GIF = 2000,
NEWPIC_JPEG = 1000,
NEWPIC_PNG = 1001,
NEWPIC_PROGERSSIV_JPEG = 1003,
NEWPIC_SHARPP = 1004,
NEWPIC_WEBP = 1002
}
/**
* 图片子类型枚举
*/
export enum PicSubType {
KNORMAL = 0,
KCUSTOM = 1,
KHOT = 2,
KDIPPERCHART = 3,
KSMART = 4,
KSPACE = 5,
KUNKNOW = 6,
KRELATED = 7
}
/**
* 消息@类型枚举
*/
export enum NTMsgAtType {
ATTYPEALL = 1,
ATTYPECATEGORY = 512,
ATTYPECHANNEL = 16,
ATTYPEME = 4,
ATTYPEONE = 2,
ATTYPEONLINE = 64,
ATTYPEROLE = 8,
ATTYPESUMMON = 32,
ATTYPESUMMONONLINE = 128,
ATTYPESUMMONROLE = 256,
ATTYPEUNKNOWN = 0
}
/**
* 消息元素接口
*/
export interface MessageElement {
elementType: ElementType,
elementId: string,
extBufForUI?: string, //"0x",
textElement?: TextElement;
faceElement?: FaceElement,
marketFaceElement?: MarketFaceElement,
replyElement?: ReplyElement,
picElement?: PicElement,
pttElement?: PttElement,
videoElement?: VideoElement,
grayTipElement?: GrayTipElement,
arkElement?: ArkElement,
fileElement?: FileElement,
liveGiftElement?: null,
markdownElement?: MarkdownElement,
structLongMsgElement?: StructLongMsgElement,
multiForwardMsgElement?: MultiForwardMsgElement,
giphyElement?: GiphyElement,
walletElement?: null,
inlineKeyboardElement?: InlineKeyboardElement,
textGiftElement?: null,//????
calendarElement?: CalendarElement,
yoloGameResultElement?: YoloGameResultElement,
avRecordElement?: AvRecordElement,
structMsgElement?: null,
faceBubbleElement?: FaceBubbleElement,
shareLocationElement?: ShareLocationElement,
tofuRecordElement?: TofuRecordElement,
taskTopMsgElement?: TaskTopMsgElement,
recommendedMsgElement?: RecommendedMsgElement,
actionBarElement?: ActionBarElement
}
/**
* 消息来源类型枚举
*/
export enum MsgSourceType {
K_DOWN_SOURCETYPE_AIOINNER = 1,
K_DOWN_SOURCETYPE_BIGSCREEN = 2,
K_DOWN_SOURCETYPE_HISTORY = 3,
K_DOWN_SOURCETYPE_UNKNOWN = 0
}
/**
* 聊天类型枚举
*/
export enum ChatType {
KCHATTYPEADELIE = 42,
KCHATTYPEBUDDYNOTIFY = 5,
KCHATTYPEC2C = 1,
KCHATTYPECIRCLE = 113,
KCHATTYPEDATALINE = 8,
KCHATTYPEDATALINEMQQ = 134,
KCHATTYPEDISC = 3,
KCHATTYPEFAV = 41,
KCHATTYPEGAMEMESSAGE = 105,
KCHATTYPEGAMEMESSAGEFOLDER = 116,
KCHATTYPEGROUP = 2,
KCHATTYPEGROUPBLESS = 133,
KCHATTYPEGROUPGUILD = 9,
KCHATTYPEGROUPHELPER = 7,
KCHATTYPEGROUPNOTIFY = 6,
KCHATTYPEGUILD = 4,
KCHATTYPEGUILDMETA = 16,
KCHATTYPEMATCHFRIEND = 104,
KCHATTYPEMATCHFRIENDFOLDER = 109,
KCHATTYPENEARBY = 106,
KCHATTYPENEARBYASSISTANT = 107,
KCHATTYPENEARBYFOLDER = 110,
KCHATTYPENEARBYHELLOFOLDER = 112,
KCHATTYPENEARBYINTERACT = 108,
KCHATTYPEQQNOTIFY = 132,
KCHATTYPERELATEACCOUNT = 131,
KCHATTYPESERVICEASSISTANT = 118,
KCHATTYPESERVICEASSISTANTSUB = 201,
KCHATTYPESQUAREPUBLIC = 115,
KCHATTYPESUBSCRIBEFOLDER = 30,
KCHATTYPETEMPADDRESSBOOK = 111,
KCHATTYPETEMPBUSSINESSCRM = 102,
KCHATTYPETEMPC2CFROMGROUP = 100,
KCHATTYPETEMPC2CFROMUNKNOWN = 99,
KCHATTYPETEMPFRIENDVERIFY = 101,
KCHATTYPETEMPNEARBYPRO = 119,
KCHATTYPETEMPPUBLICACCOUNT = 103,
KCHATTYPETEMPWPA = 117,
KCHATTYPEUNKNOWN = 0,
KCHATTYPEWEIYUN = 40,
}
/**
* 灰色提示元素子类型枚举
*/
export enum NTGrayTipElementSubTypeV2 {
GRAYTIP_ELEMENT_SUBTYPE_AIOOP = 15,
GRAYTIP_ELEMENT_SUBTYPE_BLOCK = 14,
GRAYTIP_ELEMENT_SUBTYPE_BUDDY = 5,
GRAYTIP_ELEMENT_SUBTYPE_BUDDYNOTIFY = 9,
GRAYTIP_ELEMENT_SUBTYPE_EMOJIREPLY = 3,
GRAYTIP_ELEMENT_SUBTYPE_ESSENCE = 7,
GRAYTIP_ELEMENT_SUBTYPE_FEED = 6,
GRAYTIP_ELEMENT_SUBTYPE_FEEDCHANNELMSG = 11,
GRAYTIP_ELEMENT_SUBTYPE_FILE = 10,
GRAYTIP_ELEMENT_SUBTYPE_GROUP = 4,
GRAYTIP_ELEMENT_SUBTYPE_GROUPNOTIFY = 8,
GRAYTIP_ELEMENT_SUBTYPE_JSON = 17,
GRAYTIP_ELEMENT_SUBTYPE_LOCALMSG = 13,
GRAYTIP_ELEMENT_SUBTYPE_PROCLAMATION = 2,
GRAYTIP_ELEMENT_SUBTYPE_REVOKE = 1,
GRAYTIP_ELEMENT_SUBTYPE_UNKNOWN = 0,
GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16,
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
}
/**
* 表情类型枚举
*/
export enum FaceType {
normal = 1, // 小黄脸
normal2 = 2, // 新小黄脸
dice = 3, // 骰子
poke = 5 // 拍一拍
}
/**
* Poke 类型枚举
*/
export enum PokeType {
POKE_TYPE_APPROVE = 3,
POKE_TYPE_GIVING_HEART = 2,
POKE_TYPE_GREAT_MOVE = 6,
POKE_TYPE_HEART_BREAK = 4,
POKE_TYPE_HI_TOGETHER = 5,
POKE_TYPE_POKE = 1,
POKE_TYPE_POKE_OLD = 0,
POKE_TYPE_VAS_POKE = 126,
}
/**
* 表情索引枚举
*/
export enum FaceIndex {
dice = 358,
rps = 359
}
/**
* 视频类型枚举
*/
export enum NTVideoType {
VIDEO_FORMAT_AFS = 7,
VIDEO_FORMAT_AVI = 1,
VIDEO_FORMAT_MKV = 4,
VIDEO_FORMAT_MOD = 9,
VIDEO_FORMAT_MOV = 8,
VIDEO_FORMAT_MP4 = 2,
VIDEO_FORMAT_MTS = 11,
VIDEO_FORMAT_RM = 6,
VIDEO_FORMAT_RMVB = 5,
VIDEO_FORMAT_TS = 10,
VIDEO_FORMAT_WMV = 3,
}
/**
* Markdown元素接口
*/
export interface MarkdownElement {
content: string;
}
/**
* 内联键盘按钮接口
*/
export interface InlineKeyboardElementRowButton {
id: string;
label: string;
visitedLabel: string;
style: 1; // 未知
type: 2; // 未知
clickLimit: 0; // 未知
unsupportTips: string;
data: string;
atBotShowChannelList: boolean;
permissionType: number;
specifyRoleIds: [];
specifyTinyids: [];
isReply: false;
anchor: 0;
enter: false;
subscribeDataTemplateIds: [];
}
/**
* 内联键盘元素接口
*/
export interface InlineKeyboardElement {
rows: [{
buttons: InlineKeyboardElementRowButton[]
}],
botAppid: string;
}
/**
* Aio操作灰色提示元素接口
*/
export interface TipAioOpGrayTipElement {
operateType: number;
peerUid: string;
fromGrpCodeOfTmpChat: string;
}
/**
* 群提示元素类型枚举
*/
export enum TipGroupElementType {
KUNKNOWN = 0,
KMEMBERADD = 1,
KDISBANDED = 2,
KQUITTE = 3,
KCREATED = 4,
KGROUPNAMEMODIFIED = 5,
KBLOCK = 6,
KUNBLOCK = 7,
KSHUTUP = 8,
KBERECYCLED = 9,
KDISBANDORBERECYCLED = 10
}
/**
* 群加入ShowType
*/
export enum MemberAddShowType {
K_OTHER_ADD = 0,
K_OTHER_ADD_BY_OTHER_QRCODE = 2,
K_OTHER_ADD_BY_YOUR_QRCODE = 3,
K_OTHER_INVITE_OTHER = 5,
K_OTHER_INVITE_YOU = 6,
K_YOU_ADD = 1,
K_YOU_ADD_BY_OTHER_QRCODE = 4,
K_YOU_ALREADY_MEMBER = 8,
K_YOU_INVITE_OTHER = 7,
}
/**
* 群提示元素接口
*/
export interface TipGroupElement {
type: TipGroupElementType;
role: 0;
groupName: string;
memberUid: string;
memberNick: string;
memberRemark: string;
adminUid: string;
adminNick: string;
adminRemark: string;
createGroup: null;
memberAdd?: {
showType: MemberAddShowType;
otherAdd: null;
otherAddByOtherQRCode: null;
otherAddByYourQRCode: null;
youAddByOtherQRCode: null;
otherInviteOther: null;
otherInviteYou: null;
youInviteOther: null
};
shutUp?: {
curTime: string;
duration: string; // 禁言时间,秒
admin: {
uid: string;
card: string;
name: string;
role: NTGroupMemberRole
};
member: {
uid: string
card: string;
name: string;
role: NTGroupMemberRole
}
};
}
/**
* 多条转发消息元素接口
*/
export interface MultiForwardMsgElement {
xmlContent: string; // xml格式的消息内容
resId: string;
fileName: string;
}
/**
* 发送状态类型枚举
*/
export enum SendStatusType {
KSEND_STATUS_FAILED = 0,
KSEND_STATUS_SENDING = 1,
KSEND_STATUS_SUCCESS = 2,
KSEND_STATUS_SUCCESS_NOSEQ = 3
}
/**
* 原始消息接口
*/
export interface RawMessage {
parentMsgPeer: Peer; // 父消息的Peer
parentMsgIdList: string[];// 父消息 ID 列表
id?: number;// 扩展字段,与 Ob11 msg ID 有关
guildId: string;// 频道ID
msgRandom: string;// 消息ID相关
msgId: string;// 雪花ID
msgTime: string;// 消息时间戳
msgSeq: string;// 消息序列号
msgType: NTMsgType;// 消息类型
subMsgType: number;// 子消息类型
senderUid: string;// 发送者 UID
senderUin: string;// 发送者 QQ 号
peerUid: string;// 群号 / 用户 UID
peerUin: string;// 群号 / 用户 QQ 号
remark?: string;// 备注
peerName: string;// Peer名称
sendNickName: string;// 发送者昵称
sendRemarkName: string;// 发送者好友备注
sendMemberName?: string;// 发送者群名片(如果是群消息)
chatType: ChatType;// 会话类型
sendStatus?: SendStatusType;// 消息状态
recallTime: string;// 撤回时间,"0" 是没有撤回
records: RawMessage[];// 消息记录
elements: MessageElement[];// 消息元素
sourceType: MsgSourceType;// 消息来源类型
isOnlineMsg: boolean;// 是否为在线消息
}
/**
* 查询消息参数接口
*/
export interface QueryMsgsParams {
chatInfo: Peer;
filterMsgType: [];
filterSendersUid: string[];
filterMsgFromTime: string;
filterMsgToTime: string;
pageLimit: number;
isReverseOrder: boolean;
isIncludeCurrent: boolean;
}
/**
* 临时聊天信息API接口
*/
export interface TmpChatInfoApi {
errMsg: string;
result: number;
tmpChatInfo?: TmpChatInfo;
}
/**
* 临时聊天信息接口
*/
export interface TmpChatInfo {
chatType: number;
fromNick: string;
groupCode: string;
peerUid: string;
sessionType: number;
sig: string;
}
/**
* 消息请求类型接口
*/
export interface MsgReqType {
peer: Peer,
byType: number,
msgId: string,
msgSeq: string,
msgTime: string,
clientSeq: string,
cnt: number,
queryOrder: boolean,
includeSelf: boolean,
includeDeleteMsg: boolean,
extraCnt: number
}

View File

@@ -107,9 +107,12 @@ export interface GroupNotify {
warningTips: string; warningTips: string;
} }
export enum GroupRequestOperateTypes { export enum NTGroupRequestOperateTypes {
approve = 1, KUNSPECIFIED = 0,
reject = 2 KAGREE = 1,
KREFUSE = 2,
KIGNORE = 3,
KDELETE = 4
} }
export enum BuddyReqType { export enum BuddyReqType {
@@ -135,7 +138,7 @@ export interface FriendRequest {
isDecide: boolean; isDecide: boolean;
friendUid: string; friendUid: string;
reqType: BuddyReqType, reqType: BuddyReqType,
reqTime: string; // 时间戳; reqTime: string; // 时间戳
extWords: string; // 申请人填写的验证消息 extWords: string; // 申请人填写的验证消息
isUnread: boolean; isUnread: boolean;
friendNick: string; friendNick: string;

View File

@@ -1,16 +1,20 @@
export enum Sex { // 性别枚举
male = 1, export enum NTSex {
female = 2, GENDER_UNKOWN = 0,
unknown = 255, GENDER_MALE = 1,
GENDER_FEMALE = 2,
GENDER_PRIVACY = 255,
} }
// 好友分类类型
export interface BuddyCategoryType { export interface BuddyCategoryType {
categoryId: number; categoryId: number;
categroyName: string; categoryName: string;
categroyMbCount: number; categoryMbCount: number;
buddyList: User[]; buddyList: User[];
} }
// 核心信息
export interface CoreInfo { export interface CoreInfo {
uid: string; uid: string;
uin: string; uin: string;
@@ -18,6 +22,7 @@ export interface CoreInfo {
remark: string; remark: string;
} }
// 基本信息
export interface BaseInfo { export interface BaseInfo {
qid: string; qid: string;
longNick: string; longNick: string;
@@ -33,20 +38,24 @@ export interface BaseInfo {
richBuffer: string; richBuffer: string;
} }
// 音乐信息
interface MusicInfo { interface MusicInfo {
buf: string; buf: string;
} }
// 视频业务信息
interface VideoBizInfo { interface VideoBizInfo {
cid: string; cid: string;
tvUrl: string; tvUrl: string;
synchType: string; synchType: string;
} }
// 视频信息
interface VideoInfo { interface VideoInfo {
name: string; name: string;
} }
// 扩展在线业务信息
interface ExtOnlineBusinessInfo { interface ExtOnlineBusinessInfo {
buf: string; buf: string;
customStatus: any; customStatus: any;
@@ -54,10 +63,12 @@ interface ExtOnlineBusinessInfo {
videoInfo: VideoInfo; videoInfo: VideoInfo;
} }
// 扩展缓冲区
interface ExtBuffer { interface ExtBuffer {
buf: string; buf: string;
} }
// 用户状态
interface UserStatus { interface UserStatus {
uid: string; uid: string;
uin: string; uin: string;
@@ -79,12 +90,14 @@ interface UserStatus {
extBuffer: ExtBuffer; extBuffer: ExtBuffer;
} }
// 特权图标
interface PrivilegeIcon { interface PrivilegeIcon {
jumpUrl: string; jumpUrl: string;
openIconList: any[]; openIconList: any[];
closeIconList: any[]; closeIconList: any[];
} }
// 增值服务信息
interface VasInfo { interface VasInfo {
vipFlag: boolean; vipFlag: boolean;
yearVipFlag: boolean; yearVipFlag: boolean;
@@ -117,6 +130,7 @@ interface VasInfo {
privilegeIcon: PrivilegeIcon; privilegeIcon: PrivilegeIcon;
} }
// 关系标志
interface RelationFlags { interface RelationFlags {
topTime: string; topTime: string;
isBlock: boolean; isBlock: boolean;
@@ -134,7 +148,7 @@ interface RelationFlags {
isHidePrivilegeIcon: number; isHidePrivilegeIcon: number;
} }
// 通用扩展信息
interface CommonExt { interface CommonExt {
constellation: number; constellation: number;
shengXiao: number; shengXiao: number;
@@ -153,20 +167,26 @@ interface CommonExt {
labels: any[]; labels: any[];
qqLevel: QQLevel; qqLevel: QQLevel;
} }
// 好友列表请求类型枚举
export enum BuddyListReqType { export enum BuddyListReqType {
KNOMAL, KNOMAL,
KLETTER KLETTER
} }
// 图片信息
interface Pic { interface Pic {
picId: string; picId: string;
picTime: number; picTime: number;
picUrlMap: Record<string, string>; picUrlMap: Record<string, string>;
} }
// 照片墙
interface PhotoWall { interface PhotoWall {
picList: Pic[]; picList: Pic[];
} }
// 简单信息
export interface SimpleInfo { export interface SimpleInfo {
uid?: string; uid?: string;
uin?: string; uin?: string;
@@ -179,8 +199,10 @@ export interface SimpleInfo {
intimate: any; intimate: any;
} }
// 好友类型
export type FriendV2 = SimpleInfo; export type FriendV2 = SimpleInfo;
// 自身状态信息
export interface SelfStatusInfo { export interface SelfStatusInfo {
uid: string; uid: string;
status: number; status: number;
@@ -192,6 +214,7 @@ export interface SelfStatusInfo {
setTime: string; setTime: string;
} }
// 用户详细信息监听参数
export interface UserDetailInfoListenerArg { export interface UserDetailInfoListenerArg {
uid: string; uid: string;
uin: string; uin: string;
@@ -200,14 +223,16 @@ export interface UserDetailInfoListenerArg {
photoWall: PhotoWall; photoWall: PhotoWall;
} }
// 修改个人资料参数
export interface ModifyProfileParams { export interface ModifyProfileParams {
nick: string, nick: string;
longNick: string, longNick: string;
sex: Sex, sex: NTSex;
birthday: { birthday_year: string, birthday_month: string, birthday_day: string }, birthday: { birthday_year: string, birthday_month: string, birthday_day: string };
location: any//undefined location: any;
} }
// 好友资料点赞请求
export interface BuddyProfileLikeReq { export interface BuddyProfileLikeReq {
friendUids: string[]; friendUids: string[];
basic: number; basic: number;
@@ -219,6 +244,7 @@ export interface BuddyProfileLikeReq {
limit?: number; limit?: number;
} }
// QQ等级信息
export interface QQLevel { export interface QQLevel {
crownNum: number; crownNum: number;
sunNum: number; sunNum: number;
@@ -226,14 +252,15 @@ export interface QQLevel {
starNum: number; starNum: number;
} }
// 用户信息
export interface User { export interface User {
uid: string; // 加密的字符串 uid: string;
uin: string; // QQ号 uin: string;
nick: string; nick: string;
avatarUrl?: string; avatarUrl?: string;
longNick?: string; // 签名 longNick?: string;
remark?: string; remark?: string;
sex?: Sex; sex?: NTSex;
age?: number; age?: number;
qqLevel?: QQLevel; qqLevel?: QQLevel;
qid?: string; qid?: string;
@@ -244,7 +271,7 @@ export interface User {
constellation?: number; constellation?: number;
shengXiao?: number; shengXiao?: number;
kBloodType?: number; kBloodType?: number;
homeTown?: string; //"0-0-0"; homeTown?: string;
makeFriendCareer?: number; makeFriendCareer?: number;
pos?: string; pos?: string;
eMail?: string; eMail?: string;
@@ -266,10 +293,10 @@ export interface User {
privilegeIcon?: { privilegeIcon?: {
jumpUrl: string; jumpUrl: string;
openIconList: unknown[]; openIconList: unknown[];
closeIconList: unknown[] closeIconList: unknown[];
}; };
photoWall?: { photoWall?: {
picList: unknown[] picList: unknown[];
}; };
vipFlag?: boolean; vipFlag?: boolean;
yearVipFlag?: boolean; yearVipFlag?: boolean;
@@ -285,34 +312,40 @@ export interface User {
pendantId?: string; pendantId?: string;
} }
// 自身信息
export interface SelfInfo extends User { export interface SelfInfo extends User {
online?: boolean; online?: boolean;
} }
// 好友类型
export type Friend = User; export type Friend = User;
// 本来是 Friend extends User 现在用不到 // 业务键枚举
export enum BizKey { export enum BizKey {
KPRIVILEGEICON, KPRIVILEGEICON,
KPHOTOWALL KPHOTOWALL
} }
// 根据UIN获取用户详细信息
export interface UserDetailInfoByUin { export interface UserDetailInfoByUin {
result: number, result: number;
errMsg: string, errMsg: string;
detail: { detail: {
uid: string, uid: string;
uin: string, uin: string;
simpleInfo: SimpleInfo, simpleInfo: SimpleInfo;
commonExt: CommonExt, commonExt: CommonExt;
photoWall: null photoWall: null;
} };
} }
// 用户详细信息来源枚举
export enum UserDetailSource { export enum UserDetailSource {
KDB, KDB,
KSERVER KSERVER
} }
// 个人资料业务类型枚举
export enum ProfileBizType { export enum ProfileBizType {
KALL, KALL,
KBASEEXTEND, KBASEEXTEND,

View File

@@ -150,7 +150,7 @@ export interface NodeIQQNTWrapperSession {
nodeIKernelSessionListener: NodeIKernelSessionListener, nodeIKernelSessionListener: NodeIKernelSessionListener,
): void; ): void;
startNT(n: 0): void; startNT(session: number): void;
startNT(): void; startNT(): void;

View File

@@ -3,7 +3,7 @@ import { LogWrapper } from '@/common/log';
import { proxiedListenerOf } from '@/common/proxy-handler'; import { proxiedListenerOf } from '@/common/proxy-handler';
import { QQBasicInfoWrapper } from '@/common/qq-basic-info'; import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from '@/core'; import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from '@/core';
import { SelfInfo } from '@/core/entities'; import { SelfInfo } from '@/core/types';
import { NodeIKernelLoginListener } from '@/core/listeners'; import { NodeIKernelLoginListener } from '@/core/listeners';
import { NodeIKernelLoginService } from '@/core/services'; import { NodeIKernelLoginService } from '@/core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper'; import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';

View File

@@ -1,32 +0,0 @@
import { OB11Return } from '../types';
import { isNull } from '@/common/helper';
export class OB11Response {
static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {
return {
status: status,
retcode: retcode,
data: data,
message: message,
wording: message,
echo: null,
};
}
static ok<T>(data: T, echo: any = null) {
const res = OB11Response.res<T>(data, 'ok', 0);
if (!isNull(echo)) {
res.echo = echo;
}
return res;
}
static error(err: string, retcode: number, echo: any = null) {
const res = OB11Response.res(null, 'failed', retcode, err);
if (!isNull(echo)) {
res.echo = echo;
}
return res;
}
}

View File

@@ -1,15 +1,38 @@
import { ActionName, BaseCheckResult } from './types'; import { ActionName, BaseCheckResult } from './router';
import { OB11Response } from './OB11Response';
import { OB11Return } from '@/onebot/types';
import Ajv, { ErrorObject, ValidateFunction } from 'ajv'; import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { isNull } from '@/common/helper';
import { NapCatOneBot11Adapter, OB11Return } from '@/onebot';
import { NapCatOneBot11Adapter } from '@/onebot'; export class OB11Response {
private static createResponse<T>(data: T, status: string, retcode: number, message: string = '', echo: any = null): OB11Return<T> {
return {
status,
retcode,
data,
message,
wording: message,
echo,
};
}
abstract class BaseAction<PayloadType, ReturnDataType> { static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {
return this.createResponse(data, status, retcode, message);
}
static ok<T>(data: T, echo: any = null): OB11Return<T> {
return this.createResponse(data, 'ok', 0, '', echo);
}
static error(err: string, retcode: number, echo: any = null): OB11Return<null> {
return this.createResponse(null, 'failed', retcode, err, echo);
}
}
export abstract class OneBotAction<PayloadType, ReturnDataType> {
actionName: ActionName = ActionName.Unknown; actionName: ActionName = ActionName.Unknown;
core: NapCatCore; core: NapCatCore;
private validate: undefined | ValidateFunction<any> = undefined; private validate: ValidateFunction<any> | undefined = undefined;
payloadSchema: any = undefined; payloadSchema: any = undefined;
obContext: NapCatOneBot11Adapter; obContext: NapCatOneBot11Adapter;
@@ -24,17 +47,13 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
} }
if (this.validate && !this.validate(payload)) { if (this.validate && !this.validate(payload)) {
const errors = this.validate.errors as ErrorObject[]; const errors = this.validate.errors as ErrorObject[];
const errorMessages: string[] = errors.map((e) => { const errorMessages = errors.map(e => `Key: ${e.instancePath.split('/').slice(1).join('.')}, Message: ${e.message}`);
return `Key: ${e.instancePath.split('/').slice(1).join('.')}, Message: ${e.message}`;
});
return { return {
valid: false, valid: false,
message: errorMessages.join('\n') ?? '未知错误', message: errorMessages.join('\n') ?? '未知错误',
}; };
} }
return { return { valid: true };
valid: true,
};
} }
public async handle(payload: PayloadType, adaptername: string): Promise<OB11Return<ReturnDataType | null>> { public async handle(payload: PayloadType, adaptername: string): Promise<OB11Return<ReturnDataType | null>> {
@@ -46,7 +65,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
const resData = await this._handle(payload, adaptername); const resData = await this._handle(payload, adaptername);
return OB11Response.ok(resData); return OB11Response.ok(resData);
} catch (e: any) { } catch (e: any) {
this.core.context.logger.logError.bind(this.core.context.logger)('发生错误', e); this.core.context.logger.logError('发生错误', e);
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200); return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
} }
} }
@@ -60,12 +79,10 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
const resData = await this._handle(payload, adaptername); const resData = await this._handle(payload, adaptername);
return OB11Response.ok(resData, echo); return OB11Response.ok(resData, echo);
} catch (e: any) { } catch (e: any) {
this.core.context.logger.logError.bind(this.core.context.logger)('发生错误', e); this.core.context.logger.logError('发生错误', e);
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo); return OB11Response.error(e.toString() || e.stack?.toString(), 1200, echo);
} }
} }
abstract _handle(payload: PayloadType, adaptername: string): PromiseLike<ReturnDataType>; abstract _handle(payload: PayloadType, adaptername: string): PromiseLike<ReturnDataType>;
} }
export default BaseAction;

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -13,7 +13,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class CreateCollection extends BaseAction<Payload, any> { export class CreateCollection extends OneBotAction<Payload, any> {
actionName = ActionName.CreateCollection; actionName = ActionName.CreateCollection;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,6 +1,6 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -11,7 +11,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class FetchCustomFace extends BaseAction<Payload, string[]> { export class FetchCustomFace extends OneBotAction<Payload, string[]> {
actionName = ActionName.FetchCustomFace; actionName = ActionName.FetchCustomFace;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,7 +1,7 @@
//getMsgEmojiLikesList //getMsgEmojiLikesList
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { MessageUnique } from '@/common/message-unique'; import { MessageUnique } from '@/common/message-unique';
const SchemaData = { const SchemaData = {
@@ -19,7 +19,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class FetchEmojiLike extends BaseAction<Payload, any> { export class FetchEmojiLike extends OneBotAction<Payload, any> {
actionName = ActionName.FetchEmojiLike; actionName = ActionName.FetchEmojiLike;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
export class FetchUserProfileLike extends BaseAction<{ qq: number }, any> { export class FetchUserProfileLike extends OneBotAction<{ qq: number }, any> {
actionName = ActionName.FetchUserProfileLike; actionName = ActionName.FetchUserProfileLike;
async _handle(payload: { qq: number }) { async _handle(payload: { qq: number }) {

View File

@@ -1,4 +1,4 @@
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
import { AIVoiceChatType } from "@/core/packet/entities/aiChat"; import { AIVoiceChatType } from "@/core/packet/entities/aiChat";

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -13,7 +13,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class GetCollectionList extends BaseAction<Payload, any> { export class GetCollectionList extends OneBotAction<Payload, any> {
actionName = ActionName.GetCollectionList; actionName = ActionName.GetCollectionList;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,14 +1,14 @@
import { OB11Entities } from '@/onebot/entities'; import { OB11Construct } from '@/onebot/helper/data';
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
export class GetFriendWithCategory extends BaseAction<void, any> { export class GetFriendWithCategory extends OneBotAction<void, any> {
actionName = ActionName.GetFriendsWithCategory; actionName = ActionName.GetFriendsWithCategory;
async _handle(payload: void) { async _handle(payload: void) {
return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({ return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({
...category, ...category,
buddyList: OB11Entities.friendsV2(category.buddyList), buddyList: OB11Construct.friends(category.buddyList),
})); }));
} }
} }

View File

@@ -1,6 +1,6 @@
import { GroupNotifyMsgStatus } from '@/core'; import { GroupNotifyMsgStatus } from '@/core';
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
interface OB11GroupRequestNotify { interface OB11GroupRequestNotify {
group_id: number, group_id: number,
@@ -8,7 +8,7 @@ interface OB11GroupRequestNotify {
flag: string flag: string
} }
export default class GetGroupAddRequest extends BaseAction<null, OB11GroupRequestNotify[] | null> { export default class GetGroupAddRequest extends OneBotAction<null, OB11GroupRequestNotify[] | null> {
actionName = ActionName.GetGroupIgnoreAddRequest; actionName = ActionName.GetGroupIgnoreAddRequest;
async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> { async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> {

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -12,7 +12,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class GetGroupInfoEx extends BaseAction<Payload, any> { export class GetGroupInfoEx extends OneBotAction<Payload, any> {
actionName = ActionName.GetGroupInfoEx; actionName = ActionName.GetGroupInfoEx;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,4 +1,4 @@
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/utils/helper/miniAppHelper"; import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/utils/helper/miniAppHelper";

View File

@@ -1,12 +1,12 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
interface Payload { interface Payload {
start: number, start: number,
count: number count: number
} }
export class GetProfileLike extends BaseAction<Payload, any> { export class GetProfileLike extends OneBotAction<Payload, any> {
actionName = ActionName.GetProfileLike; actionName = ActionName.GetProfileLike;
async _handle(payload: Payload) { async _handle(payload: Payload) {

View File

@@ -1,4 +1,4 @@
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
export class GetRobotUinRange extends BaseAction<void, Array<any>> { export class GetRobotUinRange extends OneBotAction<void, Array<any>> {
actionName = ActionName.GetRobotUinRange; actionName = ActionName.GetRobotUinRange;
async _handle(payload: void) { async _handle(payload: void) {

View File

@@ -1,4 +1,4 @@
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
// no_cache get时传字符串 // no_cache get时传字符串

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { checkFileReceived, uri2local } from '@/common/file'; import { checkFileExist, uri2local } from '@/common/file';
import fs from 'fs'; import fs from 'fs';
const SchemaData = { const SchemaData = {
@@ -14,7 +14,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class OCRImage extends BaseAction<Payload, any> { export class OCRImage extends OneBotAction<Payload, any> {
actionName = ActionName.OCRImage; actionName = ActionName.OCRImage;
payloadSchema = SchemaData; payloadSchema = SchemaData;
@@ -24,7 +24,7 @@ export class OCRImage extends BaseAction<Payload, any> {
throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`); throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`);
} }
if (path) { if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断 await checkFileExist(path, 5000); // 避免崩溃
const ret = await this.core.apis.SystemApi.ocrImage(path); const ret = await this.core.apis.SystemApi.ocrImage(path);
fs.unlink(path, () => { }); fs.unlink(path, () => { });

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { GetPacketStatusDepends } from '../packet/GetPacketStatus';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -12,7 +12,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class SetGroupSign extends BaseAction<Payload, any> { export class SetGroupSign extends GetPacketStatusDepends<Payload, any> {
actionName = ActionName.SetGroupSign; actionName = ActionName.SetGroupSign;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,6 +1,6 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { ChatType } from '@/core'; import { ChatType } from '@/core';
const SchemaData = { const SchemaData = {
@@ -14,7 +14,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class SetInputStatus extends BaseAction<Payload, any> { export class SetInputStatus extends OneBotAction<Payload, any> {
actionName = ActionName.SetInputStatus; actionName = ActionName.SetInputStatus;
async _handle(payload: Payload) { async _handle(payload: Payload) {

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -12,7 +12,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class SetLongNick extends BaseAction<Payload, any> { export class SetLongNick extends OneBotAction<Payload, any> {
actionName = ActionName.SetLongNick; actionName = ActionName.SetLongNick;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
// 设置在线状态 // 设置在线状态
@@ -15,7 +15,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class SetOnlineStatus extends BaseAction<Payload, null> { export class SetOnlineStatus extends OneBotAction<Payload, null> {
actionName = ActionName.SetOnlineStatus; actionName = ActionName.SetOnlineStatus;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -1,13 +1,13 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName, BaseCheckResult } from '../types'; import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { checkFileReceived, uri2local } from '@/common/file'; import { checkFileExist, uri2local } from '@/common/file';
interface Payload { interface Payload {
file: string; file: string;
} }
export default class SetAvatar extends BaseAction<Payload, null> { export default class SetAvatar extends OneBotAction<Payload, null> {
actionName = ActionName.SetQQAvatar; actionName = ActionName.SetQQAvatar;
// 用不着复杂检测 // 用不着复杂检测
@@ -29,7 +29,7 @@ export default class SetAvatar extends BaseAction<Payload, null> {
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`); throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
} }
if (path) { if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断 await checkFileExist(path, 5000);// 避免崩溃
const ret = await this.core.apis.UserApi.setQQAvatar(path); const ret = await this.core.apis.UserApi.setQQAvatar(path);
fs.unlink(path, () => { fs.unlink(path, () => {
}); });

View File

@@ -1,4 +1,4 @@
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
const SchemaData = { const SchemaData = {

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -14,7 +14,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class SharePeer extends BaseAction<Payload, any> { export class SharePeer extends OneBotAction<Payload, any> {
actionName = ActionName.SharePeer; actionName = ActionName.SharePeer;
payloadSchema = SchemaData; payloadSchema = SchemaData;
@@ -37,7 +37,7 @@ const SchemaDataGroupEx = {
type PayloadGroupEx = FromSchema<typeof SchemaDataGroupEx>; type PayloadGroupEx = FromSchema<typeof SchemaDataGroupEx>;
export class ShareGroupEx extends BaseAction<PayloadGroupEx, any> { export class ShareGroupEx extends OneBotAction<PayloadGroupEx, any> {
actionName = ActionName.ShareGroupEx; actionName = ActionName.ShareGroupEx;
payloadSchema = SchemaDataGroupEx; payloadSchema = SchemaDataGroupEx;

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '../types'; import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
@@ -15,7 +15,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class TranslateEnWordToZn extends BaseAction<Payload, Array<any> | null> { export class TranslateEnWordToZn extends OneBotAction<Payload, Array<any> | null> {
actionName = ActionName.TranslateEnWordToZn; actionName = ActionName.TranslateEnWordToZn;
payloadSchema = SchemaData; payloadSchema = SchemaData;

Some files were not shown because too many files have changed in this diff Show More