Compare commits

..

14 Commits

Author SHA1 Message Date
手瓜一十雪
f43c1eadd7 release: 1.5.9 2024-06-21 17:26:16 +08:00
手瓜一十雪
ae4b4e7ff9 release: v1.5.9 2024-06-21 17:25:05 +08:00
手瓜一十雪
68b9771fa9 Merge pull request #78 from NapNeko/dependabot/npm_and_yarn/types/uuid-10.0.0
build(deps-dev): bump @types/uuid from 9.0.8 to 10.0.0
2024-06-21 16:50:15 +08:00
手瓜一十雪
114c98f4cf style: lint 2024-06-21 16:49:31 +08:00
手瓜一十雪
685dc74742 build: 1.5.9 - test3 2024-06-21 16:45:36 +08:00
dependabot[bot]
56966961dc build(deps-dev): bump @types/uuid from 9.0.8 to 10.0.0
Bumps [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid) from 9.0.8 to 10.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid)

---
updated-dependencies:
- dependency-name: "@types/uuid"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-21 08:35:16 +00:00
手瓜一十雪
75b8002616 build: 1.5.9 - refactor 2024-06-21 16:24:49 +08:00
手瓜一十雪
6cd2d14e85 build: 1.5.9 - test2 2024-06-21 16:23:53 +08:00
手瓜一十雪
d11ad0585b build: 1.5.9 - test 2024-06-21 15:57:56 +08:00
手瓜一十雪
ccbb641a8e refactor: sign尝试 2024-06-21 13:29:14 +08:00
手瓜一十雪
5695d10a86 refactor: cache 2024-06-21 12:07:40 +08:00
手瓜一十雪
185f167c5f build: 1.5.9 - refactor cache 2024-06-21 11:21:09 +08:00
手瓜一十雪
9cf38a439b refactor: cache 2024-06-21 11:19:36 +08:00
手瓜一十雪
de65cd810c docs: v1.5.9 todo 2024-06-20 21:25:51 +08:00
336 changed files with 8699 additions and 15992 deletions

View File

@@ -1,21 +1,21 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
charset = utf-8
# 2 space indentation
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
indent_style = space
indent_size = 2
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf|crlf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
charset = utf-8
# 2 space indentation
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
indent_style = space
indent_size = 2
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.

View File

@@ -1,4 +1,4 @@
name: "Build Action"
name: "Build"
on:
workflow_dispatch:
push:

View File

@@ -1,4 +1,4 @@
name: "Build Release"
name: "release"
on:
push:
@@ -130,7 +130,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
body_path: CHANGELOG.md
files: |
NapCat.win32.ia32.zip
NapCat.win32.x64.zip
NapCat.linux.x64.zip
NapCat.linux.arm64.zip

View File

@@ -1,69 +0,0 @@
name: "Build Test"
on:
workflow_dispatch:
permissions: write-all
jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target_platform: [linux]
target_arch: [x64, arm64]
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
with:
repository: 'NapNeko/NapCatQQ'
submodules: true
ref: main
token: ${{ secrets.NAPCAT_BUILD }}
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NuCat Linux
run: |
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
path: dist
build-win32:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target_platform: [win32]
target_arch: [x64,ia32]
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
with:
repository: 'NapNeko/NapCatQQ'
submodules: true
ref: main
token: ${{ secrets.NAPCAT_BUILD }}
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NuCat Linux
run: |
npm i --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
npm run build:prod
cd dist
npm i --omit=dev --arch=${{ matrix.target_arch }} --platform=${{ matrix.target_platform }}
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.${{ matrix.target_platform }}.${{ matrix.target_arch }}
path: dist

3
.gitignore vendored
View File

@@ -14,5 +14,4 @@ dist/
# Build
*.db
checkVersion.sh
bun.lockb
checkVersion.sh

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "src/core"]
path = src/core
url = https://github.com/NapNeko/core.git
branch = master

394
LICENSE
View File

@@ -1,373 +1,21 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
MIT License
Copyright (c) 2024 NapCatQQ
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,13 +0,0 @@
# v1.8.2
QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702
## 启动的方式
Way03/Way05
## 新增与调整
1. 多层转发消息接收/发送
2. 消息列表排序修正
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.6.0
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
## 修复与优化
## 新增与调整
* 新增图片subtype属性 区分表情图片与商城图片
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,11 +0,0 @@
# v1.6.1
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
## 修复与优化
## 新增与调整
* 修复poke异常事件
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,13 +0,0 @@
# v1.6.2
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
## 修复与优化
* 修复获取Cookies异常崩溃问题
* 尝试修复成员退群缓存问题
* 修复自身退群后群缓存清理问题
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,13 +0,0 @@
# v1.6.3
QQ Version: Windows 9.9.11-24815 / Linux 3.2.9-24815
## 修复与优化
* 修复带有groupid的私聊消息异常发送到群聊消息
* 尝试修复rws热重载失效问题
* 尝试修复进群事件无法正常获取uin
## 新增与调整
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,18 +0,0 @@
# v1.6.4
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
## 使用前警告
1. 在最近版本由于QQ本体大幅变动为了保证NapCat可用性NapCat近期启动与安装方式将将大幅变动请关注文档和社群获取。
2. 在Core上完全执行开源请不要用于违法用途如此可能造成NapCat完全停止更新。
3. 针对原启动方式的围堵NapCat研发了多种方式除此其余理论与扩展的分析和思路将部分展示于Docs以便各位参与开发与维护NapCat。
## 其余·备注
启动方式: WayBoot.03 Electron Main进程为Node 直接注入代码 同理项目: LiteLoader
## 修复与优化
1. 支持Win平台 9.9.12
2. 修复部分发送图片下载异常情况
## 新增与调整
没有哦
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,18 +0,0 @@
# v1.6.5
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
## 使用前警告
1. 在最近版本由于QQ本体大幅变动为了保证NapCat可用性NapCat近期启动与安装方式将将大幅变动请关注文档和社群获取。
2. 在Core上完全执行开源请不要用于违法用途如此可能造成NapCat完全停止更新。
3. 针对原启动方式的围堵NapCat研发了多种方式除此其余理论与扩展的分析和思路将部分展示于Docs以便各位参与开发与维护NapCat。
## 其余·备注
启动方式: WayBoot.03 Electron Main进程为Node 直接注入代码 同理项目: LiteLoader
## 修复与优化
1. 优化了WrapperNative载入代码
2. 优化缓存
## 新增与调整
没有哦
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -1,17 +0,0 @@
# v1.6.6
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
## 使用前警告
1. 在最近版本由于QQ本体大幅变动为了保证NapCat可用性NapCat近期启动与安装方式将将大幅变动请关注文档和社群获取。
2. 在Core上完全执行开源请不要用于违法用途如此可能造成NapCat完全停止更新。
3. 针对原启动方式的围堵NapCat研发了多种方式除此其余理论与扩展的分析和思路将部分展示于Docs以便各位参与开发与维护NapCat。
## 其余·备注
启动方式: WayBoot.03 Electron Main进程为Node 直接注入代码 同理项目: LiteLoader
## 修复与优化
1. 修复了一些问题
## 新增与调整
没有哦
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

2
docs/develop/Android.md Normal file
View File

@@ -0,0 +1,2 @@
# 开始
jadx 跳转于 `com.tencent.qqnt.kernel.*`

View File

@@ -0,0 +1,42 @@
# Android
```java
GroupMemberExtReq groupMemberExtReq = new GroupMemberExtReq();
groupMemberExtReq.sourceType = MemberExtSourceType.TITLETYPE.ordinal();
groupMemberExtReq.groupCode = longOrNull.longValue();
groupMemberExtReq.beginUin = "0";
groupMemberExtReq.dataTime = "0";
Long[] lArr = new Long[1];
AppInterface a2 = dVar.a();
lArr[0] = Long.valueOf(a2 != null ? a2.getLongAccountUin() : 0L);
arrayListOf = CollectionsKt__CollectionsKt.arrayListOf(lArr);
groupMemberExtReq.uinList = arrayListOf;
MemberExtInfoFilter memberExtInfoFilter = new MemberExtInfoFilter();
memberExtInfoFilter.memberLevelInfoUin = 1;
memberExtInfoFilter.memberLevelInfoPoint = 1;
memberExtInfoFilter.memberLevelInfoActiveDay = 1;
memberExtInfoFilter.memberLevelInfoLevel = 1;
memberExtInfoFilter.levelName = 1;
memberExtInfoFilter.dataTime = 1;
memberExtInfoFilter.sysShowFlag = 1;
memberExtInfoFilter.userShowFlag = 1;
memberExtInfoFilter.userShowFlagNew = 1;
memberExtInfoFilter.levelNameNew = 1;
Unit unit = Unit.INSTANCE;
groupMemberExtReq.memberExtFilter = memberExtInfoFilter;
troopLevelFrequencyControl.f(troopUin, new TroopListRepo$fetchTroopLevelInfo$2(b2, groupMemberExtReq, troopUin, new com.tencent.qqnt.troopmemberlist.report.c("fetchTroopLevelInfo")));
```
# Win
参数解析位于 sub_181456A10(24108) -> wrapper.node(24108)+1456A10
IGroupService.GetMemberExt(param: object);
param展开如下
```
groupCode string
beginUin string
dataTime string
uinList Array<string>
uinNum string
groupType string
richCardNameVer string
sourceType number
memberExtFilter object// 参数解析位于 sub_18145A6D0(24108) -> wrapper.node(24108)+145A6D0
```

View File

@@ -1,4 +1,3 @@
public static final int C2C_PIC_DOWNLOAD = 1004;
public static final String C2C_PIC_DOWNLOAD_DOMAIN = "c2cpicdw.qpic.cn";
public static final String C2C_PIC_DOWNLOAD_QUIC_DOMAIN = "c2cpicdw.quic.qpic.cn";

View File

@@ -1,16 +0,0 @@
# 开发方向
方向一 NativeCall/Hook:
1. 崩溃检测机制的实现
2. Api_Caller 的Hook 可以拿到Event/Handler 进一步提升NC 即时的拦截与处理一些事件比如ReCall拦截
3. Node包装层 进一步分析拿到脱离自带Listener/Adapter可以拿到一些更加底层的数据变动 或许包括更多二进制数据
方向二 全新的无头启动 Way01
1. 基于Node启动原理借助导出符号获取函数地址 再次还原NodeMain
方向三 发包与收包
1. 参考 方向一/3 大概可以收包
2. 发包 (暂时没有计划)
方向四 版本控制
1. 根据不同版本进行逻辑控制
2. 某些参数的自动提取

View File

@@ -0,0 +1,24 @@
# 前排提示
由于Core未处于开源非组织人员无法参与Core开发此处为Core开发提示
# 准备工具
frida ida-pro jadx x64dbg ce 内部调试脚本
## ida-pro
1. 用于快速分析入参和返回类型
2. 通过静态QLog推测语义
3. 提取Listener与Service (常用)
## frida
1. 用于动态获取QLog推测语义
2. 捕捉Native函数 实际入参与数据 分析中间流程
## jadx
1. 通过其它平台实现 静态获取QLog推测语义
2. 提供部分未调用代码 参考
## x64dbg
1. 验证IDA的Hook点
## 内部脚本
1. 提取Listener与Service (不调用无类型 不推荐)
2. 获取NT调用流程

View File

@@ -1,8 +0,0 @@
# Api方向
## getMsgUniqueId √ 已应用
getMsgUniqueId 传入时间 产出一个唯一ID 发送消息作为一个参数
# Native方向
## magic_load
## api_caller
## NodeMain

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "1.8.2",
"version": "1.5.9",
"scripts": {
"watch:dev": "vite --mode development",
"watch:prod": "vite --mode production",
@@ -19,9 +19,10 @@
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"vite-plugin-babel": "^1.2.0",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2",
"@protobuf-ts/plugin": "^2.9.4",
"@rollup/plugin-node-resolve": "^15.2.3",
@@ -30,9 +31,9 @@
"@types/express": "^4.17.21",
"@types/figlet": "^1.5.8",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/jest": "^29.5.12",
"@types/node": "^22.0.0",
"@types/node": "^20.11.30",
"@types/qrcode-terminal": "^0.12.2",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
@@ -46,7 +47,6 @@
"rollup-plugin-obfuscator": "^1.1.0",
"typescript": "^5.3.3",
"vite": "^5.2.6",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-cp": "^4.0.8",
"vite-plugin-dts": "^3.8.2",
"vite-tsconfig-paths": "^4.3.2"
@@ -64,7 +64,9 @@
"json-schema-to-ts": "^3.1.0",
"log4js": "^6.9.1",
"qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1",
"silk-wasm": "^3.3.4",
"sqlite3": "^5.1.7",
"uuid": "^10.0.0",
"ws": "^8.16.0"
}
}

View File

@@ -1,45 +0,0 @@
# Dont Use This Script
# 2024.7.3
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
}
catch {
throw "get QQ path error: $_"
}
}
function Select-QQPath {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialogTitle = "Select QQ.exe"
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
$filePicker.Title = $dialogTitle
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
$filePicker.FilterIndex = 1
$null = $filePicker.ShowDialog()
if (-not ($filePicker.FileName)) {
throw "User did not select an .exe file."
}
return $filePicker.FileName
}
$params = $args -join " "
Try {
$QQpath = Get-QQpath
}
Catch {
$QQpath = Select-QQPath
}
if (!(Test-Path $QQpath)) {
throw "provided QQ path is invalid: $QQpath"
}
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
$env:ELECTRON_RUN_AS_NODE = 1
$commandInfo = Get-Command $QQpath -ErrorAction Stop
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"

View File

