mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
169 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
902fe907bd | ||
![]() |
bbb4ad7d95 | ||
![]() |
24bc9f35b2 | ||
![]() |
52c68a3bfb | ||
![]() |
d982bcdad5 | ||
![]() |
b8165242f0 | ||
![]() |
7ce95bca04 | ||
![]() |
cd212abd5f | ||
![]() |
e5b063accb | ||
![]() |
eeef5409dc | ||
![]() |
2bf8d8f791 | ||
![]() |
56e62392a6 | ||
![]() |
2ecf04c78c | ||
![]() |
a19358da5b | ||
![]() |
a5d4998933 | ||
![]() |
8edbe54456 | ||
![]() |
e898915d01 | ||
![]() |
b2075130d9 | ||
![]() |
02e39b5714 | ||
![]() |
de64b03054 | ||
![]() |
fa70eec3d8 | ||
![]() |
583ec10c7c | ||
![]() |
38a098c77d | ||
![]() |
d17674d06e | ||
![]() |
0b839258aa | ||
![]() |
50e207cf6f | ||
![]() |
5d2d8c7123 | ||
![]() |
23702f412c | ||
![]() |
31e94792c4 | ||
![]() |
249afdce81 | ||
![]() |
ee8f381341 | ||
![]() |
83f3df76cd | ||
![]() |
16195ca52b | ||
![]() |
d5f492775e | ||
![]() |
1f273a8799 | ||
![]() |
f44f6fd1e9 | ||
![]() |
21ca13789e | ||
![]() |
648faedca6 | ||
![]() |
3a6748ae37 | ||
![]() |
4d4b1ad26c | ||
![]() |
e42fbea918 | ||
![]() |
48b648b0fb | ||
![]() |
68e86b07c7 | ||
![]() |
12cb500818 | ||
![]() |
9ffaab178a | ||
![]() |
d4fbbd6711 | ||
![]() |
ded53cd348 | ||
![]() |
be9e80c87b | ||
![]() |
e9fe6f28cc | ||
![]() |
0b8bf739e9 | ||
![]() |
0222664db8 | ||
![]() |
a88792e452 | ||
![]() |
ad45400742 | ||
![]() |
53e5ba03be | ||
![]() |
b587d6b91d | ||
![]() |
5e750d4ee9 | ||
![]() |
50fb32f81c | ||
![]() |
6c46cdd947 | ||
![]() |
372452fbee | ||
![]() |
417ef5d335 | ||
![]() |
9c534f8afd | ||
![]() |
ecd426bb80 | ||
![]() |
f74ef273de | ||
![]() |
f913e0b027 | ||
![]() |
f7268c30ca | ||
![]() |
0f5ef03d63 | ||
![]() |
745276d0f0 | ||
![]() |
2e108a4bd6 | ||
![]() |
666da80ef5 | ||
![]() |
cc73104d62 | ||
![]() |
3c10b82bab | ||
![]() |
9a65dae6a2 | ||
![]() |
f26cd8cdc9 | ||
![]() |
eeec905df0 | ||
![]() |
0c6aac7f66 | ||
![]() |
86d22db141 | ||
![]() |
48a5d0eef3 | ||
![]() |
bda174bed4 | ||
![]() |
caf98b8655 | ||
![]() |
c9833c5988 | ||
![]() |
55ef7e529e | ||
![]() |
9b04ddcefd | ||
![]() |
6dc4f38581 | ||
![]() |
93ce8bfb85 | ||
![]() |
e7d138448a | ||
![]() |
02c4a468cb | ||
![]() |
d392e653e1 | ||
![]() |
e8faa09f1d | ||
![]() |
e80ed3b33e | ||
![]() |
41a346e1cf | ||
![]() |
5e19fc112a | ||
![]() |
2f7aff2b56 | ||
![]() |
ccb0e1fb4f | ||
![]() |
d4163c913a | ||
![]() |
8087ba0e4a | ||
![]() |
6700523b61 | ||
![]() |
49f1c3f9ba | ||
![]() |
575ab4f1d1 | ||
![]() |
3658547731 | ||
![]() |
eb6590e9e2 | ||
![]() |
83f28795f2 | ||
![]() |
e98bfaac11 | ||
![]() |
4f4bd3c6e0 | ||
![]() |
bd1faccaa8 | ||
![]() |
25751b8149 | ||
![]() |
e34b60315c | ||
![]() |
046afc0c23 | ||
![]() |
2f61ba7f25 | ||
![]() |
8981f12b1a | ||
![]() |
34e96b1089 | ||
![]() |
41db435ef5 | ||
![]() |
b525fa81bb | ||
![]() |
6382b29da8 | ||
![]() |
8bc0403139 | ||
![]() |
9f261e78c3 | ||
![]() |
15d9390ee4 | ||
![]() |
572b8809a5 | ||
![]() |
623799c049 | ||
![]() |
4271acc6ab | ||
![]() |
609e83a824 | ||
![]() |
e98910c9ff | ||
![]() |
c432799580 | ||
![]() |
fa87f7c8c3 | ||
![]() |
4a44062814 | ||
![]() |
fe0bda11d3 | ||
![]() |
1ec1040e43 | ||
![]() |
e44595334a | ||
![]() |
f40de023b0 | ||
![]() |
9799d02ad2 | ||
![]() |
bec88fee04 | ||
![]() |
1a94e20691 | ||
![]() |
3690307d0b | ||
![]() |
2d5b4bc90a | ||
![]() |
cc93ed3567 | ||
![]() |
dce4988767 | ||
![]() |
5c81b60b58 | ||
![]() |
a668bfbc13 | ||
![]() |
bc0fc96b9b | ||
![]() |
ae14692d5b | ||
![]() |
d445dc6644 | ||
![]() |
db3d435402 | ||
![]() |
7ee48f1443 | ||
![]() |
a54f30acc1 | ||
![]() |
75e7bc7275 | ||
![]() |
f1b2c8b1cf | ||
![]() |
50079e7a96 | ||
![]() |
6d37868ae8 | ||
![]() |
543961e980 | ||
![]() |
1e2c76bb47 | ||
![]() |
ddc0ed066d | ||
![]() |
6708903c65 | ||
![]() |
5ee0afb604 | ||
![]() |
9b20e9db29 | ||
![]() |
74b4d9bf49 | ||
![]() |
89f7892681 | ||
![]() |
f83bf197d2 | ||
![]() |
5bcc130dd7 | ||
![]() |
4be6d8ec01 | ||
![]() |
aad5ed55d2 | ||
![]() |
86da417c17 | ||
![]() |
ae57ab78f3 | ||
![]() |
4487db4e0a | ||
![]() |
a0a50755d3 | ||
![]() |
621e41cc96 | ||
![]() |
96b1f71437 | ||
![]() |
5e0b3b2f35 | ||
![]() |
6829fad5bd | ||
![]() |
7af0d9e87b | ||
![]() |
c089ebea99 |
80
.github/workflows/build.yml
vendored
80
.github/workflows/build.yml
vendored
@@ -1,5 +1,7 @@
|
|||||||
name: "Build Action"
|
name: "Build Action"
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
@@ -8,54 +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
|
|
||||||
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:
|
||||||
npm run build:shell
|
name: NapCat.Shell
|
||||||
cd dist
|
path: dist
|
||||||
npm i --omit=dev
|
|
||||||
rm package-lock.json
|
|
||||||
cd ..
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: NapCat.Shell
|
|
||||||
path: dist
|
|
||||||
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -49,6 +49,9 @@ jobs:
|
|||||||
- name: Build NuCat Framework
|
- name: Build NuCat Framework
|
||||||
run: |
|
run: |
|
||||||
npm i
|
npm i
|
||||||
|
cd napcat.webui
|
||||||
|
npm i
|
||||||
|
cd ..
|
||||||
npm run build:framework
|
npm run build:framework
|
||||||
cd dist
|
cd dist
|
||||||
npm i --omit=dev
|
npm i --omit=dev
|
||||||
@@ -78,6 +81,9 @@ jobs:
|
|||||||
- name: Build NuCat Shell
|
- name: Build NuCat Shell
|
||||||
run: |
|
run: |
|
||||||
npm i
|
npm i
|
||||||
|
cd napcat.webui
|
||||||
|
npm i
|
||||||
|
cd ..
|
||||||
npm run build:shell
|
npm run build:shell
|
||||||
cd dist
|
cd dist
|
||||||
npm i --omit=dev
|
npm i --omit=dev
|
||||||
|
480
LICENSE
480
LICENSE
@@ -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.
|
||||||
|
|
||||||
d)You 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.
|
|
||||||
|
21
README.md
21
README.md
@@ -8,7 +8,7 @@
|
|||||||
## 欢迎回家
|
## 欢迎回家
|
||||||
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||||
|
|
||||||
## 碎碎叨叨
|
## 特性介绍
|
||||||
- [x] **安装简单**:就算是笨蛋也能使用
|
- [x] **安装简单**:就算是笨蛋也能使用
|
||||||
- [x] **性能友好**:就算是低内存也能使用
|
- [x] **性能友好**:就算是低内存也能使用
|
||||||
- [x] **接口丰富**:就算是没有也能使用
|
- [x] **接口丰富**:就算是没有也能使用
|
||||||
@@ -26,25 +26,32 @@ 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
|
||||||
|
|
||||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 延缓Native模块与NapCat对新版QQ适配
|
||||||
|
为未来持续与高效的使用Native模块 模块代码转为完全非Git仓库的本地保存源码 并进行相关重构
|
||||||
|
|
||||||
|
同时为了保证稳定 NapCat 本体通常会在3 Week+的周期进行新版本适配
|
||||||
|
|
||||||
|
因此此时推荐使用release指定版本
|
||||||
|
|
||||||
## 开源附加
|
## 开源附加
|
||||||
|
|
||||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**
|
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "3.7.0",
|
"version": "4.1.12",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
24
napcat.webui/.gitignore
vendored
Normal file
24
napcat.webui/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
3
napcat.webui/.vscode/extensions.json
vendored
Normal file
3
napcat.webui/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
5
napcat.webui/README.md
Normal file
5
napcat.webui/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
52
napcat.webui/eslint.config.mjs
Normal file
52
napcat.webui/eslint.config.mjs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import globals from 'globals';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
import vue from 'eslint-plugin-vue';
|
||||||
|
import prettier from 'eslint-plugin-prettier/recommended';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...ts.configs.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
'@typescript-eslint/no-var-requires': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...vue.configs['flat/base'],
|
||||||
|
{
|
||||||
|
files: ['*.vue', '**/*.vue'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
indent: ['error', 4],
|
||||||
|
semi: ['error', 'always'],
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
'@typescript-eslint/no-var-requires': 'warn',
|
||||||
|
'object-curly-spacing': ['error', 'always'],
|
||||||
|
'vue/v-for-delimiter-style': ['error', 'in'],
|
||||||
|
'vue/require-name-property': 'warn',
|
||||||
|
'vue/prefer-true-attribute-shorthand': 'warn',
|
||||||
|
'prefer-arrow-callback': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prettier,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
13
napcat.webui/index.html
Normal file
13
napcat.webui/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>NapCat WebUI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="./src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
34
napcat.webui/package.json
Normal file
34
napcat.webui/package.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "napcat.webui",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"webui:lint": "eslint --fix src/**/*.{js,ts,vue}",
|
||||||
|
"webui:dev": "vite",
|
||||||
|
"webui:build": "vite build",
|
||||||
|
"webui:preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"tdesign-icons-vue-next": "^0.3.3",
|
||||||
|
"tdesign-vue-next": "^1.10.3",
|
||||||
|
"vue": "^3.5.12",
|
||||||
|
"vue-router": "^4.4.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
|
"@eslint/js": "^9.14.0",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
|
"@vitejs/plugin-legacy": "^5.4.3",
|
||||||
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-vue": "^9.31.0",
|
||||||
|
"globals": "^15.12.0",
|
||||||
|
"terser": "^5.36.0",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"vite": "^5.4.10",
|
||||||
|
"vue-tsc": "^2.1.8"
|
||||||
|
}
|
||||||
|
}
|
BIN
napcat.webui/public/logo.png
Normal file
BIN
napcat.webui/public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 335 KiB |
1
napcat.webui/public/vite.svg
Normal file
1
napcat.webui/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
7
napcat.webui/src/App.vue
Normal file
7
napcat.webui/src/App.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
BIN
napcat.webui/src/assets/Sotheby.ttf
Normal file
BIN
napcat.webui/src/assets/Sotheby.ttf
Normal file
Binary file not shown.
BIN
napcat.webui/src/assets/logo.png
Normal file
BIN
napcat.webui/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 335 KiB |
1
napcat.webui/src/assets/vue.svg
Normal file
1
napcat.webui/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
185
napcat.webui/src/backend/shell.ts
Normal file
185
napcat.webui/src/backend/shell.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import { OneBotConfig } from '../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
export class QQLoginManager {
|
||||||
|
private retCredential: string;
|
||||||
|
private readonly apiPrefix: string;
|
||||||
|
|
||||||
|
//调试时http://127.0.0.1:6099/api 打包时 ../api
|
||||||
|
constructor(retCredential: string, apiPrefix: string = '../api') {
|
||||||
|
this.retCredential = retCredential;
|
||||||
|
this.apiPrefix = apiPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
public async GetOB11Config(): Promise<OneBotConfig> {
|
||||||
|
try {
|
||||||
|
const ConfigResponse = await fetch(`${this.apiPrefix}/OB11Config/GetConfig`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (ConfigResponse.status == 200) {
|
||||||
|
const ConfigResponseJson = await ConfigResponse.json();
|
||||||
|
if (ConfigResponseJson.code == 0) {
|
||||||
|
return ConfigResponseJson?.data as OneBotConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting OB11 config:', error);
|
||||||
|
}
|
||||||
|
return {} as OneBotConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async SetOB11Config(config: OneBotConfig): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const ConfigResponse = await fetch(`${this.apiPrefix}/OB11Config/SetConfig`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ config: JSON.stringify(config) }),
|
||||||
|
});
|
||||||
|
if (ConfigResponse.status == 200) {
|
||||||
|
const ConfigResponseJson = await ConfigResponse.json();
|
||||||
|
if (ConfigResponseJson.code == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting OB11 config:', error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkQQLoginStatus(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/CheckLoginStatus`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (QQLoginResponse.status == 200) {
|
||||||
|
const QQLoginResponseJson = await QQLoginResponse.json();
|
||||||
|
if (QQLoginResponseJson.code == 0) {
|
||||||
|
return QQLoginResponseJson.data.isLogin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking QQ login status:', error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkWebUiLogined(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const LoginResponse = await fetch(`${this.apiPrefix}/auth/check`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (LoginResponse.status == 200) {
|
||||||
|
const LoginResponseJson = await LoginResponse.json();
|
||||||
|
if (LoginResponseJson.code == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking web UI login status:', error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loginWithToken(token: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const loginResponse = await fetch(`${this.apiPrefix}/auth/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ token: token }),
|
||||||
|
});
|
||||||
|
const loginResponseJson = await loginResponse.json();
|
||||||
|
const retCode = loginResponseJson.code;
|
||||||
|
if (retCode === 0) {
|
||||||
|
this.retCredential = loginResponseJson.data.Credential;
|
||||||
|
return this.retCredential;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error logging in with token:', error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getQQLoginQrcode(): Promise<string> {
|
||||||
|
try {
|
||||||
|
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/GetQQLoginQrcode`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (QQLoginResponse.status == 200) {
|
||||||
|
const QQLoginResponseJson = await QQLoginResponse.json();
|
||||||
|
if (QQLoginResponseJson.code == 0) {
|
||||||
|
return QQLoginResponseJson.data.qrcode || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting QQ login QR code:', error);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getQQQuickLoginList(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/GetQuickLoginList`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (QQLoginResponse.status == 200) {
|
||||||
|
const QQLoginResponseJson = await QQLoginResponse.json();
|
||||||
|
if (QQLoginResponseJson.code == 0) {
|
||||||
|
return QQLoginResponseJson.data || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting QQ quick login list:', error);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setQuickLogin(uin: string): Promise<{ result: boolean; errMsg: string }> {
|
||||||
|
try {
|
||||||
|
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/SetQuickLogin`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + this.retCredential,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ uin: uin }),
|
||||||
|
});
|
||||||
|
if (QQLoginResponse.status == 200) {
|
||||||
|
const QQLoginResponseJson = await QQLoginResponse.json();
|
||||||
|
if (QQLoginResponseJson.code == 0) {
|
||||||
|
return { result: true, errMsg: '' };
|
||||||
|
} else {
|
||||||
|
return { result: false, errMsg: QQLoginResponseJson.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting quick login:', error);
|
||||||
|
}
|
||||||
|
return { result: false, errMsg: '接口异常' };
|
||||||
|
}
|
||||||
|
}
|
55
napcat.webui/src/components/Dashboard.vue
Normal file
55
napcat.webui/src/components/Dashboard.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dashboard-container">
|
||||||
|
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
|
||||||
|
<div class="content">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import SidebarMenu from './webui/Nav.vue';
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
value: string;
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
route: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuItems = ref<MenuItem[]>([
|
||||||
|
{ value: 'item1', icon: 'dashboard', label: '基础信息', route: '/dashboard/basic-info' },
|
||||||
|
{ value: 'item3', icon: 'wifi-1', label: '网络配置', route: '/dashboard/network-config' },
|
||||||
|
{ value: 'item4', icon: 'setting', label: '其余配置', route: '/dashboard/other-config' },
|
||||||
|
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
|
||||||
|
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dashboard-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-menu {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
/* padding: 20px; */
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.content {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
167
napcat.webui/src/components/QQLogin.vue
Normal file
167
napcat.webui/src/components/QQLogin.vue
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<h2 class="sotheby-font">QQ Login</h2>
|
||||||
|
<div class="login-methods">
|
||||||
|
<t-button
|
||||||
|
id="quick-login"
|
||||||
|
class="login-method"
|
||||||
|
:class="{ active: loginMethod === 'quick' }"
|
||||||
|
@click="loginMethod = 'quick'"
|
||||||
|
>Quick Login</t-button
|
||||||
|
>
|
||||||
|
<t-button
|
||||||
|
id="qrcode-login"
|
||||||
|
class="login-method"
|
||||||
|
:class="{ active: loginMethod === 'qrcode' }"
|
||||||
|
@click="loginMethod = 'qrcode'"
|
||||||
|
>QR Code</t-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
|
||||||
|
<t-select
|
||||||
|
id="quick-login-select"
|
||||||
|
v-model="selectedAccount"
|
||||||
|
placeholder="Select Account"
|
||||||
|
@change="selectAccount"
|
||||||
|
>
|
||||||
|
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
|
||||||
|
</t-select>
|
||||||
|
</div>
|
||||||
|
<div v-show="loginMethod === 'qrcode'" id="qrcode" class="qrcode">
|
||||||
|
<canvas ref="qrcodeCanvas"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import * as QRCode from 'qrcode';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
|
import { QQLoginManager } from '@/backend/shell';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const loginMethod = ref<'quick' | 'qrcode'>('quick');
|
||||||
|
const quickLoginList = ref<string[]>([]);
|
||||||
|
const selectedAccount = ref<string>('');
|
||||||
|
const qrcodeCanvas = ref<HTMLCanvasElement | null>(null);
|
||||||
|
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
|
||||||
|
let heartBeatTimer: number | null = null;
|
||||||
|
|
||||||
|
const selectAccount = async (accountName: string): Promise<void> => {
|
||||||
|
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
|
||||||
|
if (result) {
|
||||||
|
await MessagePlugin.success('登录成功即将跳转');
|
||||||
|
await router.push({ path: '/dashboard/basic-info' });
|
||||||
|
} else {
|
||||||
|
await MessagePlugin.error('登录失败,' + errMsg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateQrCode = (data: string, canvas: HTMLCanvasElement | null): void => {
|
||||||
|
if (!canvas) {
|
||||||
|
console.error('Canvas element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QRCode.toCanvas(canvas, data, function (error: Error | null | undefined) {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error generating QR Code:', error);
|
||||||
|
} else {
|
||||||
|
console.log('QR Code generated!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeartBeat = async (): Promise<void> => {
|
||||||
|
const isLogined = await qqLoginManager.checkQQLoginStatus();
|
||||||
|
if (isLogined) {
|
||||||
|
if (heartBeatTimer) {
|
||||||
|
clearInterval(heartBeatTimer);
|
||||||
|
}
|
||||||
|
await router.push({ path: '/dashboard/basic-info' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const InitPages = async (): Promise<void> => {
|
||||||
|
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
|
||||||
|
const qrcodeData = await qqLoginManager.getQQLoginQrcode();
|
||||||
|
generateQrCode(qrcodeData, qrcodeCanvas.value);
|
||||||
|
heartBeatTimer = window.setInterval(HeartBeat, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
InitPages();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: white;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.login-container {
|
||||||
|
width: 90%;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-methods {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-method {
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-method.active {
|
||||||
|
background-color: #e6f0ff;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form,
|
||||||
|
.qrcode {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sotheby-font {
|
||||||
|
font-family: Sotheby, Helvetica, monospace;
|
||||||
|
font-size: 3.125rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #888;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
151
napcat.webui/src/components/WebUiLogin.vue
Normal file
151
napcat.webui/src/components/WebUiLogin.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<h2 class="sotheby-font">WebUi Login</h2>
|
||||||
|
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
||||||
|
<t-form-item name="password">
|
||||||
|
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
|
||||||
|
<template #prefix-icon>
|
||||||
|
<lock-on-icon />
|
||||||
|
</template>
|
||||||
|
</t-input>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item>
|
||||||
|
<t-button theme="primary" type="submit" block>登录</t-button>
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
<div class="footer">Power By NapCat.WebUi</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import '../css/style.css';
|
||||||
|
import '../css/font.css';
|
||||||
|
import { reactive, onMounted } from 'vue';
|
||||||
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
|
import { LockOnIcon } from 'tdesign-icons-vue-next';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { QQLoginManager } from '@/backend/shell';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData: FormData = reactive({
|
||||||
|
token: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLoginSuccess = async (credential: string) => {
|
||||||
|
localStorage.setItem('auth', credential);
|
||||||
|
await checkLoginStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoginFailure = (message: string) => {
|
||||||
|
MessagePlugin.error(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkLoginStatus = async () => {
|
||||||
|
const storedCredential = localStorage.getItem('auth');
|
||||||
|
if (!storedCredential) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const loginManager = new QQLoginManager(storedCredential);
|
||||||
|
const isWenUiLoggedIn = await loginManager.checkWebUiLogined();
|
||||||
|
console.log('isWenUiLoggedIn', isWenUiLoggedIn);
|
||||||
|
if (!isWenUiLoggedIn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isQQLoggedIn = await loginManager.checkQQLoginStatus();
|
||||||
|
if (isQQLoggedIn) {
|
||||||
|
await router.push({ path: '/dashboard/basic-info' });
|
||||||
|
} else {
|
||||||
|
await router.push({ path: '/qqlogin' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginWithToken = async (token: string) => {
|
||||||
|
const loginManager = new QQLoginManager('');
|
||||||
|
const credential = await loginManager.loginWithToken(token);
|
||||||
|
if (credential) {
|
||||||
|
await handleLoginSuccess(credential);
|
||||||
|
} else {
|
||||||
|
handleLoginFailure('登录失败,请检查Token');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const token = url.searchParams.get('token');
|
||||||
|
if (token) {
|
||||||
|
loginWithToken(token);
|
||||||
|
}
|
||||||
|
checkLoginStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
|
||||||
|
if (validateResult) {
|
||||||
|
await loginWithToken(formData.token);
|
||||||
|
} else {
|
||||||
|
handleLoginFailure('请填写Token');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: white;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.login-container {
|
||||||
|
width: 90%;
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tdesign-demo-block-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tdesign-demo-block-column-large {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tdesign-demo-block-row {
|
||||||
|
display: flex;
|
||||||
|
column-gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sotheby-font {
|
||||||
|
font-family: Sotheby, Helvetica, monospace;
|
||||||
|
font-size: 3.125rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #888;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
71
napcat.webui/src/components/webui/Nav.vue
Normal file
71
napcat.webui/src/components/webui/Nav.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
|
||||||
|
<template #logo> </template>
|
||||||
|
<router-link v-for="item in menuItems" :key="item.value" :to="item.route">
|
||||||
|
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
|
||||||
|
<template #icon>
|
||||||
|
<t-icon :name="item.icon" />
|
||||||
|
</template>
|
||||||
|
{{ item.label }}
|
||||||
|
</t-menu-item>
|
||||||
|
</router-link>
|
||||||
|
<template #operations>
|
||||||
|
<t-button class="t-demo-collapse-btn" variant="text" shape="square" @click="changeCollapsed">
|
||||||
|
<template #icon><t-icon :name="iconName" /></template>
|
||||||
|
</t-button>
|
||||||
|
</template>
|
||||||
|
</t-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, defineProps } from 'vue';
|
||||||
|
|
||||||
|
type MenuItem = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
route: string;
|
||||||
|
icon?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
menuItems: MenuItem[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
|
||||||
|
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
|
||||||
|
|
||||||
|
const changeCollapsed = (): void => {
|
||||||
|
collapsed.value = !collapsed.value;
|
||||||
|
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
|
||||||
|
localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar-menu {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 200px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar-menu {
|
||||||
|
width: 100px; /* 移动端侧边栏宽度 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
6
napcat.webui/src/css/font.css
Normal file
6
napcat.webui/src/css/font.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Sotheby';
|
||||||
|
src: url('../assets/Sotheby.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
84
napcat.webui/src/css/style.css
Normal file
84
napcat.webui/src/css/style.css
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
62
napcat.webui/src/main.ts
Normal file
62
napcat.webui/src/main.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import App from './App.vue';
|
||||||
|
import {
|
||||||
|
Button as TButton,
|
||||||
|
Input as TInput,
|
||||||
|
Form as TForm,
|
||||||
|
FormItem as TFormItem,
|
||||||
|
Select as TSelect,
|
||||||
|
Option as TOption,
|
||||||
|
Menu as TMenu,
|
||||||
|
MenuItem as TMenuItem,
|
||||||
|
Icon as TIcon,
|
||||||
|
Submenu as TSubmenu,
|
||||||
|
Col as TCol,
|
||||||
|
Row as TRow,
|
||||||
|
Card as TCard,
|
||||||
|
Divider as TDivider,
|
||||||
|
Link as TLink,
|
||||||
|
List as TList,
|
||||||
|
Alert as TAlert,
|
||||||
|
Tag as TTag,
|
||||||
|
ListItem as TListItem,
|
||||||
|
Tabs as TTabs,
|
||||||
|
TabPanel as TTabPanel,
|
||||||
|
Space as TSpace,
|
||||||
|
Checkbox as TCheckbox,
|
||||||
|
Popup as TPopup,
|
||||||
|
Dialog as TDialog,
|
||||||
|
Switch as TSwitch,
|
||||||
|
} from 'tdesign-vue-next';
|
||||||
|
import { router } from './router';
|
||||||
|
import 'tdesign-vue-next/es/style/index.css';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
app.use(router);
|
||||||
|
app.use(TButton);
|
||||||
|
app.use(TInput);
|
||||||
|
app.use(TForm);
|
||||||
|
app.use(TFormItem);
|
||||||
|
app.use(TSelect);
|
||||||
|
app.use(TOption);
|
||||||
|
app.use(TMenu);
|
||||||
|
app.use(TMenuItem);
|
||||||
|
app.use(TIcon);
|
||||||
|
app.use(TSubmenu);
|
||||||
|
app.use(TCol);
|
||||||
|
app.use(TRow);
|
||||||
|
app.use(TCard);
|
||||||
|
app.use(TDivider);
|
||||||
|
app.use(TLink);
|
||||||
|
app.use(TList);
|
||||||
|
app.use(TAlert);
|
||||||
|
app.use(TTag);
|
||||||
|
app.use(TListItem);
|
||||||
|
app.use(TTabs);
|
||||||
|
app.use(TTabPanel);
|
||||||
|
app.use(TSpace);
|
||||||
|
app.use(TCheckbox);
|
||||||
|
app.use(TPopup);
|
||||||
|
app.use(TDialog);
|
||||||
|
app.use(TSwitch);
|
||||||
|
app.mount('#app');
|
66
napcat.webui/src/pages/AboutUs.vue
Normal file
66
napcat.webui/src/pages/AboutUs.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about-us">
|
||||||
|
<div>
|
||||||
|
<t-divider content="面板关于信息" align="left" />
|
||||||
|
<t-alert theme="success" message="NapCat.WebUi is running" />
|
||||||
|
<t-list class="list">
|
||||||
|
<t-list-item class="list-item">
|
||||||
|
<span class="item-label">开发人员:</span>
|
||||||
|
<span class="item-content">
|
||||||
|
<t-link href="mailto:nanaeonn@outlook.com">Mlikiowa</t-link>
|
||||||
|
</span>
|
||||||
|
</t-list-item>
|
||||||
|
<t-list-item class="list-item">
|
||||||
|
<span class="item-label">版本信息:</span>
|
||||||
|
<span class="item-content">
|
||||||
|
<t-tag class="tag-item" theme="success"> WebUi: {{ pkg.version }} </t-tag>
|
||||||
|
<t-tag class="tag-item" theme="success"> NapCat: {{ napCatVersion }} </t-tag>
|
||||||
|
<t-tag class="tag-item" theme="success">
|
||||||
|
TDesign: {{ pkg.dependencies['tdesign-vue-next'] }}
|
||||||
|
</t-tag>
|
||||||
|
</span>
|
||||||
|
</t-list-item>
|
||||||
|
</t-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import pkg from '../../package.json';
|
||||||
|
import { napCatVersion } from '../../../src/common/version';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.about-us {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-label {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
flex: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
6
napcat.webui/src/pages/BasicInfo.vue
Normal file
6
napcat.webui/src/pages/BasicInfo.vue
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div class="basic-info">
|
||||||
|
<h1>面板基础信息</h1>
|
||||||
|
<p>这里显示面板的基础信息。</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
6
napcat.webui/src/pages/Log.vue
Normal file
6
napcat.webui/src/pages/Log.vue
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div class="log-view">
|
||||||
|
<h1>面板日志信息</h1>
|
||||||
|
<p>这里显示面板的日志信息。</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
249
napcat.webui/src/pages/NetWork.vue
Normal file
249
napcat.webui/src/pages/NetWork.vue
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<template>
|
||||||
|
<t-space class="full-space">
|
||||||
|
<template v-if="clientPanelData.length > 0">
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" />
|
||||||
|
</template>
|
||||||
|
<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 } 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';
|
||||||
|
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
|
||||||
|
|
||||||
|
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
|
||||||
|
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
|
||||||
|
type ComponentUnion =
|
||||||
|
| typeof HttpServerComponent
|
||||||
|
| typeof HttpClientComponent
|
||||||
|
| typeof WebsocketServerComponent
|
||||||
|
| typeof WebsocketClientComponent;
|
||||||
|
|
||||||
|
const componentMap: Record<ConfigKey, ComponentUnion> = {
|
||||||
|
httpServers: HttpServerComponent,
|
||||||
|
httpClients: HttpClientComponent,
|
||||||
|
websocketServers: WebsocketServerComponent,
|
||||||
|
websocketClients: WebsocketClientComponent,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
key: K;
|
||||||
|
data: ConfigMap[K];
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTab = ref<number>(0);
|
||||||
|
const isDialogVisible = ref(false);
|
||||||
|
const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' });
|
||||||
|
const clientPanelData: Ref<ClientPanel[]> = ref([]);
|
||||||
|
|
||||||
|
const getComponent = (type: ConfigKey) => {
|
||||||
|
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 = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => {
|
||||||
|
configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addConfigDataToPanel = (data: NetworkConfig) => {
|
||||||
|
(Object.keys(data) as ConfigKey[]).forEach((key) => {
|
||||||
|
addToPanel(data[key], key);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsePanelData = (): NetworkConfig => {
|
||||||
|
const result: NetworkConfig = {
|
||||||
|
httpServers: [],
|
||||||
|
httpClients: [],
|
||||||
|
websocketServers: [],
|
||||||
|
websocketClients: [],
|
||||||
|
};
|
||||||
|
clientPanelData.value.forEach((panel) => {
|
||||||
|
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveConfig = async () => {
|
||||||
|
const config = parsePanelData();
|
||||||
|
const userConfig = await getOB11Config();
|
||||||
|
if (!userConfig) {
|
||||||
|
await MessagePlugin.error('无法获取配置!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userConfig.network = config;
|
||||||
|
const success = await setOB11Config(userConfig);
|
||||||
|
if (success) {
|
||||||
|
await MessagePlugin.success('配置保存成功');
|
||||||
|
} else {
|
||||||
|
await MessagePlugin.error('配置保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAddTabDialog = () => {
|
||||||
|
newTab.value = { name: '', type: 'httpServers' };
|
||||||
|
isDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTab = async () => {
|
||||||
|
const { name, type } = newTab.value;
|
||||||
|
if (clientPanelData.value.some((panel) => panel.name === name)) {
|
||||||
|
await MessagePlugin.error('选项卡名称已存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const defaultConfig = structuredClone(defaultConfigMap[type]);
|
||||||
|
defaultConfig.name = name;
|
||||||
|
clientPanelData.value.push({ name, data: defaultConfig, key: type });
|
||||||
|
isDialogVisible.value = false;
|
||||||
|
await nextTick();
|
||||||
|
activeTab.value = clientPanelData.value.length - 1;
|
||||||
|
await MessagePlugin.success('选项卡添加成功');
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
|
||||||
|
clientPanelData.value.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>
|
134
napcat.webui/src/pages/OtherConfig.vue
Normal file
134
napcat.webui/src/pages/OtherConfig.vue
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<t-divider content="其余配置" align="left" />
|
||||||
|
</div>
|
||||||
|
<div class="other-config-container">
|
||||||
|
<div class="other-config">
|
||||||
|
<t-form ref="form" :model="otherConfig" class="form">
|
||||||
|
<t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item">
|
||||||
|
<t-input v-model="otherConfig.musicSignUrl" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
|
||||||
|
<t-switch v-model="otherConfig.enableLocalFile2Url" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
<div class="button-container">
|
||||||
|
<t-button @click="saveConfig">保存</t-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
|
import { OneBotConfig } from '../../../src/onebot/config/config';
|
||||||
|
import { QQLoginManager } from '@/backend/shell';
|
||||||
|
|
||||||
|
const otherConfig = ref<Partial<OneBotConfig>>({
|
||||||
|
musicSignUrl: '',
|
||||||
|
enableLocalFile2Url: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 loadConfig = async () => {
|
||||||
|
try {
|
||||||
|
const userConfig = await getOB11Config();
|
||||||
|
if (userConfig) {
|
||||||
|
otherConfig.value.musicSignUrl = userConfig.musicSignUrl;
|
||||||
|
otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading config:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveConfig = async () => {
|
||||||
|
try {
|
||||||
|
const userConfig = await getOB11Config();
|
||||||
|
if (userConfig) {
|
||||||
|
userConfig.musicSignUrl = otherConfig.value.musicSignUrl || '';
|
||||||
|
userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false;
|
||||||
|
const success = await setOB11Config(userConfig);
|
||||||
|
if (success) {
|
||||||
|
MessagePlugin.success('配置保存成功');
|
||||||
|
} else {
|
||||||
|
MessagePlugin.error('配置保存失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving config:', error);
|
||||||
|
MessagePlugin.error('配置保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadConfig();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.other-config-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-config {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.form-item {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item t-input,
|
||||||
|
.form-item t-switch {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
22
napcat.webui/src/pages/network/EmptyStateComponent.vue
Normal file
22
napcat.webui/src/pages/network/EmptyStateComponent.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div class="empty-state">
|
||||||
|
<p>当前没有网络配置</p>
|
||||||
|
<t-button @click="showAddTabDialog">添加网络配置</t-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps } from 'vue';
|
||||||
|
defineProps<{ showAddTabDialog: () => void }>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
68
napcat.webui/src/pages/network/HttpClientComponent.vue
Normal file
68
napcat.webui/src/pages/network/HttpClientComponent.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="form-container">
|
||||||
|
<h3>HTTP Client 配置</h3>
|
||||||
|
<t-form>
|
||||||
|
<t-form-item label="启用">
|
||||||
|
<t-checkbox v-model="config.enable" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="URL">
|
||||||
|
<t-input v-model="config.url" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="消息格式">
|
||||||
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="报告自身消息">
|
||||||
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="Token">
|
||||||
|
<t-input v-model="config.token" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="调试模式">
|
||||||
|
<t-checkbox v-model="config.debug" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps, ref, watch } from 'vue';
|
||||||
|
import { HttpClientConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: HttpClientConfig;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const messageFormatOptions = ref([
|
||||||
|
{ label: 'Array', value: 'array' },
|
||||||
|
{ label: 'String', value: 'string' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.config.messagePostFormat,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
|
props.config.messagePostFormat = 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
74
napcat.webui/src/pages/network/HttpServerComponent.vue
Normal file
74
napcat.webui/src/pages/network/HttpServerComponent.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="form-container">
|
||||||
|
<h3>HTTP Server 配置</h3>
|
||||||
|
<t-form>
|
||||||
|
<t-form-item label="启用">
|
||||||
|
<t-checkbox v-model="config.enable" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="端口">
|
||||||
|
<t-input v-model.number="config.port" type="number" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="主机">
|
||||||
|
<t-input v-model="config.host" type="text" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="启用 CORS">
|
||||||
|
<t-checkbox v-model="config.enableCors" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="启用 WS">
|
||||||
|
<t-checkbox v-model="config.enableWebsocket" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="消息格式">
|
||||||
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="Token">
|
||||||
|
<t-input v-model="config.token" type="text" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="调试模式">
|
||||||
|
<t-checkbox v-model="config.debug" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps, ref, watch } from 'vue';
|
||||||
|
import { HttpServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: HttpServerConfig;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const messageFormatOptions = ref([
|
||||||
|
{ label: 'Array', value: 'array' },
|
||||||
|
{ label: 'String', value: 'string' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.config.messagePostFormat,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
|
props.config.messagePostFormat = 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
71
napcat.webui/src/pages/network/WebsocketClientComponent.vue
Normal file
71
napcat.webui/src/pages/network/WebsocketClientComponent.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="form-container">
|
||||||
|
<h3>WebSocket Client 配置</h3>
|
||||||
|
<t-form>
|
||||||
|
<t-form-item label="启用">
|
||||||
|
<t-checkbox v-model="config.enable" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="URL">
|
||||||
|
<t-input v-model="config.url" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="消息格式">
|
||||||
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="报告自身消息">
|
||||||
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="Token">
|
||||||
|
<t-input v-model="config.token" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="调试模式">
|
||||||
|
<t-checkbox v-model="config.debug" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="心跳间隔">
|
||||||
|
<t-input v-model.number="config.heartInterval" type="number" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps, ref, watch } from 'vue';
|
||||||
|
import { WebsocketClientConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: WebsocketClientConfig;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const messageFormatOptions = ref([
|
||||||
|
{ label: 'Array', value: 'array' },
|
||||||
|
{ label: 'String', value: 'string' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.config.messagePostFormat,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
|
props.config.messagePostFormat = 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
77
napcat.webui/src/pages/network/WebsocketServerComponent.vue
Normal file
77
napcat.webui/src/pages/network/WebsocketServerComponent.vue
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="form-container">
|
||||||
|
<h3>WebSocket Server 配置</h3>
|
||||||
|
<t-form>
|
||||||
|
<t-form-item label="启用">
|
||||||
|
<t-checkbox v-model="config.enable" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="主机">
|
||||||
|
<t-input v-model="config.host" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="端口">
|
||||||
|
<t-input v-model.number="config.port" type="number" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="消息格式">
|
||||||
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="上报自身消息">
|
||||||
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="Token">
|
||||||
|
<t-input v-model="config.token" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="强制推送事件">
|
||||||
|
<t-checkbox v-model="config.enableForcePushEvent" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="调试模式">
|
||||||
|
<t-checkbox v-model="config.debug" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="心跳间隔">
|
||||||
|
<t-input v-model.number="config.heartInterval" type="number" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps, ref, watch } from 'vue';
|
||||||
|
import { WebsocketServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: WebsocketServerConfig;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const messageFormatOptions = ref([
|
||||||
|
{ label: 'Array', value: 'array' },
|
||||||
|
{ label: 'String', value: 'string' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.config.messagePostFormat,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
|
props.config.messagePostFormat = 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
32
napcat.webui/src/router/index.ts
Normal file
32
napcat.webui/src/router/index.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||||
|
import Dashboard from '../components/Dashboard.vue';
|
||||||
|
import BasicInfo from '../pages/BasicInfo.vue';
|
||||||
|
import AboutUs from '../pages/AboutUs.vue';
|
||||||
|
import LogView from '../pages/Log.vue';
|
||||||
|
import NetWork from '../pages/NetWork.vue';
|
||||||
|
import QQLogin from '../components/QQLogin.vue';
|
||||||
|
import WebUiLogin from '../components/WebUiLogin.vue';
|
||||||
|
import OtherConfig from '../pages/OtherConfig.vue';
|
||||||
|
|
||||||
|
const routes: Array<RouteRecordRaw> = [
|
||||||
|
{ path: '/', redirect: '/webui' },
|
||||||
|
{ path: '/webui', component: WebUiLogin, name: 'WebUiLogin' },
|
||||||
|
{ path: '/qqlogin', component: QQLogin, name: 'QQLogin' },
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
component: Dashboard,
|
||||||
|
children: [
|
||||||
|
{ path: '', redirect: 'basic-info' },
|
||||||
|
{ path: 'basic-info', component: BasicInfo, name: 'BasicInfo' },
|
||||||
|
{ path: 'network-config', component: NetWork, name: 'NetWork' },
|
||||||
|
{ path: 'log-view', component: LogView, name: 'LogView' },
|
||||||
|
{ path: 'other-config', component: OtherConfig, name: 'OtherConfig' },
|
||||||
|
{ path: 'about-us', component: AboutUs, name: 'AboutUs' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
1
napcat.webui/src/vite-env.d.ts
vendored
Normal file
1
napcat.webui/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
34
napcat.webui/tsconfig.json
Normal file
34
napcat.webui/tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "vue",
|
||||||
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"types": [
|
||||||
|
"vite/client"
|
||||||
|
],
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"references": [{"path": "./tsconfig.node.json"}]
|
||||||
|
}
|
11
napcat.webui/tsconfig.node.json
Normal file
11
napcat.webui/tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strictNullChecks": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
41
napcat.webui/vite.config.ts
Normal file
41
napcat.webui/vite.config.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import legacy from '@vitejs/plugin-legacy';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
legacy({
|
||||||
|
targets: ['defaults', 'not IE 11'],
|
||||||
|
modernPolyfills: ['web.structured-clone'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
base: './',
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': 'http://localhost:6099',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
chunkSizeWarningLimit: 4000,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
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')) {
|
||||||
|
return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
22
package.json
22
package.json
@@ -2,12 +2,15 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "3.7.0",
|
"version": "4.1.12",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "vite build --mode framework",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
"build:shell": "vite build --mode shell",
|
"build:shell": "npm run build:webui && vite build --mode shell || exit 1",
|
||||||
"build:webui": "cd ./src/webui && vite build",
|
"build:webui": "cd napcat.webui && vite build",
|
||||||
"lint": "eslint --fix src/**/*.{js,ts}",
|
"dev:framework": "vite build --mode framework",
|
||||||
|
"dev:shell": "vite build --mode shell",
|
||||||
|
"dev:webui": "cd napcat.webui && npm run webui:dev",
|
||||||
|
"lint": "eslint --fix src/**/*.{js,ts,vue}",
|
||||||
"depend": "cd dist && npm install --omit=dev"
|
"depend": "cd dist && npm install --omit=dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -44,13 +47,14 @@
|
|||||||
"vite": "^5.2.6",
|
"vite": "^5.2.6",
|
||||||
"vite-plugin-cp": "^4.0.8",
|
"vite-plugin-cp": "^4.0.8",
|
||||||
"vite-tsconfig-paths": "^5.1.0",
|
"vite-tsconfig-paths": "^5.1.0",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0"
|
||||||
"fluent-ffmpeg": "^2.1.2"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
"silk-wasm": "^3.6.1",
|
"silk-wasm": "^3.6.1",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0",
|
||||||
"qrcode-terminal": "^0.12.0"
|
"piscina": "^4.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -45,7 +45,6 @@ try {
|
|||||||
sed -i "s/\\"version\\": \\"${currentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" package.json
|
sed -i "s/\\"version\\": \\"${currentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" package.json
|
||||||
sed -i "s/\\"version\\": \\"${manifestCurrentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" manifest.json
|
sed -i "s/\\"version\\": \\"${manifestCurrentVersion}\\"/\\"version\\": \\"${targetVersion}\\"/g" manifest.json
|
||||||
sed -i "s/napCatVersion = '.*'/napCatVersion = '${targetVersion}'/g" ./src/common/version.ts
|
sed -i "s/napCatVersion = '.*'/napCatVersion = '${targetVersion}'/g" ./src/common/version.ts
|
||||||
sed -i "s/SettingButton(\\"V.*\\", \\"napcat-update-button\\", \\"secondary\\")/SettingButton(\\"V${targetVersion}\\", \\"napcat-update-button\\", \\"secondary\\")/g" ./static/assets/renderer.js
|
|
||||||
git add .
|
git add .
|
||||||
git commit -m "release: v${targetVersion}"
|
git commit -m "release: v${targetVersion}"
|
||||||
git push -u origin main`;
|
git push -u origin main`;
|
||||||
|
9
src/common/audio-worker.ts
Normal file
9
src/common/audio-worker.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { encode } from "silk-wasm";
|
||||||
|
|
||||||
|
export interface EncodeArgs {
|
||||||
|
input: ArrayBufferView | ArrayBuffer
|
||||||
|
sampleRate: number
|
||||||
|
}
|
||||||
|
export default async ({ input, sampleRate }: EncodeArgs) => {
|
||||||
|
return await encode(input, sampleRate);
|
||||||
|
};
|
@@ -1,13 +1,23 @@
|
|||||||
|
import Piscina from 'piscina';
|
||||||
import fsPromise from 'fs/promises';
|
import fsPromise from 'fs/promises';
|
||||||
import path from 'node:path';
|
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 { encode, 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";
|
||||||
|
|
||||||
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||||
const EXIT_CODES = [0, 255];
|
const EXIT_CODES = [0, 255];
|
||||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
const FFMPEG_PATH = process.env.FFMPEG_PATH ?? 'ffmpeg';
|
||||||
|
|
||||||
|
async function getWorkerPath() {
|
||||||
|
return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const piscina = new Piscina<EncodeArgs, EncodeResult>({
|
||||||
|
filename: await getWorkerPath(),
|
||||||
|
});
|
||||||
|
|
||||||
async function guessDuration(pttPath: string, logger: LogWrapper) {
|
async function guessDuration(pttPath: string, logger: LogWrapper) {
|
||||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||||
@@ -41,8 +51,11 @@ async function convert(filePath: string, pcmPath: string, logger: LogWrapper): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleWavFile(
|
async function handleWavFile(
|
||||||
file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper
|
file: Buffer,
|
||||||
): Promise<{input: Buffer, sampleRate: number}> {
|
filePath: string,
|
||||||
|
pcmPath: string,
|
||||||
|
logger: LogWrapper
|
||||||
|
): Promise<{ input: Buffer; sampleRate: number }> {
|
||||||
const { fmt } = getWavFileInfo(file);
|
const { fmt } = getWavFileInfo(file);
|
||||||
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
||||||
return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||||
@@ -60,8 +73,8 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
const { input, sampleRate } = isWav(file)
|
const { input, sampleRate } = isWav(file)
|
||||||
? (await handleWavFile(file, filePath, pcmPath, logger))
|
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||||
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||||
const silk = await encode(input, sampleRate);
|
const silk = await piscina.run({ input: input, sampleRate: sampleRate });
|
||||||
await fsPromise.writeFile(pttPath, silk.data);
|
await fsPromise.writeFile(pttPath, Buffer.from(silk.data));
|
||||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||||
return {
|
return {
|
||||||
converted: true,
|
converted: true,
|
||||||
@@ -86,4 +99,4 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
logger.logError.bind(logger)('convert silk failed', error.stack);
|
logger.logError.bind(logger)('convert silk failed', error.stack);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,12 @@ export abstract class ConfigBase<T> {
|
|||||||
configPath: string;
|
configPath: string;
|
||||||
configData: T = {} as T;
|
configData: T = {} as T;
|
||||||
|
|
||||||
protected constructor(name: string, core: NapCatCore, configPath: string) {
|
protected constructor(name: string, core: NapCatCore, configPath: string, copy_default: boolean = true) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this.configPath = configPath;
|
this.configPath = configPath;
|
||||||
fs.mkdirSync(this.configPath, { recursive: true });
|
fs.mkdirSync(this.configPath, { recursive: true });
|
||||||
this.read();
|
this.read(copy_default);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getKeys(): string[] | null {
|
protected getKeys(): string[] | null {
|
||||||
@@ -32,16 +32,18 @@ export abstract class ConfigBase<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
read(): T {
|
read(copy_default: boolean = true): T {
|
||||||
const logger = this.core.context.logger;
|
const logger = this.core.context.logger;
|
||||||
const configPath = this.getConfigPath(this.core.selfInfo.uin);
|
const configPath = this.getConfigPath(this.core.selfInfo.uin);
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath) && copy_default) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
fs.writeFileSync(configPath, fs.readFileSync(this.getConfigPath(undefined), 'utf-8'));
|
||||||
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
logger.log(`[Core] [Config] 配置文件创建成功!\n`);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
logger.logError.bind(logger)(`[Core] [Config] 创建配置文件时发生错误:`, e.message);
|
||||||
}
|
}
|
||||||
|
} else if (!fs.existsSync(configPath) && !copy_default) {
|
||||||
|
fs.writeFileSync(configPath, '{}');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
this.configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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: '' };
|
||||||
}
|
}
|
||||||
|
@@ -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 '';
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '3.7.0';
|
export const napCatVersion = '4.1.12';
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -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) {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { InstanceContext, NapCatCore } from '..';
|
import { InstanceContext, NapCatCore } from '@/core';
|
||||||
|
|
||||||
export class NTQQCollectionApi {
|
export class NTQQCollectionApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
|
@@ -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 {
|
||||||
@@ -410,27 +416,29 @@ export class NTQQFileApi {
|
|||||||
if (!element) {
|
if (!element) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getRkeyData() {
|
private async getRkeyData() {
|
||||||
const rkeyData = {
|
const rkeyData = {
|
||||||
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
|
private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4',
|
||||||
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
|
group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds',
|
||||||
online_rkey: false
|
online_rkey: false
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.core.apis.PacketApi.available) {
|
if (this.core.apis.PacketApi.available) {
|
||||||
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||||
@@ -447,7 +455,7 @@ export class NTQQFileApi {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
|
this.context.logger.logError.bind(this.context.logger)('获取rkey失败', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rkeyData.online_rkey) {
|
if (!rkeyData.online_rkey) {
|
||||||
try {
|
try {
|
||||||
const tempRkeyData = await this.rkeyManager.getRkey();
|
const tempRkeyData = await this.rkeyManager.getRkey();
|
||||||
@@ -458,34 +466,23 @@ export class NTQQFileApi {
|
|||||||
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
|
this.context.logger.logError.bind(this.context.logger)('获取rkey失败 Fallback Old Mode', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rkeyData;
|
return rkeyData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getImageUrlFromParsedUrl(parsedUrl: URL, rkeyData: any): string {
|
private getImageUrlFromParsedUrl(imageFileId: string, appid: string, rkeyData: any): string {
|
||||||
const urlRkey = parsedUrl.searchParams.get('rkey');
|
const rkey = appid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||||
const imageAppid = parsedUrl.searchParams.get('appid');
|
if (rkeyData.online_rkey) {
|
||||||
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
|
return IMAGE_HTTP_HOST_NT + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}`;
|
||||||
const imageFileId = parsedUrl.searchParams.get('fileid');
|
|
||||||
|
|
||||||
if (isNTV2 && urlRkey) {
|
|
||||||
return IMAGE_HTTP_HOST_NT + urlRkey;
|
|
||||||
} else if (isNTV2 && rkeyData.online_rkey) {
|
|
||||||
const rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
|
||||||
return IMAGE_HTTP_HOST_NT + parsedUrl.pathname + `&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 {
|
||||||
if (fileMd5 || md5HexStr) {
|
if (fileMd5 || md5HexStr) {
|
||||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr ?? '').toUpperCase()}/0`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context.logger.logDebug('图片url获取失败', { fileMd5, md5HexStr });
|
this.context.logger.logDebug('图片url获取失败', { fileMd5, md5HexStr });
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@@ -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),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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';
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
@@ -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, 没有host,host是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
|
|
2
src/core/external/napcat.json
vendored
2
src/core/external/napcat.json
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"fileLog": true,
|
"fileLog": false,
|
||||||
"consoleLog": true,
|
"consoleLog": true,
|
||||||
"fileLogLevel": "debug",
|
"fileLogLevel": "debug",
|
||||||
"consoleLogLevel": "info",
|
"consoleLogLevel": "info",
|
||||||
|
@@ -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 = {
|
||||||
|
@@ -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 = {
|
||||||
|
@@ -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)!;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { BuddyCategoryType, FriendRequestNotify } from '@/core/entities';
|
import { BuddyCategoryType, FriendRequestNotify } from '@/core/types';
|
||||||
|
|
||||||
export type OnBuddyChangeParams = BuddyCategoryType[];
|
export type OnBuddyChangeParams = BuddyCategoryType[];
|
||||||
|
|
||||||
|
@@ -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 { }
|
||||||
|
@@ -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
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -65,35 +65,22 @@ export class PacketOperationContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async UploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
async UploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
const reqList = [];
|
const chatType = groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C;
|
||||||
for (const m of msg) {
|
const peerUid = groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid;
|
||||||
for (const e of m.msg) {
|
const reqList = msg.flatMap(m =>
|
||||||
|
m.msg.map(e => {
|
||||||
if (e instanceof PacketMsgPicElement) {
|
if (e instanceof PacketMsgPicElement) {
|
||||||
reqList.push(this.context.highway.uploadImage({
|
return this.context.highway.uploadImage({ chatType, peerUid }, e);
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
} else if (e instanceof PacketMsgVideoElement) {
|
||||||
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
return this.context.highway.uploadVideo({ chatType, peerUid }, e);
|
||||||
}, e));
|
} else if (e instanceof PacketMsgPttElement) {
|
||||||
|
return this.context.highway.uploadPtt({ chatType, peerUid }, e);
|
||||||
|
} else if (e instanceof PacketMsgFileElement) {
|
||||||
|
return this.context.highway.uploadFile({ chatType, peerUid }, e);
|
||||||
}
|
}
|
||||||
if (e instanceof PacketMsgVideoElement) {
|
return null;
|
||||||
reqList.push(this.context.highway.uploadVideo({
|
}).filter(Boolean)
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
);
|
||||||
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
|
||||||
}, e));
|
|
||||||
}
|
|
||||||
if (e instanceof PacketMsgPttElement) {
|
|
||||||
reqList.push(this.context.highway.uploadPtt({
|
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
|
||||||
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
|
||||||
}, e));
|
|
||||||
}
|
|
||||||
if (e instanceof PacketMsgFileElement) {
|
|
||||||
reqList.push(this.context.highway.uploadFile({
|
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
|
||||||
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
|
||||||
}, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res = await Promise.allSettled(reqList);
|
const res = await Promise.allSettled(reqList);
|
||||||
this.context.logger.info(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}个`);
|
this.context.logger.info(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}个`);
|
||||||
res.forEach((result, index) => {
|
res.forEach((result, index) => {
|
||||||
|
@@ -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> {
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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);
|
||||||
|
@@ -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,
|
||||||
|
@@ -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),
|
||||||
|
@@ -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),
|
||||||
|
@@ -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),
|
||||||
|
@@ -45,11 +45,17 @@ export class Sha1Stream {
|
|||||||
let e = this._state[4];
|
let e = this._state[4];
|
||||||
|
|
||||||
for (let i = 0; i < 80; i++) {
|
for (let i = 0; i < 80; i++) {
|
||||||
const [f, k] = (i < 20) ? [(b & c) | ((~b) & d), 0x5A827999] :
|
let temp;
|
||||||
(i < 40) ? [b ^ c ^ d, 0x6ED9EBA1] :
|
if (i < 20) {
|
||||||
(i < 60) ? [(b & c) | (b & d) | (c & d), 0x8F1BBCDC] :
|
temp = ((b & c) | (~b & d)) + 0x5A827999;
|
||||||
[b ^ c ^ d, 0xCA62C1D6];
|
} else if (i < 40) {
|
||||||
const temp = (this.rotateLeft(a, 5) + f + k + e + w[i]) >>> 0;
|
temp = (b ^ c ^ d) + 0x6ED9EBA1;
|
||||||
|
} else if (i < 60) {
|
||||||
|
temp = ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC;
|
||||||
|
} else {
|
||||||
|
temp = (b ^ c ^ d) + 0xCA62C1D6;
|
||||||
|
}
|
||||||
|
temp += ((this.rotateLeft(a, 5) + e + w[i]) >>> 0);
|
||||||
e = d;
|
e = d;
|
||||||
d = c;
|
d = c;
|
||||||
c = this.rotateLeft(b, 30) >>> 0;
|
c = this.rotateLeft(b, 30) >>> 0;
|
||||||
|
@@ -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 & {
|
||||||
|
@@ -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
|
||||||
|
@@ -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 {
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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
68
src/core/types/cache.ts
Normal 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; // 是否已检查(可选)
|
||||||
|
}
|
2
src/core/types/constant.ts
Normal file
2
src/core/types/constant.ts
Normal 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';
|
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export interface FSABRecentContactParams {
|
export interface FSABRecentContactParams {
|
||||||
anchorPointContact: {
|
anchorPointContact: {
|
||||||
contactId: string;
|
contactId: string;
|
348
src/core/types/element.ts
Normal file
348
src/core/types/element.ts
Normal 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;
|
@@ -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;
|
||||||
}
|
}
|
@@ -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
535
src/core/types/msg.ts
Normal 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
|
||||||
|
}
|
@@ -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;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user