@@ -1,123 +0,0 @@
# 检查当前会话是否具有管理员权限
function Test-Administrator {
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if (-not (Test-Administrator)) {
# 如果不是管理员,则重新启动脚本以管理员模式运行
$scriptPath = $myInvocation.MyCommand.Path
if (-not $scriptPath) {
$scriptPath = $PSCommandPath
}
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
$newProcess.Arguments = "-File `"$scriptPath`" $args"
$newProcess.Verb = "runas";
[System.Diagnostics.Process]::Start($newProcess);
exit
}
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
}
catch {
throw "get QQ path error: $_"
}
}
function Select-QQPath {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialogTitle = "Select QQ.exe"
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
$filePicker.Title = $dialogTitle
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
$filePicker.FilterIndex = 1
$null = $filePicker.ShowDialog()
if (-not ($filePicker.FileName)) {
throw "User did not select an .exe file."
}
return $filePicker.FileName
}
# 设置当前工作目录
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
Set-Location $scriptDirectory
# 获取当前目录路径
$currentPath = Get-Location
# 替换\为/
$currentPath = $currentPath -replace '\\', '/'
# 生成JavaScript代码
$jsCode = @"
(async () => {
await import('file:///$currentPath/napcat.mjs');
})();
"@
# 将JavaScript代码保存到文件中
$jsFilePath = Join-Path $currentPath "loadScript.js"
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
$env:NAPCAT_PATH = $jsFilePath
$params = $args -join " "
Try {
$QQpath = Get-QQpath
}
Catch {
$QQpath = Select-QQPath
}
# 拿不到QQ路径则退出
if (!(Test-Path $QQpath)) {
Write-Output "provided QQ path is invalid: $QQpath"
Read-Host "Press any key to continue..."
exit
}
$commandInfo = Get-Command $QQpath -ErrorAction Stop
# 收集dbghelp.dll路径和HASH信息
$QQpath = Split-Path $QQpath
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
$newDllPath = Join-Path $currentPath "dbghelp.dll"
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
# 如果文件一致则跳过
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
if ($processes) {
# 文件占用则退出
Write-Output "dbghelp.dll is in use by the following processes:"
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
Write-Output "dbghelp.dll is in use, cannot continue."
Read-Host "Press any key to continue..."
exit
} else {
# 文件未占用则尝试覆盖
try {
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
Write-Output "dbghelp.dll has been copied to $QQpath"
} catch {
Write-Output "Failed to copy dbghelp.dll: $_"
Read-Host "Press any key to continue..."
exit
}
}
}
# 带参数启动QQ
try {
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
} catch {
Write-Output "Failed to start process as administrator: $_"
Read-Host "Press any key to continue..."
}

View File

@@ -1,28 +0,0 @@
@echo off
chcp 65001
:: 检查是否有管理员权限
net session >nul 2>&1
if %errorlevel% neq 0 (
echo 请求管理员权限...
powershell -Command "Start-Process '%~f0' -Verb runAs"
exit /b
)
:: 如果有管理员权限,继续执行
setlocal enabledelayedexpansion
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("!RetString!") do (
set "pathWithoutUninstall=%%~dpa"
)
set "QQPath=!pathWithoutUninstall!QQ.exe"
echo !QQPath!
"!QQPath!" --enable-logging %*
pause

View File

@@ -1,3 +0,0 @@
REM 全新启动脚本 基于 Hook Native 预计版本1.6.0左右发布
@echo off
pause

Binary file not shown.

View File

@@ -1,20 +0,0 @@
// --------------------
// 2024.7.3 9.9.12 BootWay.03 其余方法暂不公开(此方案为临时方案 Win平台已验证
// 缺陷 (已知)
// 1.与非入侵式不同 现在破坏本体代码
// 2.重启代码与正常启动代码失效
// 3.Win需要补丁
// 4.更新后丢失内容 需要重写此文件
// 5.安装难度上升与周围基础设施失效
// --------------------
const path = require('path');
const CurrentPath = path.dirname(__filename)
const hasNapcatParam = process.argv.includes('--enable-logging');
if (hasNapcatParam) {
(async () => {
await import("file://" + path.join(CurrentPath, './napcat/napcat.mjs'));
})();
} else {
require('./launcher.node').load('external_index', module);
}

View File

@@ -1,18 +0,0 @@
@echo off
setlocal enabledelayedexpansion
chcp 65001
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("!RetString!") do (
set "pathWithoutUninstall=%%~dpa"
)
set "QQPath=!pathWithoutUninstall!"
cd /d !QQPath!
echo !QQPath!
QQ.exe --enable-logging %*

View File

@@ -1,41 +0,0 @@
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\"
}
catch {
throw "get QQ path error: $_"
}
}
function Select-QQPath {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialogTitle = "Select QQ.exe"
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
$filePicker.Title = $dialogTitle
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
$filePicker.FilterIndex = 1
$null = $filePicker.ShowDialog()
if (-not ($filePicker.FileName)) {
throw "User did not select an .exe file."
}
return $filePicker.FileName
}
$params = $args -join " "
Try {
$QQpath = Get-QQpath
}
Catch {
$QQpath = Select-QQPath
}
if (!(Test-Path $QQpath)) {
throw "provided QQ path is invalid: $QQpath"
}
Set-Location -Path $QQpath
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& ./QQ.exe --enable-logging $params}"

View File

@@ -1,17 +0,0 @@
@echo off
setlocal enabledelayedexpansion
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("!RetString!") do (
set "pathWithoutUninstall=%%~dpa"
)
set QQPath=!pathWithoutUninstall!
cd /d !QQPath!
echo !QQPath!
QQ.exe --enable-logging %*

View File

@@ -1,41 +0,0 @@
function Get-QQpath {
try {
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
$uninstallString = $key.UninstallString
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\"
}
catch {
throw "get QQ path error: $_"
}
}
function Select-QQPath {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialogTitle = "Select QQ.exe"
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
$filePicker.Title = $dialogTitle
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
$filePicker.FilterIndex = 1
$null = $filePicker.ShowDialog()
if (-not ($filePicker.FileName)) {
throw "User did not select an .exe file."
}
return $filePicker.FileName
}
$params = $args -join " "
Try {
$QQpath = Get-QQpath
}
Catch {
$QQpath = Select-QQPath
}
if (!(Test-Path $QQpath)) {
throw "provided QQ path is invalid: $QQpath"
}
Set-Location -Path $QQpath
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& ./QQ.exe --enable-logging $params}"

View File

@@ -9,15 +9,7 @@ type RegisterHandler = (res: Response, payload: any) => Promise<any>
export abstract class HttpServerBase {
name: string = 'NapCatQQ';
private readonly expressAPP: Express;
private _server: http.Server | null = null;
public get server(): http.Server | null {
return this._server;
}
private set server(value: http.Server | null) {
this._server = value;
}
private server: http.Server | null = null;
constructor() {
this.expressAPP = express();
@@ -86,7 +78,7 @@ export abstract class HttpServerBase {
this.start(port, host);
}
abstract handleFailed(res: Response, payload: any, err: Error): void
abstract handleFailed(res: Response, payload: any, err: any): void
registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) {
if (!url.startsWith('/')) {
@@ -111,7 +103,7 @@ export abstract class HttpServerBase {
try {
res.send(await handler(res, payload));
} catch (e: any) {
this.handleFailed(res, payload, e);
this.handleFailed(res, payload, e.stack.toString());
}
});
}

View File

@@ -1,5 +1,4 @@
import { WebSocket, WebSocketServer } from 'ws';
import http from 'http';
import urlParse from 'url';
import { IncomingMessage } from 'node:http';
import { log } from '@/common/utils/log';
@@ -28,36 +27,17 @@ export class WebsocketServerBase {
constructor() {
}
start(port: number | http.Server, host: string = '') {
if (port instanceof http.Server) {
try {
const wss = new WebSocketServer({
noServer: true,
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
this.ws = wss;
port.on('upgrade', function upgrade(request, socket, head) {
wss.handleUpgrade(request, socket, head, function done(ws) {
wss.emit('connection', ws, request);
});
});
log('ws服务启动成功, 绑定到HTTP服务');
} catch (e: any) {
throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString());
}
} else {
try {
this.ws = new WebSocketServer({
port,
host: '',
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
log(`ws服务启动成功, ${host}:${port}`);
} catch (e: any) {
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
}
start(port: number, host: string = '') {
try {
this.ws = new WebSocketServer({
port,
host: '',
maxPayload: 1024 * 1024 * 1024
}).on('error', () => {
});
log(`ws服务启动成功, ${host}:${port}`);
} catch (e: any) {
throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString());
}
this.ws.on('connection', (wsClient, req) => {
const url: string = req.url!.split('?').shift() || '/';
@@ -70,12 +50,10 @@ export class WebsocketServerBase {
}
stop() {
if (this.ws) {
this.ws.close((err) => {
if (err) log('ws server close failed!', err);
});
this.ws = null;
}
this.ws && this.ws.close((err) => {
log('ws server close failed!', err);
});
this.ws = null;
}
restart(port: number) {

View File

@@ -0,0 +1,36 @@
import { sleep } from '@/common/utils/helper';
import { logError } from './log';
type AsyncQueueTask = (() => void) | (()=>Promise<void>);
export class AsyncQueue {
private tasks: (AsyncQueueTask)[] = [];
public addTask(task: AsyncQueueTask) {
this.tasks.push(task);
// console.log('addTask', this.tasks.length);
if (this.tasks.length === 1) {
this.runQueue().then().catch(()=>{});
}
}
private async runQueue() {
// console.log('runQueue', this.tasks.length);
while (this.tasks.length > 0) {
const task = this.tasks[0];
// console.log('typeof task', typeof task);
try {
const taskRet = task();
// console.log('type of taskRet', typeof taskRet, taskRet);
if (taskRet instanceof Promise) {
await taskRet;
}
} catch (e) {
// console.error(e);
logError(e);
}
this.tasks.shift();
await sleep(100);
}
}
}

View File

@@ -3,7 +3,6 @@ import fs from 'node:fs';
import { log, logDebug, logError } from '@/common/utils/log';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { selfInfo } from '@/core/data';
const __filename = fileURLToPath(import.meta.url);
@@ -13,9 +12,8 @@ const configDir = path.resolve(__dirname, 'config');
fs.mkdirSync(configDir, { recursive: true });
export class ConfigBase<T> {
public name: string = 'default_config';
private pathName: string | null = null; // 本次读取的文件路径
export class ConfigBase<T>{
constructor() {
}
@@ -24,28 +22,19 @@ export class ConfigBase<T> {
return null;
}
getConfigDir() {
getConfigDir(){
const configDir = path.resolve(__dirname, 'config');
fs.mkdirSync(configDir, { recursive: true });
return configDir;
}
getConfigPath(pathName: string | null): string {
const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.getConfigDir(), filename);
getConfigPath(): string {
throw new Error('Method not implemented.');
}
read() {
// 尝试加载当前账号配置
if (this.read_from_file(selfInfo.uin, false)) return this;
// 尝试加载默认配置
return this.read_from_file('', true);
}
read_from_file(pathName: string, createIfNotExist: boolean) {
const configPath = this.getConfigPath(pathName);
const configPath = this.getConfigPath();
if (!fs.existsSync(configPath)) {
if (!createIfNotExist) return null;
this.pathName = pathName; // 记录有效的设置文件
try {
try{
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
log(`配置文件${configPath}已创建\n如果修改此文件后需要重启 NapCat 生效`);
}
@@ -54,7 +43,6 @@ export class ConfigBase<T> {
}
return this;
}
try {
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
logDebug(`配置文件${configPath}已加载`, data);
@@ -73,13 +61,9 @@ export class ConfigBase<T> {
}
}
save(config: T, overwrite: boolean = false) {
save(config: T) {
Object.assign(this, config);
if (overwrite) {
// 用户要求强制写入,则变更当前文件为目标文件
this.pathName = `${selfInfo.uin}`;
}
const configPath = this.getConfigPath(this.pathName);
const configPath = this.getConfigPath();
try {
fs.writeFileSync(configPath, JSON.stringify(this, this.getKeys(), 2));
} catch (e: any) {

View File

@@ -1,3 +1,4 @@
import { NodeIKernelMsgListener } from '@/core';
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
import { randomUUID } from 'crypto';
@@ -5,7 +6,6 @@ interface Internal_MapKey {
timeout: number,
createtime: number,
func: (...arg: any[]) => any,
checker: ((...args: any[]) => boolean) | undefined,
}
export class ListenerClassBase {
@@ -84,19 +84,17 @@ export class NTEventWrapper {
}
//统一回调清理事件
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args);
//console.log(ListenerMainName, this.EventTask.get(ListenerMainName), ListenerSubName, this.EventTask.get(ListenerMainName)?.get(ListenerSubName));
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout);
if (task.createtime + task.timeout < Date.now()) {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid);
return;
}
if (task.checker && task.checker(...args)) {
task.func(...args);
}
task.func(...args);
});
}
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any>,>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.CreatEventFunction<EventType>(EventName);
let complete = false;
@@ -110,71 +108,27 @@ export class NTEventWrapper {
resolve(retData);
});
}
async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
const databack = () => {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'));
} else {
resolve(retData!);
}
};
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: Parameters<ListenerType>) => {
complete++;
retData = args;
if (complete >= waitTimes) {
clearTimeout(Timeouter);
databack();
}
}
};
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map());
}
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.CreatListenerFunction(ListenerMainName);
});
}
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
const id = randomUUID();
let complete = 0;
let retData: Parameters<ListenerType> | undefined = undefined;
let retEvent: any = {};
const databack = () => {
if (complete == 0) {
reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
if (complete < waitTimes) {
reject(new Error('NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' timeout'));
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
}
};
const Timeouter = setTimeout(databack, timeout);
const ListenerNameList = ListenerName.split('/');
const ListenerMainName = ListenerNameList[0];
const ListenerSubName = ListenerNameList[1];
const Timeouter = setTimeout(databack, timeout);
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: any[]) => {
complete++;
//console.log('func', ...args);
@@ -194,6 +148,7 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
this.CreatListenerFunction(ListenerMainName);
const EventFunc = this.CreatEventFunction<EventType>(EventName);
//console.log("测试打点", args);
retEvent = await EventFunc!(...(args as any[]));
});
}

View File

@@ -0,0 +1,145 @@
import { logError, logDebug } from '@/common/utils/log';
type group_id = number;
type user_id = number;
class cacheNode<T> {
value: T;
groupId: group_id;
userId: user_id;
prev: cacheNode<T> | null;
next: cacheNode<T> | null;
timestamp: number;
constructor(groupId: group_id, userId: user_id, value: T) {
this.groupId = groupId;
this.userId = userId;
this.value = value;
this.prev = null;
this.next = null;
this.timestamp = Date.now();
}
}
type cache<T> = { [key: group_id]: { [key: user_id]: cacheNode<T> } };
class LRU<T> {
private maxAge: number;
private maxSize: number;
private currentSize: number;
private cache: cache<T>;
private head: cacheNode<T> | null = null;
private tail: cacheNode<T> | null = null;
private onFuncs: ((node: cacheNode<T>) => void)[] = [];
constructor(maxAge: number = 2e4, maxSize: number = 5e3) {
this.maxAge = maxAge;
this.maxSize = maxSize;
this.cache = Object.create(null);
this.currentSize = 0;
if (maxSize == 0) return;
setInterval(() => this.removeExpired(), this.maxAge);
}
// 移除LRU节点
private removeLRUNode(node: cacheNode<T>) {
logDebug(
'removeLRUNode',
node.groupId,
node.userId,
node.value,
this.currentSize
);
node.prev = node.next = null;
delete this.cache[node.groupId][node.userId];
this.removeNode(node);
this.onFuncs.forEach((func) => func(node));
this.currentSize--;
}
public on(func: (node: cacheNode<T>) => void) {
this.onFuncs.push(func);
}
private removeExpired() {
const now = Date.now();
let current = this.tail;
const nodesToRemove: cacheNode<T>[] = [];
let removedCount = 0;
// 收集需要删除的节点
while (current && now - current.timestamp > this.maxAge) {
nodesToRemove.push(current);
current = current.prev;
removedCount++;
if (removedCount >= 100) break;
}
// 更新链表指向
if (nodesToRemove.length > 0) {
const newTail = nodesToRemove[nodesToRemove.length - 1].prev;
if (newTail) {
newTail.next = null;
} else {
this.head = null;
}
this.tail = newTail;
}
nodesToRemove.forEach((node) => {
node.prev = node.next = null;
delete this.cache[node.groupId][node.userId];
this.currentSize--;
this.onFuncs.forEach((func) => func(node));
});
}
private addNode(node: cacheNode<T>) {
node.next = this.head;
if (this.head) this.head.prev = node;
if (!this.tail) this.tail = node;
this.head = node;
}
private removeNode(node: cacheNode<T>) {
if (node.prev) node.prev.next = node.next;
if (node.next) node.next.prev = node.prev;
if (node === this.head) this.head = node.next;
if (node === this.tail) this.tail = node.prev;
}
private moveToHead(node: cacheNode<T>) {
if (this.head === node) return;
this.removeNode(node);
this.addNode(node);
node.prev = null;
}
public set(groupId: group_id, userId: user_id, value: T) {
if (!this.cache[groupId]) {
this.cache[groupId] = Object.create(null);
}
const groupObject = this.cache[groupId];
if (groupObject[userId]) {
const node = groupObject[userId];
node.value = value;
node.timestamp = Date.now();
this.moveToHead(node);
} else {
const node = new cacheNode(groupId, userId, value);
groupObject[userId] = node;
this.currentSize++;
this.addNode(node);
if (this.currentSize > this.maxSize) {
const tail = this.tail!;
this.removeLRUNode(tail);
}
}
}
}
export default LRU;

View File

@@ -1,40 +1,20 @@
import { Peer } from '@/core';
import crypto, { randomInt, randomUUID } from 'crypto';
import { logError } from './log';
import crypto from 'crypto';
export class LimitedHashTable<K, V> {
class LimitedHashTable<K, V> {
private keyToValue: Map<K, V> = new Map();
private valueToKey: Map<V, K> = new Map();
private maxSize: number;
private KeyQueneList: K[] = [];
private ValueQueneList: V[] = [];
constructor(maxSize: number) {
this.maxSize = maxSize;
}
resize(count: number) {
this.maxSize = count;
}
set(key: K, value: V): void {
// const isExist = this.keyToValue.get(key);
// if (isExist && isExist === value) {
// return;
// }
this.keyToValue.set(key, value);
this.valueToKey.set(value, key);
while (this.keyToValue.size !== this.valueToKey.size) {
console.log('keyToValue.size !== valueToKey.size Error Atom');
this.keyToValue.clear();
this.valueToKey.clear();
}
// console.log('---------------');
// console.log(this.keyToValue);
// console.log(this.valueToKey);
// console.log('---------------');
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
//console.log(this.keyToValue.size > this.maxSize, this.valueToKey.size > this.maxSize);
const oldestKey = this.keyToValue.keys().next().value;
this.valueToKey.delete(this.keyToValue.get(oldestKey)!);
this.keyToValue.delete(oldestKey);
if (this.KeyQueneList.length >= this.maxSize || this.ValueQueneList.length >= this.maxSize) {
this.KeyQueneList.shift();
this.ValueQueneList.shift();
}
}
@@ -46,99 +26,28 @@ export class LimitedHashTable<K, V> {
return this.valueToKey.get(value);
}
deleteByValue(value: V): void {
const key = this.valueToKey.get(value);
if (key !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
deleteByKey(key: K): void {
delete(key: K): void {
const value = this.keyToValue.get(key);
if (value !== undefined) {
this.keyToValue.delete(key);
this.valueToKey.delete(value);
}
}
getKeyList(): K[] {
return Array.from(this.keyToValue.keys());
}
//获取最近刚写入的几个值
getHeads(size: number): { key: K; value: V }[] | undefined {
const keyList = this.getKeyList();
if (keyList.length === 0) {
return undefined;
}
const result: { key: K; value: V }[] = [];
const listSize = Math.min(size, keyList.length);
for (let i = 0; i < listSize; i++) {
const key = keyList[listSize - i];
result.push({ key, value: this.keyToValue.get(key)! });
}
return result;
}
}
class MessageUniqueWrapper {
private msgDataMap: LimitedHashTable<string, number>;
private msgIdMap: LimitedHashTable<string, number>;
constructor(maxMap: number = 1000) {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
private msgIdMap: LimitedHashTable<number, string> = new LimitedHashTable(1000);
createMsg(MsgId: string) {
const ShortId = parseInt(crypto.createHash('sha1').update('2345').digest('hex').slice(0, 8), 16);
this.msgIdMap.set(ShortId, MsgId);
return ShortId;
}
getRecentMsgIds(Peer: Peer, size: number): string[] {
const heads = this.msgIdMap.getHeads(size);
if (!heads) {
return [];
}
const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value));
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid);
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined);
getMsgIdByShortId(ShortId: number) {
return this.msgIdMap.getValue(ShortId);
}
createMsg(peer: Peer, msgId: string): number | undefined {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key).digest();
//设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f;
const shortId = hash.readInt32BE(0);
//减少性能损耗
// const isExist = this.msgIdMap.getKey(shortId);
// if (isExist && isExist === msgId) {
// return shortId;
// }
this.msgIdMap.set(msgId, shortId);
this.msgDataMap.set(key, shortId);
return shortId;
}
getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined {
const data = this.msgDataMap.getKey(shortId);
if (data) {
const [msgId, chatTypeStr, peerUid] = data.split('|');
const peer: Peer = {
chatType: parseInt(chatTypeStr),
peerUid,
guildId: '',
};
return { MsgId: msgId, Peer: peer };
}
return undefined;
}
getShortIdByMsgId(msgId: string): number | undefined {
return this.msgIdMap.getValue(msgId);
}
getPeerByMsgId(msgId: string) {
const shortId = this.msgIdMap.getValue(msgId);
if (!shortId) return undefined;
return this.getMsgIdAndPeerByShortId(shortId);
}
resize(maxSize: number): void {
this.msgIdMap.resize(maxSize);
this.msgDataMap.resize(maxSize);
getShortIdByMsgId(MsgId: string) {
return this.msgIdMap.getKey(MsgId);
}
}
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();
export const MessageUnique = new MessageUniqueWrapper();

View File

@@ -1,57 +1,78 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { systemPlatform } from '@/common/utils/system';
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { log } from './log';
import { logError } from '@/common/utils/log';
//基础目录获取
export const QQMainPath = process.execPath;
export const QQPackageInfoPath: string = path.join(path.dirname(QQMainPath), 'resources', 'app', 'package.json');
export const QQVersionConfigPath: string | undefined = getQQVersionConfigPath(QQMainPath);
export const exePath = process.execPath;
//基础信息获取 无快更则启用默认模板填充
export const isQuickUpdate: boolean = !!QQVersionConfigPath;
export const QQVersionConfig: QQVersionConfigType = isQuickUpdate ? JSON.parse(fs.readFileSync(QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo();
export const QQPackageInfo: QQPackageInfoType = JSON.parse(fs.readFileSync(QQPackageInfoPath).toString());
export const { appid: QQVersionAppid, qua: QQVersionQua } = getAppidV2();
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
let configVersionInfoPath;
//基础函数
export function getQQBuildStr() {
return isQuickUpdate ? QQVersionConfig.buildId : QQPackageInfo.buildVersion;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
export function getFullQQVesion() {
return isQuickUpdate ? QQVersionConfig.curVersion : QQPackageInfo.version;
if (typeof configVersionInfoPath !== 'string') {
throw new Error('Something went wrong when load QQ info path');
}
export function requireMinNTQQBuild(buildStr: string) {
return parseInt(getQQBuildStr()) >= parseInt(buildStr);
export { configVersionInfoPath };
type QQPkgInfo = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
}
//此方法不要直接使用
export function getQUAInternal() {
return systemPlatform === 'linux' ? `V1_LNX_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B`;
type QQVersionConfigInfo = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
}
export function getAppidV2(): { appid: string, qua: string } {
const appidTbale = AppidTable as unknown as QQAppidTableType;
let _qqVersionConfigInfo: QQVersionConfigInfo = {
'baseVersion': '9.9.11-24568',
'curVersion': '9.9.11-24568',
'prevVersion': '',
'onErrorVersions': [],
'buildId': '24568'
};
if (fs.existsSync(configVersionInfoPath)) {
try {
const data = appidTbale[getFullQQVesion()];
if (data) {
return data;
}
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
} catch (e) {
logError('Load QQ version config info failed, Use default version', e);
}
catch (e) {
log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 以下是兜底措施
log(`[QQ版本兼容性检测] ${getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: getQUAInternal() };
}
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
//V1_WIN_NQ_9.9.11_24568_GW_B
export const qqPkgInfo: QQPkgInfo = JSON.parse(fs.readFileSync(pkgInfoPath).toString());
// platform_type: 3,
// app_type: 4,
// app_version: '9.9.12-25765',
// qua: 'V1_WIN_NQ_9.9.12_25765_GW_B',
// appid: '537234702',
// app_version: '9.9.9-23159',
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
// appid: '537213764',
// platVer: '10.0.26100',
// clientVer: '9.9.9-25765',
// clientVer: '9.9.9-23159',
//Android
//V1_AND_SQ_9.0.60_6478_YYB_D
// Linux
// app_version: '3.2.9-25765',
// qua: 'V1_LNX_NQ_3.2.10_25765_GW_B',
// app_version: '3.2.9-24568',
// qua: 'V1_LNX_NQ_3.2.9_24568_GW_B',
let _appid: string = '537226369'; // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') {
_appid = '537226441';
}
// todo: mac 平台的 appid
export const appid = _appid;

View File

@@ -1,9 +1,9 @@
import fs from 'fs';
import { encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm';
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm';
import fsPromise from 'fs/promises';
import { log, logError } from './log';
import path from 'node:path';
import { randomUUID } from 'crypto';
import { v4 as uuidv4 } from 'uuid';
import { spawn } from 'node:child_process';
import { getTempDir } from '@/common/utils/file';
@@ -63,11 +63,10 @@ export async function encodeSilk(filePath: string) {
// }
try {
const file = await fsPromise.readFile(filePath);
const pttPath = path.join(TEMP_DIR, randomUUID());
if (!isSilk(file)) {
const pttPath = path.join(TEMP_DIR, uuidv4());
if (getFileHeader(filePath) !== '02232153494c4b') {
log(`语音文件${filePath}需要转换成silk`);
const _isWav = isWav(file);
const _isWav = await isWavFile(filePath);
const pcmPath = pttPath + '.pcm';
let sampleRate = 0;
const convert = () => {
@@ -97,7 +96,7 @@ export async function encodeSilk(filePath: string) {
if (!_isWav) {
input = await convert();
} else {
input = file;
input = fs.readFileSync(filePath);
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
const { fmt } = getWavFileInfo(input);
// log(`wav文件信息`, fmt)
@@ -114,7 +113,7 @@ export async function encodeSilk(filePath: string) {
duration: silk.duration / 1000
};
} else {
const silk = file;
const silk = fs.readFileSync(filePath);
let duration = 0;
try {
duration = getDuration(silk) / 1000;

449
src/common/utils/db.ts Normal file
View File

@@ -0,0 +1,449 @@
import { ElementType, FileElement, PicElement, PttElement, RawMessage, VideoElement } from '../../core/src/entities';
import sqlite3 from 'sqlite3';
import { log, logDebug, logError } from '@/common/utils/log';
import { NTQQMsgApi } from '@/core';
import LRU from '@/common/utils/LRUCache';
export interface IRember {
last_sent_time: number;
join_time: number;
user_id: number;
}
type DBMsg = {
id: number,
shortId: number,
longId: string,
seq: number,
peerUid: string,
chatType: number,
}
type DBFile = {
name: string; // 文件名
path: string;
url: string;
size: number;
uuid: string;
msgId: string;
elementId: string;
element: PicElement | VideoElement | FileElement | PttElement;
elementType: ElementType.PIC | ElementType.VIDEO | ElementType.FILE | ElementType.PTT;
}
class DBUtilBase {
protected db: sqlite3.Database | undefined;
async init(dbPath: string) {
if (this.db) {
return;
}
return new Promise<void>((resolve, reject) => {
this.db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
if (err) {
logError('Could not connect to database', err);
reject(err);
return;
}
this.createTable();
resolve();
});
});
}
protected createTable() {
throw new Error('Method not implemented.');
}
close() {
this.db?.close();
}
}
class DBUtil extends DBUtilBase {
private msgCache: Map<string | number, RawMessage> = new Map<string | number, RawMessage>();
private globalMsgShortId = -2147483640;
private groupIds: number[] = [];
private LURCache = new LRU<number>();
private LastSentCache = new (class {
private cache: { gid: number; uid: number }[] = [];
private maxSize: number;
constructor(maxSize: number = 5000) {
this.maxSize = maxSize;
}
get(gid: number, uid: number): boolean {
const exists = this.cache.some(
(entry) => entry.gid === gid && entry.uid === uid
);
if (!exists) {
this.cache.push({ gid, uid });
if (this.cache.length > this.maxSize) {
this.cache.shift();
}
}
return exists;
}
})();
constructor() {
super();
const interval = 1000 * 60 * 10; // 10分钟清理一次缓存
setInterval(() => {
logDebug('清理消息缓存');
this.msgCache.forEach((msg, key) => {
if ((Date.now() - parseInt(msg.msgTime) * 1000) > interval) {
this.msgCache.delete(key);
}
});
}, interval);
}
async init(dbPath: string) {
await super.init(dbPath);
this.globalMsgShortId = await this.getCurrentMaxShortId();
// 初始化群缓存列表
this.db!.serialize(() => {
const sql = 'SELECT * FROM sqlite_master WHERE type=\'table\'';
this.db!.all(sql, [], (err, rows: { name: string }[]) => {
if (err) return logError(err);
rows.forEach((row) => this.groupIds.push(parseInt(row.name)));
//logDebug(`已加载 ${groupIds.length} 个群`);
});
});
this.LURCache.on(async (node) => {
const { value: time, groupId, userId } = node;
logDebug('插入发言时间', userId, groupId);
await this.createGroupInfoTimeTableIfNotExist(groupId);
const method = await this.getDataSetMethod(groupId, userId);
logDebug('插入发言时间方法判断', userId, groupId, method);
const sql =
method == 'update'
? `UPDATE "${groupId}" SET last_sent_time = ? WHERE user_id = ?`
: `INSERT INTO "${groupId}" (last_sent_time, user_id) VALUES (?, ?)`;
this.db!.all(sql, [time, userId], (err) => {
if (err) {
return logError('插入/更新发言时间失败', userId, groupId);
}
logDebug('插入/更新发言时间成功', userId, groupId);
});
});
}
async getDataSetMethod(groupId: number, userId: number) {
// 缓存记录
if (this.LastSentCache.get(groupId, userId)) {
logDebug('缓存命中', userId, groupId);
return 'update';
}
// 数据库判断
return new Promise<'insert' | 'update'>((resolve, reject) => {
this.db!.all(
`SELECT * FROM "${groupId}" WHERE user_id = ?`,
[userId],
(err, rows) => {
if (err) {
logError('查询发言时间存在失败', userId, groupId, err);
return logError('插入发言时间失败', userId, groupId, err);
}
if (rows.length === 0) {
logDebug('查询发言时间不存在', userId, groupId);
return resolve('insert');
}
logDebug('查询发言时间存在', userId, groupId);
resolve('update');
}
);
});
}
async createGroupInfoTimeTableIfNotExist(groupId: number) {
const createTableSQL = (groupId: number) =>
`CREATE TABLE IF NOT EXISTS "${groupId}" (
user_id INTEGER,
last_sent_time INTEGER,
join_time INTEGER,
PRIMARY KEY (user_id)
);`;
if (this.groupIds.includes(groupId)) {
return;
}
return new Promise((resolve, reject) => {
const sql = createTableSQL(groupId);
this.db!.all(sql, (err) => {
if (err) {
reject(err);
return;
}
this.groupIds.push(groupId);
resolve(true);
});
});
}
protected createTable() {
// 消息记录
const createTableSQL = `
CREATE TABLE IF NOT EXISTS msgs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
shortId INTEGER NOT NULL UNIQUE,
longId TEXT NOT NULL UNIQUE,
seq INTEGER NOT NULL,
peerUid TEXT NOT NULL,
chatType INTEGER NOT NULL
)`;
this.db!.run(createTableSQL, function (err) {
if (err) {
logError('Could not create table msgs', err.stack);
}
});
// 文件缓存
const createFileTableSQL = `
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
path TEXT NOT NULL,
url TEXT,
size INTEGER NOT NULL,
uuid TEXT,
elementType INTEGER,
element TEXT NOT NULL,
elementId TEXT NOT NULL,
msgId TEXT NOT NULL
)`;
this.db!.run(createFileTableSQL, function (err) {
if (err) {
logError('Could not create table files', err);
}
});
}
private async getCurrentMaxShortId() {
return new Promise<number>((resolve, reject) => {
this.db!.get('SELECT MAX(shortId) as maxId FROM msgs', (err, row: { maxId: number }) => {
if (err) {
logDebug('Could not get max short id, Use default -2147483640', err);
return resolve(-2147483640);
}
logDebug('数据库中消息最大短id', row?.maxId);
resolve(row?.maxId ?? -2147483640);
});
});
}
private async getMsg(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<RawMessage | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBMsg) => {
// log("getMsg", row, err);
if (err) {
logError('Could not get msg', err, query, params);
return resolve(null);
}
if (!row) {
// logDebug('不存在数据库中的消息,不进行处理', query, params);
resolve(null);
return;
}
const msgId = row.longId;
NTQQMsgApi.getMsgsByMsgId({ peerUid: row.peerUid, chatType: row.chatType }, [msgId]).then(res => {
const msg = res.msgList[0];
if (!msg) {
resolve(null);
return;
}
msg.id = row.shortId;
resolve(msg);
}).catch(e => {
resolve(null);
});
});
});
}
async getMsgByShortId(shortId: number): Promise<RawMessage | null> {
if (this.msgCache.has(shortId)) {
return this.msgCache.get(shortId)!;
}
const getStmt = 'SELECT * FROM msgs WHERE shortId = ?';
return this.getMsg(getStmt, [shortId]);
}
async getMsgByLongId(longId: string): Promise<RawMessage | null> {
if (this.msgCache.has(longId)) {
return this.msgCache.get(longId)!;
}
return this.getMsg('SELECT * FROM msgs WHERE longId = ?', [longId]);
}
async getMsgBySeq(peerUid: string, seq: string): Promise<RawMessage | null> {
const stmt = 'SELECT * FROM msgs WHERE peerUid = ? AND seq = ?';
return this.getMsg(stmt, [peerUid, seq]);
}
async addMsg(msg: RawMessage, update = true): Promise<number> {
const existMsg = await this.getMsgByLongId(msg.msgId);
if (existMsg) {
// logDebug('消息已存在,更新数据库', msg.msgId);
if (update) this.updateMsg(msg).then();
return existMsg.id!;
}
const stmt = this.db!.prepare('INSERT INTO msgs (shortId, longId, seq, peerUid, chatType) VALUES (?, ?, ?, ?, ?)');
// const runAsync = promisify(stmt.run.bind(stmt));
const shortId = ++this.globalMsgShortId;
msg.id = shortId;
//logDebug(`记录消息到数据库, 消息长id: ${msg.msgId}, 短id: ${msg.id}`);
this.msgCache.set(shortId, msg);
this.msgCache.set(msg.msgId, msg);
stmt.run(this.globalMsgShortId, msg.msgId, msg.msgSeq.toString(), msg.peerUid, msg.chatType, (err: any) => {
if (err) {
if (err.errno === 19) {
this.getMsgByLongId(msg.msgId).then((msg: RawMessage | null) => {
if (msg) {
this.msgCache.set(shortId, msg);
this.msgCache.set(msg.msgId, msg);
// logDebug('获取消息短id成功', msg.id);
} else {
logError('db could not get msg by long id', err);
}
}).catch(e => logError('db getMsgByLongId error', e));
} else {
logError('db could not add msg', err);
}
}
});
return shortId;
}
async updateMsg(msg: RawMessage) {
const existMsg = this.msgCache.get(msg.msgId);
if (existMsg) {
Object.assign(existMsg, msg);
}
//logDebug(`更新消息, shortId:${msg.id}, seq: ${msg.msgSeq}, msgId: ${msg.msgId}`);
const stmt = this.db!.prepare('UPDATE msgs SET seq=? WHERE longId=?');
stmt.run(msg.msgSeq, msg.msgId, (err: any) => {
if (err) {
logError('updateMsg db error', err);
}
});
}
async addFileCache(file: DBFile) {
const stmt = this.db!.prepare('INSERT INTO files (name, path, url, size, uuid, elementType ,element, elementId, msgId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
return new Promise((resolve, reject) => {
stmt.run(file.name, file.path, file.url, file.size, file.uuid,
file.elementType,
JSON.stringify(file.element),
file.elementId,
file.msgId,
function (err: any) {
if (err) {
logError('db could not add file', err);
reject(err);
}
resolve(null);
});
});
}
private async getFileCache(query: string, params: any[]) {
const stmt = this.db!.prepare(query);
return new Promise<DBFile | null>((resolve, reject) => {
stmt.get(...params, (err: any, row: DBFile & { element: string }) => {
if (err) {
logError('db could not get file cache', err);
reject(err);
}
if (row) {
row.element = JSON.parse(row.element);
}
resolve(row);
});
});
}
async getFileCacheByName(name: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE name = ?', [name]);
}
async getFileCacheByUuid(uuid: string): Promise<DBFile | null> {
return this.getFileCache('SELECT * FROM files WHERE uuid = ?', [uuid]);
}
// todo: 是否所有的文件都有uuid语音消息有没有uuid
async updateFileCache(file: DBFile) {
const stmt = this.db!.prepare('UPDATE files SET path = ?, url = ? WHERE uuid = ?');
return new Promise((resolve, reject) => {
stmt.run(file.path, file.url, file.uuid, function (err: any) {
if (err) {
logError('db could not update file cache', err);
reject(err);
}
resolve(null);
});
});
}
async getLastSentTimeAndJoinTime(
groupId: number
): Promise<IRember[]> {
logDebug('读取发言时间', groupId);
return new Promise<IRember[]>((resolve, reject) => {
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
if (err) {
logError('查询发言时间失败', groupId);
return resolve([]);
}
logDebug('查询发言时间成功', groupId, rows);
resolve(rows);
});
});
}
insertLastSentTime(
groupId: number,
userId: number,
time: number
) {
this.LURCache.set(groupId, userId, time);
}
async insertJoinTime(
groupId: number,
userId: number,
time: number
) {
await this.createGroupInfoTimeTableIfNotExist(groupId);
this.db!.all(
`INSERT OR REPLACE INTO "${groupId}" (user_id, last_sent_time, join_time) VALUES (?,?,?)`,
[userId, time, time],
(err) => {
if (err)
logError(err),
Promise.reject(),
logError('插入入群时间失败', userId, groupId);
}
);
}
}
export const dbUtil = new DBUtil();

View File

@@ -4,8 +4,9 @@ import crypto from 'crypto';
import util from 'util';
import path from 'node:path';
import { log, logError } from './log';
import { dbUtil } from '@/common/utils/db';
import * as fileType from 'file-type';
import { randomUUID } from 'crypto';
import { v4 as uuidv4 } from 'uuid';
import { napCatCore } from '@/core';
export const getNapCatDir = () => {
@@ -147,8 +148,6 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
};
if (typeof options === 'string') {
url = options;
const host = new URL(url).hostname;
headers['Host'] = host;
} else {
url = options.url;
if (options.headers) {
@@ -159,12 +158,7 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
}
}
}
const fetchRes = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause;
}
throw err;
});
const fetchRes = await fetch(url, { headers });
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
const blob = await fetchRes.blob();
@@ -181,7 +175,7 @@ type Uri2LocalRes = {
isLocal: boolean
}
export async function uri2local(UriOrPath: string, fileName: string | null = null): Promise<Uri2LocalRes> {
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> {
const res = {
success: false,
errMsg: '',
@@ -190,29 +184,26 @@ export async function uri2local(UriOrPath: string, fileName: string | null = nul
path: '',
isLocal: false
};
if (!fileName) fileName = randomUUID();
let filePath = path.join(getTempDir(), fileName);//临时目录
if (!fileName) {
fileName = uuidv4();
}
let filePath = path.join(getTempDir(), fileName);
let url = null;
//区分path和uri
try {
if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath);
} catch (error: any) { }
try {
url = new URL(UriOrPath);
} catch (error: any) { }
//验证url
if (!url) {
res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`;
url = new URL(uri);
} catch (e: any) {
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`;
return res;
}
// log("uri protocol", url.protocol, uri);
if (url.protocol == 'base64:') {
// base64转成文件
const base64Data = UriOrPath.split('base64://')[1];
const base64Data = uri.split('base64://')[1];
try {
const buffer = Buffer.from(base64Data, 'base64');
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = 'base64文件下载失败,' + e.toString();
return res;
@@ -221,7 +212,7 @@ export async function uri2local(UriOrPath: string, fileName: string | null = nul
// 下载文件
let buffer: Buffer | null = null;
try {
buffer = await httpDownload(UriOrPath);
buffer = await httpDownload(uri);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
@@ -237,7 +228,7 @@ export async function uri2local(UriOrPath: string, fileName: string | null = nul
}
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
res.fileName = fileName;
filePath = path.join(getTempDir(), randomUUID() + fileName);
filePath = path.join(getTempDir(), uuidv4() + fileName);
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
@@ -253,16 +244,15 @@ export async function uri2local(UriOrPath: string, fileName: string | null = nul
} else {
filePath = pathname;
}
} else {
const cache = await dbUtil.getFileCacheByName(uri);
if (cache) {
filePath = cache.path;
} else {
filePath = uri;
}
}
else {
// 26702执行forword file文件操作 不应该在这里乱来
// const cache = await dbUtil.getFileCacheByName(uri);
// if (cache) {
// filePath = cache.path;
// } else {
// filePath = uri;
// }
}
res.isLocal = true;
}
// else{

View File

@@ -1,58 +1,17 @@
import crypto from 'node:crypto';
import path from 'node:path';
import fs from 'fs';
import fs from 'fs/promises';
import { log, logDebug } from './log';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import * as fsPromise from 'node:fs/promises';
import os from 'node:os';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
//下面这个类是用于将uid+msgid合并的类
export class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr);
const low = BigInt(lowStr);
const highHex = high.toString(16).padStart(16, '0');
const lowHex = low.toString(16).padStart(16, '0');
const combinedHex = highHex + lowHex;
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(12, 16)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`;
return uuid;
}
static decode(uuid: string): { high: string, low: string } {
const hex = uuid.replace(/-/g, '');
const high = BigInt('0x' + hex.substring(0, 16));
const low = BigInt('0x' + hex.substring(16));
return { high: high.toString(), low: low.toString() };
}
}
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function PromiseTimer<T>(promise: Promise<T>, ms: number): Promise<T> {
const timeoutPromise = new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms)
);
return Promise.race([promise, timeoutPromise]);
}
export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number): Promise<T[]> {
const wrappedTasks = tasks.map(task =>
PromiseTimer(task, timeout).then(
result => ({ status: 'fulfilled', value: result }),
error => ({ status: 'rejected', reason: error })
)
);
const results = await Promise.all(wrappedTasks);
return results
.filter(result => result.status === 'fulfilled')
.map(result => (result as { status: 'fulfilled'; value: T }).value);
}
export function getMd5(s: string) {
const h = crypto.createHash('md5');
@@ -130,36 +89,7 @@ export function CacheClassFuncAsync(ttl: number = 3600 * 1000, customKey: string
}
return logExecutionTime;
}
export function CacheClassFuncAsyncExtend(ttl: number = 3600 * 1000, customKey: string = '', checker: any = (...data: any[]) => { return true; }) {
//console.log('CacheClassFuncAsync', ttl, customKey);
function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) {
//console.log('logExecutionTime', target, methodName, descriptor);
const cache = new Map<string, { expiry: number; value: any }>();
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`;
cache.forEach((value, key) => {
if (value.expiry < Date.now()) {
cache.delete(key);
}
});
const cachedValue = cache.get(key);
if (cachedValue && cachedValue.expiry > Date.now()) {
return cachedValue.value;
}
// const start = Date.now();
const result = await originalMethod.apply(this, args);
if (!checker(...args, result)) {
return result;//丢弃缓存
}
// const end = Date.now();
// console.log(`Method ${methodName} executed in ${end - start} ms.`);
cache.set(key, { expiry: Date.now() + ttl, value: result });
return result;
};
}
return logExecutionTime;
}
// export function CacheClassFuncAsync(ttl: number = 3600 * 1000, customKey: string = ''): any {
// const cache = new Map<string, { expiry: number; value: any }>();
@@ -305,14 +235,14 @@ export function migrateConfig(oldConfig: any) {
}
// 升级旧的配置到新的
export async function UpdateConfig() {
const configFiles = await fsPromise.readdir(path.join(__dirname, 'config'));
const configFiles = await fs.readdir(path.join(__dirname, 'config'));
for (const file of configFiles) {
if (file.match(/^onebot11_\d+.json$/)) {
const CurrentConfig = JSON.parse(await fsPromise.readFile(path.join(__dirname, 'config', file), 'utf8'));
const CurrentConfig = JSON.parse(await fs.readFile(path.join(__dirname, 'config', file), 'utf8'));
if (isValidOldConfig(CurrentConfig)) {
log('正在迁移旧配置到新配置 File:', file);
const NewConfig = migrateConfig(CurrentConfig);
await fsPromise.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
await fs.writeFile(path.join(__dirname, 'config', file), JSON.stringify(NewConfig, null, 2));
}
}
}
@@ -332,56 +262,7 @@ export function isEqual(obj1: any, obj2: any) {
}
return true;
}
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702'
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
prevVersion: '',
onErrorVersions: [],
buildId: '26702'
};
}
export async function promisePipeline(promises: Promise<any>[], callback: (result: any) => boolean): Promise<void> {
let callbackCalled = false;
for (const promise of promises) {
if (callbackCalled) break;
try {
const result = await promise;
if (!callbackCalled) {
callbackCalled = callback(result);
}
} catch (error) {
console.error('Error in promise pipeline:', error);
}
}
}
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath;
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
}
if (typeof configVersionInfoPath !== 'string') {
return undefined;
}
if (!fs.existsSync(configVersionInfoPath)) {
return undefined;
}
return configVersionInfoPath;
}
export async function deleteOldFiles(directoryPath: string, daysThreshold: number) {
try {
const files = await fsPromise.readdir(directoryPath);

View File

@@ -91,10 +91,8 @@ export function enableConsoleLog(enable: boolean) {
function formatMsg(msg: any[]) {
let logMsg = '';
for (const msgItem of msg) {
if (msgItem instanceof Error) { // 判断是否是错误
logMsg += msgItem.stack + ' ';
continue;
} else if (typeof msgItem === 'object') { // 判断是否是对象
// 判断是否是对象
if (typeof msgItem === 'object') {
const obj = JSON.parse(JSON.stringify(msgItem, null, 2));
logMsg += JSON.stringify(truncateString(obj)) + ' ';
continue;

View File

@@ -1,13 +1,15 @@
import https from 'node:https';
import http from 'node:http';
import { readFileSync } from 'node:fs';
import fs, { readFileSync } from 'node:fs';
import { NTQQUserApi } from '@/core';
import path from 'node:path';
import { request } from 'node:http';
export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
const client = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
const req = client.get(url, (res) => {
client.get(url, (res) => {
let cookies: { [key: string]: string } = {};
const handleRedirect = (res: http.IncomingMessage) => {
//console.log(res.headers.location);
@@ -18,8 +20,6 @@ export class RequestUtil {
// 合并重定向过程中的cookies
cookies = { ...cookies, ...redirectCookies };
resolve(cookies);
}).catch((err) => {
reject(err);
});
} else {
resolve(cookies);
@@ -43,10 +43,9 @@ export class RequestUtil {
}
});
}
}).on('error', (err) => {
reject(err);
});
req.on('error', (error: any) => {
reject(error);
});
});
}
@@ -158,6 +157,7 @@ export class RequestUtil {
});
res.on('end', () => {
try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
const responseJson = JSON.parse(responseBody) as retType;
@@ -174,7 +174,6 @@ export class RequestUtil {
});
req.on('error', (error) => {
reject(error);
console.error('Error during upload:', error);
});
@@ -190,4 +189,4 @@ export class RequestUtil {
return undefined;
});
}
}
}

View File

@@ -1,7 +1,7 @@
import os from 'node:os';
import path from 'node:path';
import { networkInterfaces } from 'os';
import { randomUUID } from 'crypto';
import { v4 as uuidv4 } from 'uuid';
// 缓解Win7设备兼容性问题
let osName: string;
@@ -30,7 +30,7 @@ export async function getMachineId(): Promise<string> {
if (!machineId) {
machineId = (async () => {
const id = await getMacMachineId();
return id || randomUUID(); // fallback, generate a UUID
return id || uuidv4(); // fallback, generate a UUID
})();
}

View File

@@ -1,17 +1,31 @@
//QQVersionType
type QQPackageInfoType = {
version: string;
buildVersion: string;
platform: string;
eleArch: string;
/**
* 运行时类型转换与检查类
*/
export class TypeCheck {
static isEmpty(value: any): boolean {
return value === null || value === undefined || value === '' ||
(Array.isArray(value) && value.length === 0) || (typeof value === 'object' && Object.keys(value).length === 0);
}
}
type QQVersionConfigType = {
baseVersion: string;
curVersion: string;
prevVersion: string;
onErrorVersions: Array<any>;
buildId: string;
}
type QQAppidTableType = {
[key: string]: { appid: string, qua: string };
export class TypeConvert {
static toNumber(value: any): number {
const num = Number(value);
if (isNaN(num)) {
throw new Error(`无法将输入转换为数字: ${value}`);
}
return num;
}
static toString(value: any): string {
return String(value);
}
static toBoolean(value: any): boolean {
return Boolean(value);
}
static toArray(value: any): any[] {
return Array.isArray(value) ? value : [value];
}
}

View File

@@ -4,10 +4,10 @@ export async function checkVersion(): Promise<string> {
return new Promise(async (resolve, reject) => {
const MirrorList =
[
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json',
'https://fastly.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
'https://gcore.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json',
'https://cdn.jsdelivr.net/gh/NapNeko/NapCatQQ@main/package.json'
'https://cdn.jsdelivr.us/gh/NapNeko/NapCatQQ@main/package.json',
'https://jsd.cdn.zzko.cn/gh/NapNeko/NapCatQQ@main/package.json'
];
let version = undefined;
for (const url of MirrorList) {

File diff suppressed because one or more lines are too long

1
src/core Submodule

Submodule src/core added at f362cdb9fa

View File

@@ -0,0 +1,14 @@
interface IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number): void;
onMSFSsoError(args: unknown): void;
getGroupCode(args: unknown): void;
}
export interface NodeIDependsAdapter extends IDependsAdapter {
new (adapter: IDependsAdapter): NodeIDependsAdapter;
}
export declare class DependsAdapter implements IDependsAdapter {
onMSFStatusChange(arg1: number, arg2: number): void;
onMSFSsoError(args: unknown): void;
getGroupCode(args: unknown): void;
}
export {};

View File

@@ -0,0 +1 @@
function _0x3af3(_0x356d40,_0x2a20fb){var _0x101a99=_0x101a();return _0x3af3=function(_0x3af393,_0x53d8da){_0x3af393=_0x3af393-0x64;var _0x1a7781=_0x101a99[_0x3af393];return _0x1a7781;},_0x3af3(_0x356d40,_0x2a20fb);}var _0x37baa3=_0x3af3;(function(_0x3d6d4c,_0x1c9364){var _0x3270f1=_0x3af3,_0x5d4667=_0x3d6d4c();while(!![]){try{var _0x6921cf=parseInt(_0x3270f1(0x68))/0x1+-parseInt(_0x3270f1(0x6b))/0x2+parseInt(_0x3270f1(0x6f))/0x3+-parseInt(_0x3270f1(0x6e))/0x4*(parseInt(_0x3270f1(0x67))/0x5)+-parseInt(_0x3270f1(0x6a))/0x6*(parseInt(_0x3270f1(0x64))/0x7)+-parseInt(_0x3270f1(0x66))/0x8*(parseInt(_0x3270f1(0x65))/0x9)+parseInt(_0x3270f1(0x6d))/0xa;if(_0x6921cf===_0x1c9364)break;else _0x5d4667['push'](_0x5d4667['shift']());}catch(_0xc0a47b){_0x5d4667['push'](_0x5d4667['shift']());}}}(_0x101a,0xe1bfe));export class DependsAdapter{[_0x37baa3(0x69)](_0x1e1f6e,_0x459b96){}[_0x37baa3(0x6c)](_0x1f7874){}[_0x37baa3(0x70)](_0x52d383){}}function _0x101a(){var _0xa96996=['42367970pybWcp','5588848WWCYNI','1222647CgLZgG','getGroupCode','7658BHknpG','4631319UaMPvI','24IHKGAL','5juNrBF','406432qNpTWf','onMSFStatusChange','54fzBnPw','2350554PxwUSd','onMSFSsoError'];_0x101a=function(){return _0xa96996;};return _0x101a();}

View File

@@ -0,0 +1,14 @@
interface IDispatcherAdapter {
dispatchRequest(arg: unknown): void;
dispatchCall(arg: unknown): void;
dispatchCallWithJson(arg: unknown): void;
}
export interface NodeIDispatcherAdapter extends IDispatcherAdapter {
new (adapter: IDispatcherAdapter): NodeIDispatcherAdapter;
}
export declare class DispatcherAdapter implements IDispatcherAdapter {
dispatchRequest(arg: unknown): void;
dispatchCall(arg: unknown): void;
dispatchCallWithJson(arg: unknown): void;
}
export {};

View File

@@ -0,0 +1 @@
var _0x1a2d1a=_0x34fa;(function(_0x1a1675,_0x286fde){var _0x8a52bc=_0x34fa,_0x5eddf7=_0x1a1675();while(!![]){try{var _0x18454f=parseInt(_0x8a52bc(0x72))/0x1+-parseInt(_0x8a52bc(0x6b))/0x2*(-parseInt(_0x8a52bc(0x74))/0x3)+parseInt(_0x8a52bc(0x6e))/0x4*(-parseInt(_0x8a52bc(0x6f))/0x5)+parseInt(_0x8a52bc(0x76))/0x6*(parseInt(_0x8a52bc(0x78))/0x7)+parseInt(_0x8a52bc(0x6c))/0x8+-parseInt(_0x8a52bc(0x77))/0x9*(parseInt(_0x8a52bc(0x6d))/0xa)+-parseInt(_0x8a52bc(0x79))/0xb*(-parseInt(_0x8a52bc(0x73))/0xc);if(_0x18454f===_0x286fde)break;else _0x5eddf7['push'](_0x5eddf7['shift']());}catch(_0x39129d){_0x5eddf7['push'](_0x5eddf7['shift']());}}}(_0x5423,0x8d2fe));function _0x34fa(_0x11e3c6,_0x18a7d3){var _0x5423ce=_0x5423();return _0x34fa=function(_0x34fa44,_0x1c1e83){_0x34fa44=_0x34fa44-0x6b;var _0x529ce9=_0x5423ce[_0x34fa44];return _0x529ce9;},_0x34fa(_0x11e3c6,_0x18a7d3);}function _0x5423(){var _0x4f219a=['534RXDMqF','1512891LnLYPL','70672RErDOC','4908838tnrFuS','2WKotYM','4074456lRLzFT','50xLhqVM','10548OlSYSu','2105dnCecF','dispatchRequest','dispatchCall','100450BrGFPc','24rEVuId','384471eTeeAT','dispatchCallWithJson'];_0x5423=function(){return _0x4f219a;};return _0x5423();}export class DispatcherAdapter{[_0x1a2d1a(0x70)](_0x2b7d81){}[_0x1a2d1a(0x71)](_0x25661b){}[_0x1a2d1a(0x75)](_0x3209fd){}}

View File

@@ -0,0 +1,24 @@
interface IGlobalAdapter {
onLog(...args: unknown[]): void;
onGetSrvCalTime(...args: unknown[]): void;
onShowErrUITips(...args: unknown[]): void;
fixPicImgType(...args: unknown[]): void;
getAppSetting(...args: unknown[]): void;
onInstallFinished(...args: unknown[]): void;
onUpdateGeneralFlag(...args: unknown[]): void;
onGetOfflineMsg(...args: unknown[]): void;
}
export interface NodeIGlobalAdapter extends IGlobalAdapter {
new (adapter: IGlobalAdapter): NodeIGlobalAdapter;
}
export declare class GlobalAdapter implements IGlobalAdapter {
onLog(...args: unknown[]): void;
onGetSrvCalTime(...args: unknown[]): void;
onShowErrUITips(...args: unknown[]): void;
fixPicImgType(...args: unknown[]): void;
getAppSetting(...args: unknown[]): void;
onInstallFinished(...args: unknown[]): void;
onUpdateGeneralFlag(...args: unknown[]): void;
onGetOfflineMsg(...args: unknown[]): void;
}
export {};

View File

@@ -0,0 +1 @@
var _0x3c354d=_0x3b46;(function(_0xf8f98c,_0x45495c){var _0x5f2988=_0x3b46,_0x2c4e82=_0xf8f98c();while(!![]){try{var _0x1801ba=parseInt(_0x5f2988(0xae))/0x1*(-parseInt(_0x5f2988(0xb8))/0x2)+parseInt(_0x5f2988(0xb7))/0x3+-parseInt(_0x5f2988(0xb5))/0x4*(parseInt(_0x5f2988(0xad))/0x5)+parseInt(_0x5f2988(0xb1))/0x6*(parseInt(_0x5f2988(0xa8))/0x7)+parseInt(_0x5f2988(0xa9))/0x8*(-parseInt(_0x5f2988(0xb9))/0x9)+-parseInt(_0x5f2988(0xac))/0xa*(parseInt(_0x5f2988(0xaa))/0xb)+-parseInt(_0x5f2988(0xb4))/0xc*(-parseInt(_0x5f2988(0xaf))/0xd);if(_0x1801ba===_0x45495c)break;else _0x2c4e82['push'](_0x2c4e82['shift']());}catch(_0x4dbbd6){_0x2c4e82['push'](_0x2c4e82['shift']());}}}(_0x474f,0x69172));function _0x474f(){var _0x3ee35d=['34978177AIDyES','onLog','192060noCQbU','onUpdateGeneralFlag','onShowErrUITips','12vbnUnK','668OiwwSf','getAppSetting','1233990kfBaaw','974zIRzTn','6014043WVSUxt','56PxbZlm','8vOxLof','258962qYXpMY','onInstallFinished','340kZgviQ','25615umUBvX','1239nBzLHw'];_0x474f=function(){return _0x3ee35d;};return _0x474f();}function _0x3b46(_0x282052,_0x1a1016){var _0x474f1f=_0x474f();return _0x3b46=function(_0x3b46fc,_0x1b9e81){_0x3b46fc=_0x3b46fc-0xa8;var _0x54f1ce=_0x474f1f[_0x3b46fc];return _0x54f1ce;},_0x3b46(_0x282052,_0x1a1016);}export class GlobalAdapter{[_0x3c354d(0xb0)](..._0x3b6849){}['onGetSrvCalTime'](..._0x563485){}[_0x3c354d(0xb3)](..._0x585ec0){}['fixPicImgType'](..._0x3668d1){}[_0x3c354d(0xb6)](..._0x488201){}[_0x3c354d(0xab)](..._0x38eda3){}[_0x3c354d(0xb2)](..._0x3654f6){}['onGetOfflineMsg'](..._0x175446){}}

View File

@@ -0,0 +1 @@
(function(_0x1e93c0,_0x5a6695){var _0x2a6062=_0x153d,_0xdcabef=_0x1e93c0();while(!![]){try{var _0x3be4aa=parseInt(_0x2a6062(0x134))/0x1*(-parseInt(_0x2a6062(0x133))/0x2)+parseInt(_0x2a6062(0x135))/0x3*(-parseInt(_0x2a6062(0x13b))/0x4)+-parseInt(_0x2a6062(0x13a))/0x5+parseInt(_0x2a6062(0x138))/0x6*(parseInt(_0x2a6062(0x136))/0x7)+parseInt(_0x2a6062(0x137))/0x8+parseInt(_0x2a6062(0x132))/0x9+parseInt(_0x2a6062(0x139))/0xa;if(_0x3be4aa===_0x5a6695)break;else _0xdcabef['push'](_0xdcabef['shift']());}catch(_0x5e4725){_0xdcabef['push'](_0xdcabef['shift']());}}}(_0x26c1,0x9fe93));export*from'./NodeIDependsAdapter';export*from'./NodeIDispatcherAdapter';function _0x26c1(){var _0x5ab3ab=['1045125RogMJB','4HtttjG','7126110XrNMQk','19792pkfHra','6UjmuQF','3470811spDoPb','100821kYZGkB','2481152tdUThE','42ZCdkWb','8775780ThKppr'];_0x26c1=function(){return _0x5ab3ab;};return _0x26c1();}function _0x153d(_0x40bd82,_0x4e0626){var _0x26c110=_0x26c1();return _0x153d=function(_0x153dc9,_0x1f8255){_0x153dc9=_0x153dc9-0x132;var _0x42dfdf=_0x26c110[_0x153dc9];return _0x42dfdf;},_0x153d(_0x40bd82,_0x4e0626);}export*from'./NodeIGlobalAdapter';

41
src/core.lib/src/apis/collection.d.ts vendored Normal file
View File

@@ -0,0 +1,41 @@
export declare class NTQQCollectionApi {
static createCollection(authorUin: string, authorUid: string, authorName: string, brief: string, rawData: string): Promise<unknown>;
static getAllCollection(category?: number, count?: number): Promise<import("..").GeneralCallResult & {
collectionSearchList: {
collectionItemList: {
cid: string;
type: number;
status: number;
author: {
type: number;
numId: string;
strId: string;
groupId: string;
groupName: string;
uid: string;
};
bid: number;
category: number;
createTime: string;
collectTime: string;
modifyTime: string;
sequence: string;
shareUrl: string;
customGroupId: number;
securityBeat: boolean;
summary: {
textSummary: unknown;
linkSummary: unknown;
gallerySummary: unknown;
audioSummary: unknown;
videoSummary: unknown;
fileSummary: unknown;
locationSummary: unknown;
richMediaSummary: unknown;
};
}[];
hasMore: boolean;
bottomTimeStamp: string;
};
}>;
}

View File

@@ -0,0 +1 @@
const _0x2a256f=_0x7eb6;(function(_0x4493fb,_0x2ee991){const _0x37ecc8=_0x7eb6,_0x3dd8ba=_0x4493fb();while(!![]){try{const _0xc11bad=parseInt(_0x37ecc8(0x15a))/0x1+parseInt(_0x37ecc8(0x167))/0x2+parseInt(_0x37ecc8(0x15d))/0x3*(-parseInt(_0x37ecc8(0x168))/0x4)+-parseInt(_0x37ecc8(0x163))/0x5*(parseInt(_0x37ecc8(0x15e))/0x6)+parseInt(_0x37ecc8(0x16b))/0x7+-parseInt(_0x37ecc8(0x164))/0x8*(parseInt(_0x37ecc8(0x169))/0x9)+-parseInt(_0x37ecc8(0x15b))/0xa*(parseInt(_0x37ecc8(0x160))/0xb);if(_0xc11bad===_0x2ee991)break;else _0x3dd8ba['push'](_0x3dd8ba['shift']());}catch(_0x29c5db){_0x3dd8ba['push'](_0x3dd8ba['shift']());}}}(_0x2939,0x688c8));import{napCatCore}from'..';export class NTQQCollectionApi{static async[_0x2a256f(0x162)](_0x24e2dd,_0x5a812d,_0x36ec48,_0x1f96c5,_0x43ae5e){const _0x4722c1=_0x2a256f;let _0x1af445={'commInfo':{'bid':0x1,'category':0x2,'author':{'type':0x1,'numId':_0x24e2dd,'strId':_0x36ec48,'groupId':'0','groupName':'','uid':_0x5a812d},'customGroupId':'0','createTime':Date['now']()[_0x4722c1(0x165)](),'sequence':Date[_0x4722c1(0x161)]()[_0x4722c1(0x165)]()},'richMediaSummary':{'originalUri':'','publisher':'','richMediaVersion':0x0,'subTitle':'','title':'','brief':_0x1f96c5,'picList':[],'contentType':0x1},'richMediaContent':{'rawData':_0x43ae5e,'bizDataList':[],'picList':[],'fileList':[]},'need_share_url':![]};return napCatCore[_0x4722c1(0x16a)][_0x4722c1(0x16c)]()[_0x4722c1(0x15f)](_0x1af445);}static async[_0x2a256f(0x166)](_0x71a742=0x0,_0x33b489=0x32){const _0xee929=_0x2a256f;let _0x48cb33={'category':_0x71a742,'groupId':-0x1,'forceSync':!![],'forceFromDb':![],'timeStamp':'0','count':_0x33b489,'searchDown':!![]};return napCatCore[_0xee929(0x16a)][_0xee929(0x16c)]()[_0xee929(0x15c)](_0x48cb33);}}function _0x7eb6(_0x31436a,_0x3db8f3){const _0x29394d=_0x2939();return _0x7eb6=function(_0x7eb6ec,_0x1f82ac){_0x7eb6ec=_0x7eb6ec-0x15a;let _0x1ac45f=_0x29394d[_0x7eb6ec];return _0x1ac45f;},_0x7eb6(_0x31436a,_0x3db8f3);}function _0x2939(){const _0x45b02f=['session','5171593boFMAV','getCollectionService','576409KTadoA','250dEpJMF','getCollectionItemList','449817nQzMkW','6WaquQm','createNewCollectionItem','101167uOkBSF','now','createCollection','4067885AjgkPQ','16vFkWTs','toString','getAllCollection','1014584BZLvBR','8BlMKhh','228996atcIhL'];_0x2939=function(){return _0x45b02f;};return _0x2939();}

38
src/core.lib/src/apis/file.d.ts vendored Normal file
View File

@@ -0,0 +1,38 @@
import { CacheFileListItem, CacheFileType, ChatCacheListItemBasic, ChatType, ElementType, RawMessage } from '@/core/entities';
import { GeneralCallResult } from '@/core';
import * as fileType from 'file-type';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
export declare class NTQQFileApi {
static getFileType(filePath: string): Promise<fileType.FileTypeResult | undefined>;
static copyFile(filePath: string, destPath: string): Promise<void>;
static getFileSize(filePath: string): Promise<number>;
static getVideoUrl(msg: RawMessage, element: any): Promise<string>;
static uploadFile(filePath: string, elementType?: ElementType, elementSubType?: number): Promise<{
md5: string;
fileName: string;
path: string;
fileSize: number;
ext: string;
}>;
static downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout?: number, force?: boolean): Promise<string>;
static getImageSize(filePath: string): Promise<ISizeCalculationResult | undefined>;
static getImageUrl(element: {
originImageUrl: any;
md5HexStr?: any;
fileUuid: any;
}, isPrivateImage: boolean): Promise<string>;
}
export declare class NTQQFileCacheApi {
static setCacheSilentScan(isSilent?: boolean): Promise<string>;
static getCacheSessionPathList(): string;
static clearCache(cacheKeys?: Array<string>): unknown;
static addCacheScannedPaths(pathMap?: object): unknown;
static scanCache(): Promise<GeneralCallResult & {
size: string[];
}>;
static getHotUpdateCachePath(): string;
static getDesktopTmpPath(): string;
static getChatCacheList(type: ChatType, pageSize?: number, pageIndex?: number): unknown;
static getFileCacheInfo(fileType: CacheFileType, pageSize?: number, lastRecord?: CacheFileListItem): void;
static clearChatCache(chats?: ChatCacheListItemBasic[], fileKeys?: string[]): Promise<unknown>;
}

File diff suppressed because one or more lines are too long

7
src/core.lib/src/apis/friend.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { BuddyCategoryType, User } from '@/core/entities';
export declare class NTQQFriendApi {
static isBuddy(uid: string): Promise<boolean>;
static getFriends(forced?: boolean): Promise<User[]>;
static getFriendsRaw(forced?: boolean): Promise<BuddyCategoryType[]>;
static handleFriendRequest(flag: string, accept: boolean): Promise<void>;
}

View File

@@ -0,0 +1 @@
const _0x359c1d=_0x2ce0;(function(_0x179d88,_0x4e098e){const _0x203a9b=_0x2ce0,_0x2659bf=_0x179d88();while(!![]){try{const _0x1c34a1=parseInt(_0x203a9b(0x163))/0x1+-parseInt(_0x203a9b(0x15a))/0x2+parseInt(_0x203a9b(0x15d))/0x3+parseInt(_0x203a9b(0x15e))/0x4+-parseInt(_0x203a9b(0x155))/0x5+parseInt(_0x203a9b(0x15b))/0x6*(parseInt(_0x203a9b(0x158))/0x7)+parseInt(_0x203a9b(0x152))/0x8*(-parseInt(_0x203a9b(0x165))/0x9);if(_0x1c34a1===_0x4e098e)break;else _0x2659bf['push'](_0x2659bf['shift']());}catch(_0x5aa158){_0x2659bf['push'](_0x2659bf['shift']());}}}(_0x1680,0x1fd21));function _0x2ce0(_0xe61570,_0x57b6c4){const _0x168002=_0x1680();return _0x2ce0=function(_0x2ce0d2,_0xf34ea4){_0x2ce0d2=_0x2ce0d2-0x14e;let _0xc995a1=_0x168002[_0x2ce0d2];return _0xc995a1;},_0x2ce0(_0xe61570,_0x57b6c4);}import{napCatCore}from'@/core';import{NTEventDispatch}from'@/common/utils/EventTask';function _0x1680(){const _0x14d3ec=['FnWMW','494068QHoHHO','6SjRBCo','NodeIKernelBuddyListener/onBuddyListChange','755598jkWdlO','91516YdYpLS','session','NVVsQ','getFriends','approvalFriendRequest','231047MualYQ','handleFriendRequest','27tlkXId','isBuddy','NodeIKernelBuddyService/getBuddyList','split','getBuddyService','560088mpYXYw','push','cVhsM','871865oiMkEH','length','QEXcv','1791895GKaSdB'];_0x1680=function(){return _0x14d3ec;};return _0x1680();}export class NTQQFriendApi{static async[_0x359c1d(0x14e)](_0x27e7c9){const _0x3c2d25=_0x359c1d;return napCatCore[_0x3c2d25(0x15f)][_0x3c2d25(0x151)]()['isBuddy'](_0x27e7c9);}static async[_0x359c1d(0x161)](_0x229e39=![]){const _0x15772d=_0x359c1d,_0x1d1e60={'NVVsQ':'NodeIKernelBuddyListener/onBuddyListChange'};let [_0x7d0350,_0x1cbb23]=await NTEventDispatch['CallNormalEvent'](_0x15772d(0x14f),_0x1d1e60[_0x15772d(0x160)],0x1,0x1388,_0x229e39);const _0x58ecac=[];for(const _0x3f8407 of _0x1cbb23){for(const _0x7b596b of _0x3f8407['buddyList']){_0x58ecac[_0x15772d(0x153)](_0x7b596b);}}return _0x58ecac;}static async['getFriendsRaw'](_0x5f53b2=![]){const _0x429157=_0x359c1d,_0x584e08={'QEXcv':_0x429157(0x14f),'FnWMW':_0x429157(0x15c)};let [_0x4aa72e,_0x17d4fd]=await NTEventDispatch['CallNormalEvent'](_0x584e08[_0x429157(0x157)],_0x584e08[_0x429157(0x159)],0x1,0x1388,_0x5f53b2);return _0x17d4fd;}static async[_0x359c1d(0x164)](_0x1df72a,_0x358cbd){const _0x2e1a53=_0x359c1d,_0x671392={'cVhsM':function(_0xcaefd0,_0x2db9e2){return _0xcaefd0<_0x2db9e2;}};let _0xb0bfeb=_0x1df72a[_0x2e1a53(0x150)]('|');if(_0x671392[_0x2e1a53(0x154)](_0xb0bfeb[_0x2e1a53(0x156)],0x2))return;let _0x1e5422=_0xb0bfeb[0x0],_0x69e4ae=_0xb0bfeb[0x1];napCatCore[_0x2e1a53(0x15f)]['getBuddyService']()?.[_0x2e1a53(0x162)]({'friendUid':_0x1e5422,'reqTime':_0x69e4ae,'accept':_0x358cbd});}}

62
src/core.lib/src/apis/group.d.ts vendored Normal file
View File

@@ -0,0 +1,62 @@
import { GroupMember, GroupRequestOperateTypes, GroupMemberRole, GroupNotify, Group } from '../entities';
import { GeneralCallResult } from '@/core';
export declare class NTQQGroupApi {
static getGroups(forced?: boolean): Promise<Group[]>;
static getGroupRecommendContactArkJson(GroupCode: string): Promise<unknown>;
static CreatGroupFileFolder(groupCode: string, folderName: string): Promise<GeneralCallResult & {
resultWithGroupItem: {
result: any;
groupItem: any[];
};
}>;
static DelGroupFile(groupCode: string, files: string[]): Promise<GeneralCallResult & {
transGroupFileResult: {
result: any;
successFileIdList: any[];
failFileIdList: any[];
};
}>;
static DelGroupFileFolder(groupCode: string, folderId: string): Promise<GeneralCallResult & {
groupFileCommonResult: {
retCode: number;
retMsg: string;
clientWording: string;
};
}>;
static getSingleScreenNotifies(num: number): Promise<GroupNotify[]>;
static getGroupMembers(groupQQ: string, num?: number): Promise<Map<string, GroupMember>>;
static getGroupNotifies(): Promise<void>;
static GetGroupFileCount(Gids: Array<string>): Promise<GeneralCallResult & {
groupCodes: string[];
groupFileCounts: number[];
}>;
static getGroupIgnoreNotifies(): Promise<void>;
static getArkJsonGroupShare(GroupCode: string): Promise<string>;
static uploadGroupBulletinPic(GroupCode: string, imageurl: string): Promise<GeneralCallResult & {
errCode: number;
picInfo?: {
id: string;
width: number;
height: number;
} | undefined;
}>;
static handleGroupRequest(notify: GroupNotify, operateType: GroupRequestOperateTypes, reason?: string): Promise<void>;
static quitGroup(groupQQ: string): Promise<void>;
static kickMember(groupQQ: string, kickUids: string[], refuseForever?: boolean, kickReason?: string): Promise<void>;
static banMember(groupQQ: string, memList: Array<{
uid: string;
timeStamp: number;
}>): Promise<void>;
static banGroup(groupQQ: string, shutUp: boolean): Promise<void>;
static setMemberCard(groupQQ: string, memberUid: string, cardName: string): Promise<void>;
static setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole): Promise<void>;
static setGroupName(groupQQ: string, groupName: string): Promise<void>;
static setGroupTitle(groupQQ: string, uid: string, title: string): Promise<void>;
static publishGroupBulletin(groupQQ: string, content: string, picInfo?: {
id: string;
width: number;
height: number;
} | undefined, pinned?: number, confirmRequired?: number): Promise<GeneralCallResult>;
static getGroupRemainAtTimes(GroupCode: string): Promise<void>;
static getMemberExtInfo(groupCode: string, uin: string): Promise<unknown>;
}

File diff suppressed because one or more lines are too long

View File

@@ -5,4 +5,4 @@ export * from './msg';
export * from './user';
export * from './webapi';
export * from './sign';
export * from './system';
export * from './system';

View File

@@ -0,0 +1 @@
(function(_0x31fa30,_0x4dc339){var _0xd13ae6=_0x1cd2,_0x83225f=_0x31fa30();while(!![]){try{var _0x22a79f=-parseInt(_0xd13ae6(0xc7))/0x1+parseInt(_0xd13ae6(0xc5))/0x2+-parseInt(_0xd13ae6(0xc9))/0x3+-parseInt(_0xd13ae6(0xca))/0x4*(parseInt(_0xd13ae6(0xc8))/0x5)+-parseInt(_0xd13ae6(0xcc))/0x6+parseInt(_0xd13ae6(0xcb))/0x7*(-parseInt(_0xd13ae6(0xc4))/0x8)+parseInt(_0xd13ae6(0xc6))/0x9;if(_0x22a79f===_0x4dc339)break;else _0x83225f['push'](_0x83225f['shift']());}catch(_0x5ef940){_0x83225f['push'](_0x83225f['shift']());}}}(_0x54ec,0x4a67b));export*from'./file';export*from'./friend';export*from'./group';export*from'./msg';export*from'./user';export*from'./webapi';function _0x54ec(){var _0x44e0f7=['53887ZDJIrx','25MsMJFo','1460136GEJVZd','302756gCkVDF','196NOsvqE','1467768CGZUWF','49880QgRcPu','981290DZNCfB','10371330zVPJGi'];_0x54ec=function(){return _0x44e0f7;};return _0x54ec();}function _0x1cd2(_0x1948ba,_0x1beade){var _0x54ec02=_0x54ec();return _0x1cd2=function(_0x1cd2f6,_0x2876d3){_0x1cd2f6=_0x1cd2f6-0xc4;var _0x2b12dd=_0x54ec02[_0x1cd2f6];return _0x2b12dd;},_0x1cd2(_0x1948ba,_0x1beade);}export*from'./sign';export*from'./system';

26
src/core.lib/src/apis/msg.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
import { GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { GeneralCallResult } from '@/core/services/common';
export declare class NTQQMsgApi {
static setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set?: boolean): Promise<unknown>;
static getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
msgList: RawMessage[];
} | undefined>;
static getMsgsByMsgId(peer: Peer, msgIds: string[]): Promise<GeneralCallResult & {
msgList: RawMessage[];
}>;
static getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[];
}>;
static activateChat(peer: Peer): Promise<void>;
static activateChatAndGetHistory(peer: Peer): Promise<void>;
static setMsgRead(peer: Peer): Promise<GeneralCallResult>;
static getGroupFileList(GroupCode: string, params: GetFileListParam): Promise<any[]>;
static getMsgHistory(peer: Peer, msgId: string, count: number): Promise<GeneralCallResult & {
msgList: RawMessage[];
}>;
static fetchRecentContact(): Promise<void>;
static recallMsg(peer: Peer, msgIds: string[]): Promise<void>;
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete?: boolean, timeout?: number): Promise<RawMessage>;
static forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage>;
}

File diff suppressed because one or more lines are too long

43
src/core.lib/src/apis/sign.d.ts vendored Normal file
View File

@@ -0,0 +1,43 @@
export interface IdMusicSignPostData {
type: 'qq' | '163';
id: string | number;
}
export interface CustomMusicSignPostData {
type: 'custom';
url: string;
audio: string;
title: string;
image?: string;
singer?: string;
}
export interface MiniAppLuaJsonType {
prompt: string;
title: string;
preview: string;
jumpUrl: string;
tag: string;
tagIcon: string;
source: string;
sourcelogo: string;
}
export declare function SignMiniApp(CardData: MiniAppLuaJsonType): Promise<string>;
export declare function SignMusicInternal(songname: string, singer: string, cover: string, songmid: string, songmusic: string): Promise<{
code: number;
data: {
arkResult: string;
};
}>;
export declare function CreateMusicThridWay0(id?: string, mid?: string): Promise<{
mid: string;
name?: string | undefined;
singer?: string | undefined;
url?: string | undefined;
cover?: string | undefined;
}>;
export declare function CreateMusicThridWay1(id?: string, mid?: string): Promise<void>;
export declare function SignMusicWrapper(id?: string): Promise<{
code: number;
data: {
arkResult: string;
};
}>;

File diff suppressed because one or more lines are too long

13
src/core.lib/src/apis/system.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import { GeneralCallResult } from '@/core';
export declare class NTQQSystemApi {
static hasOtherRunningQQProcess(): Promise<boolean>;
static ORCImage(filePath: string): Promise<GeneralCallResult>;
static translateEnWordToZn(words: string[]): Promise<GeneralCallResult & {
words: string[];
}>;
static getOnlineDev(): Promise<any>;
static getArkJsonCollection(cid: string): Promise<GeneralCallResult & {
arkJson: string;
}>;
static BootMiniApp(appfile: string, params: string): Promise<unknown>;
}

View File

@@ -0,0 +1 @@
const _0x3de4d1=_0x2e6f;function _0x4ece(){const _0x1099ac=['4021820Zwofnx','hasOtherRunningQQProcess','474tgXvij','getOnLineDev','BootMiniApp','16890120PtxuND','startNewMiniApp','4ggiZpi','session','1967709gDffCK','NodeIKernelCollectionService/collectionArkShare','7010024JKdBbE','getNodeMiscService','939570GtiRJs','FUSlq','5013666osYlWz','CallNoListenerEvent','wantWinScreenOCR','JifDj','sPsBP','getRichMediaService','2191AIZCDC','getMiniAppPath','log','getOnlineDev','setMiniAppVersion','util','2.16.4','translateEnWordToZn'];_0x4ece=function(){return _0x1099ac;};return _0x4ece();}function _0x2e6f(_0x33eacd,_0x93bb05){const _0x4ece19=_0x4ece();return _0x2e6f=function(_0x2e6f0d,_0x328137){_0x2e6f0d=_0x2e6f0d-0x172;let _0x405801=_0x4ece19[_0x2e6f0d];return _0x405801;},_0x2e6f(_0x33eacd,_0x93bb05);}(function(_0x14421c,_0x6e6316){const _0xcdad56=_0x2e6f,_0x41ab50=_0x14421c();while(!![]){try{const _0x412256=-parseInt(_0xcdad56(0x179))/0x1*(parseInt(_0xcdad56(0x183))/0x2)+parseInt(_0xcdad56(0x18a))/0x3+parseInt(_0xcdad56(0x188))/0x4*(parseInt(_0xcdad56(0x181))/0x5)+-parseInt(_0xcdad56(0x18e))/0x6+parseInt(_0xcdad56(0x173))/0x7+parseInt(_0xcdad56(0x18c))/0x8+-parseInt(_0xcdad56(0x186))/0x9;if(_0x412256===_0x6e6316)break;else _0x41ab50['push'](_0x41ab50['shift']());}catch(_0x1dc32e){_0x41ab50['push'](_0x41ab50['shift']());}}}(_0x4ece,0x7a1f8));import{NTEventDispatch}from'@/common/utils/EventTask';import{napCatCore}from'@/core';export class NTQQSystemApi{static async['hasOtherRunningQQProcess'](){const _0x386c01=_0x2e6f;return napCatCore[_0x386c01(0x17e)][_0x386c01(0x182)]();}static async['ORCImage'](_0x19edb7){const _0x1041e3=_0x2e6f;return napCatCore[_0x1041e3(0x189)][_0x1041e3(0x18d)]()[_0x1041e3(0x175)](_0x19edb7);}static async[_0x3de4d1(0x180)](_0x2af078){const _0x4cbd33=_0x3de4d1;return napCatCore[_0x4cbd33(0x189)][_0x4cbd33(0x178)]()['translateEnWordToZn'](_0x2af078);}static async[_0x3de4d1(0x17c)](){const _0x43e628=_0x3de4d1;return napCatCore[_0x43e628(0x189)]['getMsgService']()[_0x43e628(0x184)]();}static async['getArkJsonCollection'](_0x409fef){const _0x34dbd3=_0x3de4d1,_0x20d647={'FUSlq':_0x34dbd3(0x18b),'sPsBP':'1717662698058'};let _0x59d26d=await NTEventDispatch[_0x34dbd3(0x174)](_0x20d647[_0x34dbd3(0x172)],0x1388,_0x20d647[_0x34dbd3(0x177)]);return _0x59d26d;}static async[_0x3de4d1(0x185)](_0x404ffe,_0xcba4ff){const _0x11c489=_0x3de4d1,_0x2c0df6={'JifDj':_0x11c489(0x17f)};await napCatCore[_0x11c489(0x189)][_0x11c489(0x18d)]()[_0x11c489(0x17d)](_0x2c0df6[_0x11c489(0x176)]);let _0x260e2d=await napCatCore[_0x11c489(0x189)][_0x11c489(0x18d)]()[_0x11c489(0x17a)]();return console[_0x11c489(0x17b)](_0x260e2d),napCatCore[_0x11c489(0x189)][_0x11c489(0x18d)]()[_0x11c489(0x187)](_0x404ffe,_0xcba4ff);}}

35
src/core.lib/src/apis/user.d.ts vendored Normal file
View File

@@ -0,0 +1,35 @@
import { ModifyProfileParams, User, UserDetailInfoByUin } from '@/core/entities';
import { GeneralCallResult } from '@/core';
export declare class NTQQUserApi {
static setLongNick(longNick: string): Promise<unknown>;
static setSelfOnlineStatus(status: number, extStatus: number, batteryStatus: number): Promise<GeneralCallResult>;
static getBuddyRecommendContactArkJson(uin: string, sencenID?: string): Promise<unknown>;
static like(uid: string, count?: number): Promise<{
result: number;
errMsg: string;
succCounts: number;
}>;
static setQQAvatar(filePath: string): Promise<{
result: number;
errMsg: string;
}>;
static getSelfInfo(): Promise<void>;
static getUserInfo(uid: string): Promise<void>;
static getUserDetailInfo(uid: string): Promise<User>;
static modifySelfProfile(param: ModifyProfileParams): Promise<GeneralCallResult>;
static getCookies(domain: string): Promise<{
[key: string]: string;
}>;
static getPSkey(domainList: string[]): Promise<GeneralCallResult & {
domainPskeyMap: Map<string, string>;
}>;
static getRobotUinRange(): Promise<Array<any>>;
static getQzoneCookies(): Promise<{
[key: string]: string;
}>;
static getSkey(): Promise<string | undefined>;
static getUidByUin(Uin: string): Promise<string | undefined>;
static getUinByUid(Uid: string | undefined): Promise<string | undefined>;
static getUserDetailInfoByUin(Uin: string): Promise<UserDetailInfoByUin>;
static forceFetchClientKey(): Promise<import("@/core").forceFetchClientKeyRetType>;
}

File diff suppressed because one or more lines are too long

105
src/core.lib/src/apis/webapi.d.ts vendored Normal file
View File

@@ -0,0 +1,105 @@
export declare enum WebHonorType {
ALL = "all",
TALKACTIVE = "talkative",
PERFROMER = "performer",
LEGEND = "legend",
STORONGE_NEWBI = "strong_newbie",
EMOTION = "emotion"
}
export interface WebApiGroupMember {
uin: number;
role: number;
g: number;
join_time: number;
last_speak_time: number;
lv: {
point: number;
level: number;
};
card: string;
tags: string;
flag: number;
nick: string;
qage: number;
rm: number;
}
export interface WebApiGroupNoticeFeed {
u: number;
fid: string;
pubt: number;
msg: {
text: string;
text_face: string;
title: string;
pics?: {
id: string;
w: string;
h: string;
}[];
};
type: number;
fn: number;
cn: number;
vn: number;
settings: {
is_show_edit_card: number;
remind_ts: number;
tip_window_type: number;
confirm_required: number;
};
read_num: number;
is_read: number;
is_all_confirm: number;
}
export interface WebApiGroupNoticeRet {
ec: number;
em: string;
ltsm: number;
srv_code: number;
read_only: number;
role: number;
feeds: WebApiGroupNoticeFeed[];
group: {
group_id: number;
class_ext: number;
};
sta: number;
gln: number;
tst: number;
ui: any;
server_time: number;
svrt: number;
ad: number;
}
interface GroupEssenceMsg {
group_code: string;
msg_seq: number;
msg_random: number;
sender_uin: string;
sender_nick: string;
sender_time: number;
add_digest_uin: string;
add_digest_nick: string;
add_digest_time: number;
msg_content: any[];
can_be_removed: true;
}
export interface GroupEssenceMsgRet {
retcode: number;
retmsg: string;
data: {
msg_list: GroupEssenceMsg[];
is_end: boolean;
group_role: number;
config_page_url: string;
};
}
export declare class WebApi {
static getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet | undefined>;
static getGroupMembers(GroupCode: string, cached?: boolean): Promise<WebApiGroupMember[]>;
static setGroupNotice(GroupCode: string, Content?: string): Promise<any>;
static getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet>;
static genBkn(sKey: string): string;
static getGroupHonorInfo(groupCode: string, getType: WebHonorType): Promise<any>;
}
export {};

File diff suppressed because one or more lines are too long

36
src/core.lib/src/core.d.ts vendored Normal file
View File

@@ -0,0 +1,36 @@
/// <reference types="node" />
import { NodeIQQNTWrapperEngine, NodeIQQNTWrapperSession, NodeQQNTWrapperUtil } from '@/core/wrapper';
import { QuickLoginResult } from '@/core/services';
import { BuddyListener, GroupListener, MsgListener, ProfileListener } from '@/core/listeners';
export interface OnLoginSuccess {
(uin: string, uid: string): void | Promise<void>;
}
export declare class NapCatCore {
readonly session: NodeIQQNTWrapperSession;
readonly util: NodeQQNTWrapperUtil;
readonly engine: NodeIQQNTWrapperEngine;
private readonly loginListener;
private loginService;
private onLoginSuccessFuncList;
private proxyHandler;
constructor();
get dataPath(): string;
get dataPathGlobal(): string;
private initConfig;
private initSession;
private initDataListener;
addListener(listener: BuddyListener | GroupListener | MsgListener | ProfileListener): number;
onLoginSuccess(func: OnLoginSuccess): void;
quickLogin(uin: string): Promise<QuickLoginResult>;
qrLogin(cb: (url: string, base64: string, buffer: Buffer) => Promise<void>): Promise<{
url: string;
base64: string;
buffer: Buffer;
}>;
passwordLogin(uin: string, password: string, proofSig?: string, proofRand?: string, proofSid?: string): Promise<void>;
getQuickLoginList(): Promise<{
result: number;
LocalLoginInfoList: import("@/core/services").LoginListItem[];
}>;
}
export declare const napCatCore: NapCatCore;

1
src/core.lib/src/core.js Normal file

File diff suppressed because one or more lines are too long

16
src/core.lib/src/data.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
import { type GroupMember, GroupNotify, type SelfInfo } from './entities';
export declare const selfInfo: SelfInfo;
export declare const groupMembers: Map<string, Map<string, GroupMember>>;
export declare const groupNotifies: Record<string, GroupNotify>;
export declare function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number): Promise<GroupMember | null | undefined>;
export declare const tempGroupCodeMap: Record<string, string>;
export declare const stat: {
packet_received: number;
packet_sent: number;
message_received: number;
message_sent: number;
last_message_time: number;
disconnect_times: number;
lost_times: number;
packet_lost: number;
};

1
src/core.lib/src/data.js Normal file
View File

@@ -0,0 +1 @@
(function(_0x2ce4f4,_0x1379fe){const _0x4960b0=_0x116f,_0x4d19ab=_0x2ce4f4();while(!![]){try{const _0x453c60=parseInt(_0x4960b0(0xb3))/0x1+parseInt(_0x4960b0(0xbb))/0x2*(-parseInt(_0x4960b0(0xbc))/0x3)+parseInt(_0x4960b0(0xb7))/0x4*(parseInt(_0x4960b0(0xc0))/0x5)+-parseInt(_0x4960b0(0xb0))/0x6*(-parseInt(_0x4960b0(0xbe))/0x7)+parseInt(_0x4960b0(0xb2))/0x8*(parseInt(_0x4960b0(0xba))/0x9)+parseInt(_0x4960b0(0xb6))/0xa+parseInt(_0x4960b0(0xb9))/0xb*(-parseInt(_0x4960b0(0xc1))/0xc);if(_0x453c60===_0x1379fe)break;else _0x4d19ab['push'](_0x4d19ab['shift']());}catch(_0x5073cd){_0x4d19ab['push'](_0x4d19ab['shift']());}}}(_0x4e0c,0x493dd));function _0x116f(_0x3f6bef,_0x5b9aff){const _0x4e0c23=_0x4e0c();return _0x116f=function(_0x116f23,_0x21a912){_0x116f23=_0x116f23-0xaf;let _0xbb9d1f=_0x4e0c23[_0x116f23];return _0xbb9d1f;},_0x116f(_0x3f6bef,_0x5b9aff);}function _0x4e0c(){const _0x1a83b7=['341944VJeLcQ','25132OAewvs','get','find','787840pouKim','72KcOGkx','MQOjO','32494mvMnjh','9UZxEmI','190132QzOjZY','12FJwZNF','NyqSJ','41671iDtgJx','uin','143370hUmvtR','1380HhybVt','toString','getGroupMembers','360jNLVAF','pUqBb'];_0x4e0c=function(){return _0x1a83b7;};return _0x4e0c();}import{isNumeric}from'@/common/utils/helper';import{NTQQGroupApi}from'@/core/apis';export const selfInfo={'uid':'','uin':'','nick':'','online':!![]};export const groupMembers=new Map();export const groupNotifies={};export async function getGroupMember(_0x2a1928,_0x3c2bdc){const _0x432ab2=_0x116f,_0x1d5b7a={'MQOjO':function(_0xae238e,_0x13c0a5){return _0xae238e(_0x13c0a5);},'pUqBb':function(_0x18ab3f){return _0x18ab3f();},'NyqSJ':function(_0x48a597){return _0x48a597();}};_0x2a1928=_0x2a1928[_0x432ab2(0xc2)](),_0x3c2bdc=_0x3c2bdc[_0x432ab2(0xc2)]();let _0x20391c=groupMembers[_0x432ab2(0xb4)](_0x2a1928);if(!_0x20391c)try{_0x20391c=await NTQQGroupApi[_0x432ab2(0xaf)](_0x2a1928),groupMembers['set'](_0x2a1928,_0x20391c);}catch(_0x3b1b83){return null;}const _0x3f4f31=()=>{const _0x1f37b6=_0x432ab2;let _0x42b5c3=undefined;return _0x1d5b7a[_0x1f37b6(0xb8)](isNumeric,_0x3c2bdc)?_0x42b5c3=Array['from'](_0x20391c['values']())[_0x1f37b6(0xb5)](_0x55ed1d=>_0x55ed1d[_0x1f37b6(0xbf)]===_0x3c2bdc):_0x42b5c3=_0x20391c[_0x1f37b6(0xb4)](_0x3c2bdc),_0x42b5c3;};let _0x1bbece=_0x1d5b7a[_0x432ab2(0xb1)](_0x3f4f31);return!_0x1bbece&&(_0x20391c=await NTQQGroupApi[_0x432ab2(0xaf)](_0x2a1928),_0x1bbece=_0x1d5b7a[_0x432ab2(0xbd)](_0x3f4f31)),_0x1bbece;}export const tempGroupCodeMap={};export const stat={'packet_received':0x0,'packet_sent':0x0,'message_received':0x0,'message_sent':0x0,'last_message_time':0x0,'disconnect_times':0x0,'lost_times':0x0,'packet_lost':0x0};

View File

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

View File

@@ -0,0 +1 @@
function _0x1b7d(_0x42c842,_0x423739){var _0x24d073=_0x24d0();return _0x1b7d=function(_0x1b7d55,_0x3d45eb){_0x1b7d55=_0x1b7d55-0x1ae;var _0x6c111d=_0x24d073[_0x1b7d55];return _0x6c111d;},_0x1b7d(_0x42c842,_0x423739);}(function(_0x1abcb9,_0x56cd8b){var _0x2e5d0d=_0x1b7d,_0x13c89e=_0x1abcb9();while(!![]){try{var _0x1662e8=parseInt(_0x2e5d0d(0x1c1))/0x1*(-parseInt(_0x2e5d0d(0x1bf))/0x2)+parseInt(_0x2e5d0d(0x1bd))/0x3*(parseInt(_0x2e5d0d(0x1b0))/0x4)+parseInt(_0x2e5d0d(0x1c2))/0x5+parseInt(_0x2e5d0d(0x1c3))/0x6*(parseInt(_0x2e5d0d(0x1b4))/0x7)+-parseInt(_0x2e5d0d(0x1c0))/0x8*(parseInt(_0x2e5d0d(0x1b5))/0x9)+parseInt(_0x2e5d0d(0x1bb))/0xa*(-parseInt(_0x2e5d0d(0x1be))/0xb)+-parseInt(_0x2e5d0d(0x1b9))/0xc*(-parseInt(_0x2e5d0d(0x1bc))/0xd);if(_0x1662e8===_0x56cd8b)break;else _0x13c89e['push'](_0x13c89e['shift']());}catch(_0x32a9b4){_0x13c89e['push'](_0x13c89e['shift']());}}}(_0x24d0,0xbba9c));function _0x24d0(){var _0x37b820=['split','4512810AEaOGC','3250vSyVeY','3UAfcno','22rZlNLg','4wjqSoS','1030888RFMbqP','74891YjMbnf','1031840hOuPZU','582WBqQeg','2|4|1|0|3','OTHER','1912668WFUnZJ','qvrzM','hNJei','IMAGE','34419OQWVDt','18wQYZtg','VIDEO','PWnpT','DOCUMENT','44028DLcUhw'];_0x24d0=function(){return _0x37b820;};return _0x24d0();};export var CacheFileType;(function(_0x1f8adf){var _0x56652f=_0x1b7d,_0x12990e={'hNJei':_0x56652f(0x1b8),'PWnpT':'AUDIO','UdtuQ':_0x56652f(0x1b3),'dreDF':'OTHER','qvrzM':_0x56652f(0x1b6)},_0x58dcd2=_0x56652f(0x1ae)[_0x56652f(0x1ba)]('|'),_0x272ac7=0x0;while(!![]){switch(_0x58dcd2[_0x272ac7++]){case'0':_0x1f8adf[_0x1f8adf[_0x12990e[_0x56652f(0x1b2)]]=0x3]=_0x12990e[_0x56652f(0x1b2)];continue;case'1':_0x1f8adf[_0x1f8adf[_0x12990e[_0x56652f(0x1b7)]]=0x2]=_0x12990e[_0x56652f(0x1b7)];continue;case'2':_0x1f8adf[_0x1f8adf[_0x12990e['UdtuQ']]=0x0]=_0x56652f(0x1b3);continue;case'3':_0x1f8adf[_0x1f8adf[_0x12990e['dreDF']]=0x4]=_0x56652f(0x1af);continue;case'4':_0x1f8adf[_0x1f8adf[_0x12990e[_0x56652f(0x1b1)]]=0x1]=_0x12990e[_0x56652f(0x1b1)];continue;}break;}}(CacheFileType||(CacheFileType={})));

View File

@@ -0,0 +1,18 @@
import { AtType, SendArkElement, SendFaceElement, SendFileElement, SendMarkdownElement, SendMarketFaceElement, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, SendVideoElement } from './index';
export declare const mFaceCache: Map<string, string>;
export declare class SendMsgElementConstructor {
static text(content: string): SendTextElement;
static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement;
static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement;
static pic(picPath: string, summary?: string, subType?: 0 | 1): Promise<SendPicElement>;
static file(filePath: string, fileName?: string, folderId?: string): Promise<SendFileElement>;
static video(filePath: string, fileName?: string, diyThumbPath?: string): Promise<SendVideoElement>;
static ptt(pttPath: string): Promise<SendPttElement>;
static face(faceId: number): SendFaceElement;
static mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement;
static dice(resultId: number | null): SendFaceElement;
static rps(resultId: number | null): SendFaceElement;
static ark(data: any): SendArkElement;
static markdown(content: string): SendMarkdownElement;
static miniapp(): Promise<SendArkElement>;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

52
src/core.lib/src/entities/group.d.ts vendored Normal file
View File

@@ -0,0 +1,52 @@
import { QQLevel, Sex } from './user';
export interface Group {
groupCode: string;
maxMember: number;
memberCount: number;
groupName: string;
groupStatus: 0;
memberRole: 2;
isTop: boolean;
toppedTimestamp: string;
privilegeFlag: number;
isConf: boolean;
hasModifyConfGroupFace: boolean;
hasModifyConfGroupName: boolean;
remarkName: string;
hasMemo: boolean;
groupShutupExpireTime: string;
personShutupExpireTime: string;
discussToGroupUin: string;
discussToGroupMaxMsgSeq: number;
discussToGroupTime: number;
groupFlagExt: number;
authGroupType: number;
groupCreditLevel: number;
groupFlagExt3: number;
groupOwnerId: {
memberUin: string;
memberUid: string;
};
}
export declare enum GroupMemberRole {
normal = 2,
admin = 3,
owner = 4
}
export interface GroupMember {
memberSpecialTitle?: string;
avatarPath: string;
cardName: string;
cardType: number;
isDelete: boolean;
nick: string;
qid: string;
remark: string;
role: GroupMemberRole;
shutUpTime: number;
uid: string;
uin: string;
isRobot: boolean;
sex?: Sex;
qqLevel?: QQLevel;
}

View File

@@ -0,0 +1 @@
(function(_0x256298,_0x2e6d95){var _0x5d7b85=_0x2275,_0x3ab8cf=_0x256298();while(!![]){try{var _0x251be9=-parseInt(_0x5d7b85(0x12a))/0x1+-parseInt(_0x5d7b85(0x12e))/0x2*(parseInt(_0x5d7b85(0x12b))/0x3)+-parseInt(_0x5d7b85(0x126))/0x4*(-parseInt(_0x5d7b85(0x124))/0x5)+-parseInt(_0x5d7b85(0x127))/0x6+-parseInt(_0x5d7b85(0x131))/0x7+parseInt(_0x5d7b85(0x128))/0x8+-parseInt(_0x5d7b85(0x125))/0x9*(-parseInt(_0x5d7b85(0x12c))/0xa);if(_0x251be9===_0x2e6d95)break;else _0x3ab8cf['push'](_0x3ab8cf['shift']());}catch(_0x45480e){_0x3ab8cf['push'](_0x3ab8cf['shift']());}}}(_0x4579,0x79b0d));export var GroupMemberRole;function _0x2275(_0xab6b8a,_0x35339c){var _0x45794a=_0x4579();return _0x2275=function(_0x227514,_0x2c9bc2){_0x227514=_0x227514-0x123;var _0x19ad12=_0x45794a[_0x227514];return _0x19ad12;},_0x2275(_0xab6b8a,_0x35339c);}(function(_0x5de561){var _0x2e570b=_0x2275,_0x2332ec={'wAsuO':_0x2e570b(0x130),'oiJZE':_0x2e570b(0x129),'TVfJT':_0x2e570b(0x123)};_0x5de561[_0x5de561[_0x2e570b(0x130)]=0x2]=_0x2332ec[_0x2e570b(0x12d)],_0x5de561[_0x5de561[_0x2e570b(0x129)]=0x3]=_0x2332ec['oiJZE'],_0x5de561[_0x5de561[_0x2332ec[_0x2e570b(0x12f)]]=0x4]=_0x2332ec['TVfJT'];}(GroupMemberRole||(GroupMemberRole={})));function _0x4579(){var _0x58639c=['1041065tarjrE','5798781YFGHyx','8JXzsDe','2357118UJJWTC','1445624YQlInO','admin','452042UxEmRB','747rbQLOw','20TRkXPz','wAsuO','3302fKtSDI','TVfJT','normal','919156DxZYcH','owner'];_0x4579=function(){return _0x58639c;};return _0x4579();}

View File

@@ -4,4 +4,3 @@ export * from './msg';
export * from './notify';
export * from './cache';
export * from './constructor';

View File

@@ -0,0 +1 @@
function _0x270e(){var _0x2c19d1=['58382cUascg','1301157hawbxY','10432521FKngEB','13tJBKJI','419601ZMFfZb','6kskzdJ','187386egYPWN','48tgRBGh','8nyIqtE','572410paDubJ','1971005JEnPzr'];_0x270e=function(){return _0x2c19d1;};return _0x270e();}(function(_0x426a82,_0x2bbb4a){var _0xba9992=_0x26d8,_0x235655=_0x426a82();while(!![]){try{var _0xd97186=parseInt(_0xba9992(0x148))/0x1*(parseInt(_0xba9992(0x145))/0x2)+-parseInt(_0xba9992(0x14b))/0x3*(-parseInt(_0xba9992(0x142))/0x4)+-parseInt(_0xba9992(0x144))/0x5*(-parseInt(_0xba9992(0x14a))/0x6)+parseInt(_0xba9992(0x149))/0x7*(parseInt(_0xba9992(0x141))/0x8)+-parseInt(_0xba9992(0x146))/0x9+parseInt(_0xba9992(0x143))/0xa+-parseInt(_0xba9992(0x147))/0xb;if(_0xd97186===_0x2bbb4a)break;else _0x235655['push'](_0x235655['shift']());}catch(_0x236bd0){_0x235655['push'](_0x235655['shift']());}}}(_0x270e,0x3653b));export*from'./user';export*from'./group';export*from'./msg';export*from'./notify';function _0x26d8(_0x15fb49,_0x4cfa8f){var _0x270e58=_0x270e();return _0x26d8=function(_0x26d80a,_0xcfdf39){_0x26d80a=_0x26d80a-0x141;var _0x5d374a=_0x270e58[_0x26d80a];return _0x5d374a;},_0x26d8(_0x15fb49,_0x4cfa8f);}export*from'./cache';export*from'./constructor';

455
src/core.lib/src/entities/msg.d.ts vendored Normal file
View File

@@ -0,0 +1,455 @@
import { GroupMemberRole } from './group';
export interface Peer {
chatType: ChatType;
peerUid: string;
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;
}
export declare enum ElementType {
TEXT = 1,
PIC = 2,
FILE = 3,
PTT = 4,
VIDEO = 5,
FACE = 6,
REPLY = 7,
ARK = 10,
MFACE = 11,
MARKDOWN = 14
}
export interface SendTextElement {
elementType: ElementType.TEXT;
elementId: string;
textElement: {
content: string;
atType: number;
atUid: string;
atTinyId: string;
atNtUid: string;
};
}
export interface SendPttElement {
elementType: ElementType.PTT;
elementId: string;
pttElement: {
fileName: string;
filePath: string;
md5HexStr: string;
fileSize: number;
duration: number;
formatType: number;
voiceType: number;
voiceChangeType: number;
canConvert2Text: boolean;
waveAmplitudes: number[];
fileSubId: string;
playState: number;
autoConvertText: number;
};
}
export declare enum PicType {
gif = 2000,
jpg = 1000
}
export declare enum PicSubType {
normal = 0,// 普通图片,大图
face = 1
}
export interface SendPicElement {
elementType: ElementType.PIC;
elementId: string;
picElement: {
md5HexStr: string;
fileSize: number | string;
picWidth: number;
picHeight: number;
fileName: string;
sourcePath: string;
original: boolean;
picType: PicType;
picSubType: PicSubType;
fileUuid: string;
fileSubId: string;
thumbFileSize: number;
summary: string;
};
}
export interface SendReplyElement {
elementType: ElementType.REPLY;
elementId: string;
replyElement: {
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUinStr: string;
};
}
export interface SendFaceElement {
elementType: ElementType.FACE;
elementId: string;
faceElement: FaceElement;
}
export interface SendMarketFaceElement {
elementType: ElementType.MFACE;
marketFaceElement: MarketFaceElement;
}
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 SendFileElement {
elementType: ElementType.FILE;
elementId: string;
fileElement: FileElement;
}
export interface SendVideoElement {
elementType: ElementType.VIDEO;
elementId: string;
videoElement: VideoElement;
}
export interface SendArkElement {
elementType: ElementType.ARK;
elementId: string;
arkElement: ArkElement;
}
export interface SendMarkdownElement {
elementType: ElementType.MARKDOWN;
elementId: string;
markdownElement: MarkdownElement;
}
export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement | SendFaceElement | SendMarketFaceElement | SendFileElement | SendVideoElement | SendArkElement | SendMarkdownElement;
export declare enum AtType {
notAt = 0,
atAll = 1,
atUser = 2
}
export declare enum ChatType {
friend = 1,
group = 2,
chatDevice = 8,//移动设备?
temp = 100
}
export declare enum ChatType2 {
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;
fileName: string;
filePath: string;
fileSize: string;
fileSubId: string;
fileUuid: string;
formatType: string;
invalidState: number;
md5HexStr: string;
playState: number;
progress: number;
text: string;
transferStatus: number;
translateStatus: number;
voiceChangeType: number;
voiceType: number;
waveAmplitudes: number[];
}
export interface ArkElement {
bytesData: string;
linkInfo: null;
subElementType: null;
}
export declare const IMAGE_HTTP_HOST = "https://gchat.qpic.cn";
export declare const IMAGE_HTTP_HOST_NT = "https://multimedia.nt.qq.com.cn";
export interface PicElement {
originImageUrl: string;
originImageMd5?: string;
sourcePath: string;
thumbPath: Map<number, string>;
picWidth: number;
picHeight: number;
fileSize: number;
fileName: string;
fileUuid: string;
md5HexStr?: string;
}
export declare enum GrayTipElementSubType {
INVITE_NEW_MEMBER = 12,
MEMBER_NEW_TITLE = 17
}
export interface GrayTipElement {
subElementType: GrayTipElementSubType;
revokeElement: {
operatorRole: string;
operatorUid: string;
operatorNick: string;
operatorRemark: string;
operatorMemRemark?: string;
wording: string;
};
aioOpGrayTipElement: TipAioOpGrayTipElement;
groupElement: TipGroupElement;
xmlElement: {
content: string;
templId: string;
};
jsonGrayTipElement: {
jsonStr: string;
};
}
export declare enum FaceType {
normal = 1,// 小黄脸
normal2 = 2,// 新小黄脸, 从faceIndex 222开始
dice = 3
}
export declare 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;
thumbSize?: number;
fileFormat?: number;
fileSize?: string;
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 declare 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[];
}
];
}
export interface TipAioOpGrayTipElement {
operateType: number;
peerUid: string;
fromGrpCodeOfTmpChat: string;
}
export declare enum TipGroupElementType {
memberIncrease = 1,
kicked = 3,// 被移出群
ban = 8
}
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: 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;
resId: string;
fileName: string;
}
export interface RawMessage {
id?: number;
msgId: string;
msgTime: string;
msgSeq: string;
msgType: number;
subMsgType: number;
senderUid: string;
senderUin: string;
peerUid: string;
peerUin: string;
sendNickName: string;
sendMemberName?: string;
chatType: ChatType;
sendStatus?: number;
recallTime: string;
elements: {
elementId: string;
elementType: ElementType;
replyElement: {
senderUid: string;
sourceMsgIsIncPic: boolean;
sourceMsgText: string;
replayMsgSeq: string;
};
textElement: {
atType: AtType;
atUid: string;
content: string;
atNtUid: string;
};
picElement: PicElement;
pttElement: PttElement;
arkElement: ArkElement;
grayTipElement: GrayTipElement;
faceElement: FaceElement;
videoElement: VideoElement;
fileElement: FileElement;
marketFaceElement: MarketFaceElement;
inlineKeyboardElement: InlineKeyboardElement;
markdownElement: MarkdownElement;
multiForwardMsgElement: MultiForwardMsgElement;
}[];
}

File diff suppressed because one or more lines are too long

123
src/core.lib/src/entities/notify.d.ts vendored Normal file
View File

@@ -0,0 +1,123 @@
export declare enum GroupNotifyTypes {
INVITE_ME = 1,
INVITED_JOIN = 4,// 有人接受了邀请入群
JOIN_REQUEST = 7,
ADMIN_SET = 8,
KICK_MEMBER = 9,
MEMBER_EXIT = 11,// 主动退出
ADMIN_UNSET = 12,
ADMIN_UNSET_OTHER = 13
}
export interface GroupNotifies {
doubt: boolean;
nextStartSeq: string;
notifies: GroupNotify[];
}
export declare enum GroupNotifyStatus {
IGNORE = 0,
WAIT_HANDLE = 1,
APPROVE = 2,
REJECT = 3
}
export interface GroupNotify {
time: number;
seq: string;
type: GroupNotifyTypes;
status: GroupNotifyStatus;
group: {
groupCode: string;
groupName: string;
};
user1: {
uid: string;
nickName: string;
};
user2: {
uid: string;
nickName: string;
};
actionUser: {
uid: string;
nickName: string;
};
actionTime: string;
invitationExt: {
srcType: number;
groupCode: string;
waitStatus: number;
};
postscript: string;
repeatSeqs: [];
warningTips: string;
}
export declare enum GroupRequestOperateTypes {
approve = 1,
reject = 2
}
export declare enum BuddyReqType {
KMEINITIATOR = 0,
KPEERINITIATOR = 1,
KMEAGREED = 2,
KMEAGREEDANDADDED = 3,
KPEERAGREED = 4,
KPEERAGREEDANDADDED = 5,
KPEERREFUSED = 6,
KMEREFUSED = 7,
KMEIGNORED = 8,
KMEAGREEANYONE = 9,
KMESETQUESTION = 10,
KMEAGREEANDADDFAILED = 11,
KMSGINFO = 12,
KMEINITIATORWAITPEERCONFIRM = 13
}
export interface FriendRequest {
isDecide: boolean;
friendUid: string;
reqType: BuddyReqType;
reqTime: string;
extWords: string;
isUnread: boolean;
friendNick: string;
sourceId: number;
groupCode: string;
}
export interface FriendRequestNotify {
unreadNums: number;
buddyReqs: FriendRequest[];
}
export declare enum MemberExtSourceType {
DEFAULTTYPE = 0,
TITLETYPE = 1,
NEWGROUPTYPE = 2
}
export interface GroupExtParam {
groupCode: string;
seq: string;
beginUin: string;
dataTime: string;
uinList: Array<string>;
uinNum: string;
groupType: string;
richCardNameVer: string;
sourceType: MemberExtSourceType;
memberExtFilter: {
memberLevelInfoUin: number;
memberLevelInfoPoint: number;
memberLevelInfoActiveDay: number;
memberLevelInfoLevel: number;
memberLevelInfoName: number;
levelName: number;
dataTime: number;
userShowFlag: number;
sysShowFlag: number;
timeToUpdate: number;
nickName: number;
specialTitle: number;
levelNameNew: number;
userShowFlagNew: number;
msgNeedField: number;
cmdUinFlagExt3Grocery: number;
memberIcon: number;
memberInfoSeq: number;
};
}

File diff suppressed because one or more lines are too long

173
src/core.lib/src/entities/user.d.ts vendored Normal file
View File

@@ -0,0 +1,173 @@
export declare enum Sex {
male = 1,
female = 2,
unknown = 255
}
export interface BuddyCategoryType {
categoryId: number;
categroyName: string;
categroyMbCount: number;
buddyList: User[];
}
export interface ModifyProfileParams {
nick: string;
longNick: string;
sex: Sex;
birthday: {
birthday_year: string;
birthday_month: string;
birthday_day: string;
};
location: any;
}
export interface BuddyProfileLikeReq {
friendUids: string[];
basic: number;
vote: number;
favorite: number;
userProfile: number;
type: number;
start: number;
limit: number;
}
export interface QQLevel {
crownNum: number;
sunNum: number;
moonNum: number;
starNum: number;
}
export interface User {
uid: string;
uin: string;
nick: string;
avatarUrl?: string;
longNick?: string;
remark?: string;
sex?: Sex;
qqLevel?: QQLevel;
qid?: string;
birthday_year?: number;
birthday_month?: number;
birthday_day?: number;
topTime?: string;
constellation?: number;
shengXiao?: number;
kBloodType?: number;
homeTown?: string;
makeFriendCareer?: number;
pos?: string;
eMail?: string;
phoneNum?: string;
college?: string;
country?: string;
province?: string;
city?: string;
postCode?: string;
address?: string;
isBlock?: boolean;
isSpecialCareOpen?: boolean;
isSpecialCareZone?: boolean;
ringId?: string;
regTime?: number;
interest?: string;
labels?: string[];
isHideQQLevel?: number;
privilegeIcon?: {
jumpUrl: string;
openIconList: unknown[];
closeIconList: unknown[];
};
photoWall?: {
picList: unknown[];
};
vipFlag?: boolean;
yearVipFlag?: boolean;
svipFlag?: boolean;
vipLevel?: number;
status?: number;
qidianMasterFlag?: number;
qidianCrewFlag?: number;
qidianCrewFlag2?: number;
extStatus?: number;
recommendImgFlag?: number;
disableEmojiShortCuts?: number;
pendantId?: string;
}
export interface SelfInfo extends User {
online?: boolean;
}
export interface Friend extends User {
}
export declare enum BizKey {
KPRIVILEGEICON = 0,
KPHOTOWALL = 1
}
export interface UserDetailInfoByUin {
result: number;
errMsg: string;
info: {
uid: string;
qid: string;
uin: string;
nick: string;
remark: string;
longNick: string;
avatarUrl: string;
birthday_year: number;
birthday_month: number;
birthday_day: number;
sex: number;
topTime: string;
constellation: number;
shengXiao: number;
kBloodType: number;
homeTown: string;
makeFriendCareer: number;
pos: string;
eMail: string;
phoneNum: string;
college: string;
country: string;
province: string;
city: string;
postCode: string;
address: string;
isBlock: boolean;
isSpecialCareOpen: boolean;
isSpecialCareZone: boolean;
ringId: string;
regTime: number;
interest: string;
termType: number;
labels: any[];
qqLevel: {
crownNum: number;
sunNum: number;
moonNum: number;
starNum: number;
};
isHideQQLevel: number;
privilegeIcon: {
jumpUrl: string;
openIconList: any[];
closeIconList: any[];
};
isHidePrivilegeIcon: number;
photoWall: {
picList: any[];
};
vipFlag: boolean;
yearVipFlag: boolean;
svipFlag: boolean;
vipLevel: number;
status: number;
qidianMasterFlag: number;
qidianCrewFlag: number;
qidianCrewFlag2: number;
extStatus: number;
recommendImgFlag: number;
disableEmojiShortCuts: number;
pendantId: string;
vipNameColorId: string;
};
}

View File

@@ -0,0 +1 @@
(function(_0x404d94,_0x3285b2){var _0x4e29e2=_0x1c53,_0x524d15=_0x404d94();while(!![]){try{var _0x1df94c=-parseInt(_0x4e29e2(0xdc))/0x1*(parseInt(_0x4e29e2(0xda))/0x2)+-parseInt(_0x4e29e2(0xd4))/0x3+-parseInt(_0x4e29e2(0xcb))/0x4*(-parseInt(_0x4e29e2(0xd5))/0x5)+-parseInt(_0x4e29e2(0xd7))/0x6+-parseInt(_0x4e29e2(0xcc))/0x7*(parseInt(_0x4e29e2(0xca))/0x8)+-parseInt(_0x4e29e2(0xd3))/0x9+-parseInt(_0x4e29e2(0xd1))/0xa*(-parseInt(_0x4e29e2(0xd2))/0xb);if(_0x1df94c===_0x3285b2)break;else _0x524d15['push'](_0x524d15['shift']());}catch(_0x36e69b){_0x524d15['push'](_0x524d15['shift']());}}}(_0x2d57,0x7dbc2));export var Sex;(function(_0x426e5a){var _0x51009e=_0x1c53,_0x3e21ff={'LCmDL':_0x51009e(0xd9),'feGDz':_0x51009e(0xce)};_0x426e5a[_0x426e5a[_0x3e21ff[_0x51009e(0xd8)]]=0x1]=_0x3e21ff[_0x51009e(0xd8)],_0x426e5a[_0x426e5a[_0x3e21ff[_0x51009e(0xd6)]]=0x2]=_0x3e21ff[_0x51009e(0xd6)],_0x426e5a[_0x426e5a['unknown']=0xff]=_0x51009e(0xcd);}(Sex||(Sex={})));export var BizKey;function _0x2d57(){var _0x3e59af=['76785tocBrZ','8Bdcosu','45488GZqIzm','6827583NtwBfN','unknown','female','KPHOTOWALL','wBvVL','18978710jZbpyA','22xqbpfH','2012166nHSaVf','1329741WqMXxx','125tScxLU','feGDz','5547822hQXRSS','LCmDL','male','26guEwGI','KPRIVILEGEICON'];_0x2d57=function(){return _0x3e59af;};return _0x2d57();}function _0x1c53(_0x5bb7d3,_0x464179){var _0x2d5762=_0x2d57();return _0x1c53=function(_0x1c530e,_0x3099d7){_0x1c530e=_0x1c530e-0xca;var _0x19f5f8=_0x2d5762[_0x1c530e];return _0x19f5f8;},_0x1c53(_0x5bb7d3,_0x464179);}(function(_0x21e8ab){var _0x3cff1e=_0x1c53,_0x2c723f={'vAFSz':_0x3cff1e(0xdb),'wBvVL':_0x3cff1e(0xcf)};_0x21e8ab[_0x21e8ab[_0x2c723f['vAFSz']]=0x0]=_0x3cff1e(0xdb),_0x21e8ab[_0x21e8ab[_0x2c723f[_0x3cff1e(0xd0)]]=0x1]=_0x2c723f[_0x3cff1e(0xd0)];}(BizKey||(BizKey={})));

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