diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..f30e5309
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,115 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "dev:shell",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "dev:shell"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "build:shell",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "build:shell"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "build:universal",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "build:universal"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "build:framework",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "build:framework"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "build:webui",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "build:webui"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "dev:universal",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "dev:universal"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "dev:framework",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "dev:framework"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "dev:webui",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "dev:webui"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "lint",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "lint"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "depend",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "depend"
+ ]
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "dev:depend",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": [
+ "run",
+ "dev:depend"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..cca4546e
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+nanaeonn@outlook.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/README.md b/README.md
index 3b8fc828..19a9a00c 100644
--- a/README.md
+++ b/README.md
@@ -1,67 +1,62 @@
-
+
+# NapCat
+

-
+
+_Modern protocol-side framework implemented based on NTQQ._
+
+> 云起兮风生,心向远方兮路未曾至.
+
---
-## 欢迎回家
-NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
-## 特性介绍
-- [x] **安装简单**:就算是笨蛋也能使用
-- [x] **性能友好**:就算是低内存也能使用
-- [x] **接口丰富**:就算是没有也能使用
-- [x] **稳定好用**:就算是被捉也能使用
+## Welcome
++ NapCatQQ is a modern implementation of the Bot protocol based on NTQQ.
+ - NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
-## 使用框架
+## Feature
+
++ **Easy to Use**
+ - 作为初学者能够轻松使用.
++ **Quick and Efficient**
+ - 在低内存操作系统长时运行.
++ **Rich API Interface**
+ - 完整实现了大部分标准接口.
++ **Stable and Reliable**
+ - 持续稳定的开发与维护.
+## Quick Start
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必查看如下文档看使用教程
-### 文档地址
+## Link
-[Cloudflare.Worker](https://doc.napneko.icu/)
+| Docs | [](https://napneko.github.io/) | [](https://doc.napneko.icu/) | [](https://napcat.napneko.icu/) |
+|:-:|:-:|:-:|:-:|
-[Cloudflare.HKServer](https://napcat.napneko.icu/)
+| Docs | [](https://napneko.pages.dev/) | [](https://docs.napcat.cyou/) | [](https://www.napcat.wiki) |
+|:-:|:-:|:-:|:-:|
-[Github.IO](https://napneko.github.io/)
+| Contact | [](https://qm.qq.com/q/I6LU87a0Yq) | [](https://qm.qq.com/q/HaRcfrHpUk) | [](https://t.me/MelodicMoonlight) |
+|:-:|:-:|:-:|:-:|
-[Cloudflare.Pages](https://napneko.pages.dev/)
+## Thanks
-[Server.Other](https://docs.napcat.cyou/)
++ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
-[NapCat.Wiki](https://www.napcat.wiki)
++ [LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目部分开发
-## 回家旅途
-[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
-
-[QQ Group#2](https://qm.qq.com/q/HaRcfrHpUk)
-
-[Telegram](https://t.me/MelodicMoonlight)
-
-> QQ Group#2 准许Bot / Telegram与QQ Group#2 为新建Group
-
-## 性能设计/协议标准
-NapCat 已实现90%+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
-
-由此设计带来一系列好处,在开发中,获取群员列表通常小于50Ms,单条文本消息发送在320Ms以内,在1k+的群聊流畅运行,同时带来一些副作用,消息Id无法持久,无法上报撤回消息原始内容。
-
-NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
-
-## 感谢他们
-感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
-
-感谢 React 强力驱动 NapCat.WebUi
-
-不过最最重要的 还是需要感谢屏幕前的你哦~
++ 不过最最重要的 还是需要感谢屏幕前的你哦~
---
-## 特殊感谢
-[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
+## License
+本项目采用 混合协议 开源,因此使用本项目时,你需要注意以下几点:
+1. 第三方库代码或修改部分遵循其原始开源许可.
+2. 本项目获取部分项目授权而不受部分约束
+2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
-## 开源附加
-
-任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
+**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..faee4747
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,11 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| > 4.0 | :white_check_mark: |
+| < 4.0 | :x: |
+
+## Reporting a Vulnerability
+you should open an issue
diff --git a/external/LiteLoaderWrapper.zip b/external/LiteLoaderWrapper.zip
index d586a609..990c0c5b 100644
Binary files a/external/LiteLoaderWrapper.zip and b/external/LiteLoaderWrapper.zip differ
diff --git a/launcher/launcher-user.bat b/launcher/launcher-user.bat
index 86410c9b..224ea4eb 100644
--- a/launcher/launcher-user.bat
+++ b/launcher/launcher-user.bat
@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
- echo provided QQ path is invalid: %QQpath%
+ echo provided QQ path is invalid
pause
exit /b
)
diff --git a/launcher/launcher-win10-user.bat b/launcher/launcher-win10-user.bat
index debf0acb..84050e0b 100644
--- a/launcher/launcher-win10-user.bat
+++ b/launcher/launcher-win10-user.bat
@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
- echo provided QQ path is invalid: %QQpath%
+ echo provided QQ path is invalid
pause
exit /b
)
diff --git a/launcher/launcher-win10.bat b/launcher/launcher-win10.bat
index 0ab1677d..8e458178 100644
--- a/launcher/launcher-win10.bat
+++ b/launcher/launcher-win10.bat
@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
-if not exist "%QQpath%" (
- echo provided QQ path is invalid: %QQpath%
+if not exist "%QQPath%" (
+ echo provided QQ path is invalid
pause
exit /b
)
diff --git a/launcher/launcher.bat b/launcher/launcher.bat
index a263f2eb..970a7edd 100644
--- a/launcher/launcher.bat
+++ b/launcher/launcher.bat
@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
-if not exist "%QQpath%" (
- echo provided QQ path is invalid: %QQpath%
+if not exist "%QQPath%" (
+ echo provided QQ path is invalid
pause
exit /b
)
diff --git a/launcher/qqnt.json b/launcher/qqnt.json
index 837b4979..549b8c76 100644
--- a/launcher/qqnt.json
+++ b/launcher/qqnt.json
@@ -1,10 +1,9 @@
{
"name": "qq-chat",
- "version": "9.9.17-30899",
- "verHash": "ececf273",
- "linuxVersion": "3.2.15-30899",
- "linuxVerHash": "63c751e8",
- "type": "module",
+ "version": "9.9.18-32793",
+ "verHash": "d43f097e",
+ "linuxVersion": "3.2.16-32793",
+ "linuxVerHash": "ee4bd910",
"private": true,
"description": "QQ",
"productName": "QQ",
@@ -17,10 +16,27 @@
"bin": {
"qd": "externals/devtools/cli/index.js"
},
+ "appid": {
+ "win32": "537258389",
+ "darwin": "537258412",
+ "linux": "537258424"
+ },
"main": "./loadNapCat.js",
- "buildVersion": "30899",
+ "peerDependenciesMeta": {
+ "*": {
+ "optional": true
+ }
+ },
+ "pnpm": {
+ "patchedDependencies": {
+ "@vue/runtime-dom@3.5.12": "patches/@vue__runtime-dom@3.5.12.patch",
+ "@swc/helpers@0.5.3": "patches/@swc__helpers@0.5.3.patch",
+ "vuex@4.1.0": "patches/vuex@4.1.0.patch"
+ }
+ },
+ "buildVersion": "32793",
"isPureShell": true,
"isByteCodeShell": true,
"platform": "win32",
"eleArch": "x64"
-}
+}
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index 5aa0f42b..e2569070 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
- "version": "4.5.23",
+ "version": "4.7.5",
"icon": "./logo.png",
"authors": [
{
diff --git a/napcat.webui/src/components/onebot/api/debug.tsx b/napcat.webui/src/components/onebot/api/debug.tsx
index c699edf6..f857bb9b 100644
--- a/napcat.webui/src/components/onebot/api/debug.tsx
+++ b/napcat.webui/src/components/onebot/api/debug.tsx
@@ -136,7 +136,7 @@ const OneBotApiDebug: React.FC = (props) => {
请求体
diff --git a/package.json b/package.json
index d3b3508f..6469ecef 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
- "version": "4.5.23",
+ "version": "4.7.5",
"scripts": {
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
@@ -45,13 +45,13 @@
"cors": "^2.8.5",
"esbuild": "0.25.0",
"eslint": "^9.14.0",
- "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-import-resolver-typescript": "^4.0.0",
"eslint-plugin-import": "^2.29.1",
"express-rate-limit": "^7.5.0",
"fast-xml-parser": "^4.3.6",
"file-type": "^20.0.0",
- "globals": "^15.12.0",
- "image-size": "^1.1.1",
+ "globals": "^16.0.0",
+ "image-size": "^2.0.1",
"json5": "^2.2.3",
"multer": "^1.4.5-lts.1",
"typescript": "^5.3.3",
@@ -59,15 +59,15 @@
"vite": "^6.0.1",
"vite-plugin-cp": "^4.0.8",
"vite-tsconfig-paths": "^5.1.0",
- "winston": "^3.17.0"
+ "napcat.protobuf": "^1.1.3",
+ "winston": "^3.17.0",
+ "compressing": "^1.10.1"
},
"dependencies": {
"@ffmpeg.wasm/core-mt": "^0.13.2",
"@napi-rs/canvas": "^0.1.67",
- "compressing": "^1.10.1",
"express": "^5.0.0",
"napcat.protobuf": "^1.1.2",
- "piscina": "^4.7.0",
"silk-wasm": "^3.6.1",
"ws": "^8.18.0"
}
diff --git a/src/common/audio-worker.ts b/src/common/audio-worker.ts
index 64d48039..c1984946 100644
--- a/src/common/audio-worker.ts
+++ b/src/common/audio-worker.ts
@@ -1,9 +1,20 @@
import { encode } from 'silk-wasm';
+import { parentPort } from 'worker_threads';
export interface EncodeArgs {
input: ArrayBufferView | ArrayBuffer
sampleRate: number
}
-export default async ({ input, sampleRate }: EncodeArgs) => {
+export function recvTask(cb: (taskData: T) => Promise) {
+ parentPort?.on('message', async (taskData: T) => {
+ try {
+ let ret = await cb(taskData);
+ parentPort?.postMessage(ret);
+ } catch (error: unknown) {
+ parentPort?.postMessage({ error: (error as Error).message });
+ }
+ });
+}
+recvTask(async ({ input, sampleRate }) => {
return await encode(input, sampleRate);
-};
+});
\ No newline at end of file
diff --git a/src/common/audio.ts b/src/common/audio.ts
index 07a69fe3..190a8a4d 100644
--- a/src/common/audio.ts
+++ b/src/common/audio.ts
@@ -1,4 +1,3 @@
-import Piscina from 'piscina';
import fsPromise from 'fs/promises';
import path from 'node:path';
import { randomUUID } from 'crypto';
@@ -6,16 +5,16 @@ import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-w
import { LogWrapper } from '@/common/log';
import { EncodeArgs } from '@/common/audio-worker';
import { FFmpegService } from '@/common/ffmpeg';
+import { runTask } from './worker';
+import { fileURLToPath } from 'node:url';
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
-async function getWorkerPath() {
- return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href;
+function getWorkerPath() {
+ //return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href;
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), 'audio-worker.mjs');
}
-const piscina = new Piscina({
- filename: await getWorkerPath(),
-});
async function guessDuration(pttPath: string, logger: LogWrapper) {
const pttFileInfo = await fsPromise.stat(pttPath);
@@ -46,7 +45,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
const { input, sampleRate } = isWav(file)
? await handleWavFile(file, filePath, pcmPath)
: { input: await FFmpegService.convert(filePath, pcmPath), sampleRate: 24000 };
- const silk = await piscina.run({ input: input, sampleRate: sampleRate });
+ const silk = await runTask(getWorkerPath(), { input: input, sampleRate: sampleRate });
fsPromise.unlink(pcmPath).catch((e) => logger.logError('删除临时文件失败', pcmPath, e));
await fsPromise.writeFile(pttPath, Buffer.from(silk.data));
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
diff --git a/src/common/ffmpeg-worker.ts b/src/common/ffmpeg-worker.ts
index 38b0afa8..40228a8d 100644
--- a/src/common/ffmpeg-worker.ts
+++ b/src/common/ffmpeg-worker.ts
@@ -5,6 +5,17 @@ import { readFileSync, statSync, writeFileSync } from 'fs';
import type { VideoInfo } from './video';
import { fileTypeFromFile } from 'file-type';
import imageSize from 'image-size';
+import { parentPort } from 'worker_threads';
+export function recvTask(cb: (taskData: T) => Promise) {
+ parentPort?.on('message', async (taskData: T) => {
+ try {
+ let ret = await cb(taskData);
+ parentPort?.postMessage(ret);
+ } catch (error: unknown) {
+ parentPort?.postMessage({ error: (error as Error).message });
+ }
+ });
+}
class FFmpegService {
public static async extractThumbnail(videoPath: string, thumbnailPath: string): Promise {
const ffmpegInstance = await FFmpeg.create({ core: '@ffmpeg.wasm/core-mt' });
@@ -137,15 +148,18 @@ interface FFmpegTask {
}
export default async function handleFFmpegTask({ method, args }: FFmpegTask): Promise {
switch (method) {
- case 'extractThumbnail':
- return await FFmpegService.extractThumbnail(...args as [string, string]);
- case 'convertFile':
- return await FFmpegService.convertFile(...args as [string, string, string]);
- case 'convert':
- return await FFmpegService.convert(...args as [string, string]);
- case 'getVideoInfo':
- return await FFmpegService.getVideoInfo(...args as [string, string]);
- default:
- throw new Error(`Unknown method: ${method}`);
+ case 'extractThumbnail':
+ return await FFmpegService.extractThumbnail(...args as [string, string]);
+ case 'convertFile':
+ return await FFmpegService.convertFile(...args as [string, string, string]);
+ case 'convert':
+ return await FFmpegService.convert(...args as [string, string]);
+ case 'getVideoInfo':
+ return await FFmpegService.getVideoInfo(...args as [string, string]);
+ default:
+ throw new Error(`Unknown method: ${method}`);
}
-}
\ No newline at end of file
+}
+recvTask(async ({ method, args }: FFmpegTask) => {
+ return await handleFFmpegTask({ method, args });
+});
\ No newline at end of file
diff --git a/src/common/ffmpeg.ts b/src/common/ffmpeg.ts
index dbb543f4..737c761a 100644
--- a/src/common/ffmpeg.ts
+++ b/src/common/ffmpeg.ts
@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import Piscina from 'piscina';
import { VideoInfo } from './video';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { runTask } from './worker';
type EncodeArgs = {
method: 'extractThumbnail' | 'convertFile' | 'convert' | 'getVideoInfo';
@@ -9,42 +11,26 @@ type EncodeArgs = {
type EncodeResult = any;
-async function getWorkerPath() {
- return new URL(/* @vite-ignore */ './ffmpeg-worker.mjs', import.meta.url).href;
+function getWorkerPath() {
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), './ffmpeg-worker.mjs');
}
export class FFmpegService {
public static async extractThumbnail(videoPath: string, thumbnailPath: string): Promise {
- const piscina = new Piscina({
- filename: await getWorkerPath(),
- });
- await piscina.run({ method: 'extractThumbnail', args: [videoPath, thumbnailPath] });
- await piscina.destroy();
+ await runTask(getWorkerPath(), { method: 'extractThumbnail', args: [videoPath, thumbnailPath] });
}
public static async convertFile(inputFile: string, outputFile: string, format: string): Promise {
- const piscina = new Piscina({
- filename: await getWorkerPath(),
- });
- await piscina.run({ method: 'convertFile', args: [inputFile, outputFile, format] });
- await piscina.destroy();
+ await runTask(getWorkerPath(), { method: 'convertFile', args: [inputFile, outputFile, format] });
}
public static async convert(filePath: string, pcmPath: string): Promise {
- const piscina = new Piscina({
- filename: await getWorkerPath(),
- });
- const result = await piscina.run({ method: 'convert', args: [filePath, pcmPath] });
- await piscina.destroy();
+ const result = await runTask(getWorkerPath(), { method: 'convert', args: [filePath, pcmPath] });
return result;
}
public static async getVideoInfo(videoPath: string, thumbnailPath: string): Promise {
- const piscina = new Piscina({
- filename: await getWorkerPath(),
- });
- const result = await piscina.run({ method: 'getVideoInfo', args: [videoPath, thumbnailPath] });
- await piscina.destroy();
+ const result = await await runTask(getWorkerPath(), { method: 'getVideoInfo', args: [videoPath, thumbnailPath] });
return result;
}
}
diff --git a/src/common/store.ts b/src/common/store.ts
index 6c6b21c8..c88fb57a 100644
--- a/src/common/store.ts
+++ b/src/common/store.ts
@@ -163,7 +163,7 @@ class Store {
const current = this.get(key);
if (current === null) {
- this.set(key, 1);
+ this.set(key, 1, 60);
return 1;
}
@@ -180,7 +180,7 @@ class Store {
}
const newValue = numericValue + 1;
- this.set(key, newValue);
+ this.set(key, newValue, 60);
return newValue;
}
}
diff --git a/src/common/version.ts b/src/common/version.ts
index 8b7a24df..b1547e29 100644
--- a/src/common/version.ts
+++ b/src/common/version.ts
@@ -1 +1 @@
-export const napCatVersion = '4.5.23';
+export const napCatVersion = '4.7.5';
diff --git a/src/common/worker.ts b/src/common/worker.ts
new file mode 100644
index 00000000..f14ea3bb
--- /dev/null
+++ b/src/common/worker.ts
@@ -0,0 +1,29 @@
+import { Worker } from 'worker_threads';
+
+export async function runTask(workerScript: string, taskData: T): Promise {
+ let worker = new Worker(workerScript);
+ try {
+ return await new Promise((resolve, reject) => {
+ worker.on('message', (result: R) => {
+ resolve(result);
+ });
+
+ worker.on('error', (error) => {
+ reject(new Error(`Worker error: ${error.message}`));
+ });
+
+ worker.on('exit', (code) => {
+ if (code !== 0) {
+ reject(new Error(`Worker stopped with exit code ${code}`));
+ }
+ });
+ worker.postMessage(taskData);
+ });
+ } catch (error: unknown) {
+ throw new Error(`Failed to run task: ${(error as Error).message}`);
+ } finally {
+ // Ensure the worker is terminated after the promise is settled
+ worker.terminate();
+ }
+}
+
diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts
index a6847ac7..f058a15a 100644
--- a/src/core/apis/file.ts
+++ b/src/core/apis/file.ts
@@ -41,7 +41,8 @@ export class NTQQFileApi {
this.context = context;
this.core = core;
this.rkeyManager = new RkeyManager([
- 'https://rkey.napneko.icu/rkeys'
+ 'https://ss.xingzhige.com/music_card/rkey', // 国内
+ 'https://secret-service.bietiaop.com/rkeys',//国内
],
this.context.logger
);
diff --git a/src/core/apis/group.ts b/src/core/apis/group.ts
index 4ac216b7..80555d98 100644
--- a/src/core/apis/group.ts
+++ b/src/core/apis/group.ts
@@ -27,6 +27,9 @@ export class NTQQGroupApi {
this.core = core;
}
+ async setGroupRemark(groupCode: string, remark: string) {
+ return this.context.session.getGroupService().modifyGroupRemark(groupCode, remark);
+ }
async fetchGroupDetail(groupCode: string) {
const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getGroupDetailInfo',
@@ -165,7 +168,13 @@ export class NTQQGroupApi {
return this.groupMemberCache.get(groupCode);
}
-
+ async refreshGroupMemberCachePartial(groupCode: string, uid: string) {
+ const member = await this.getGroupMemberEx(groupCode, uid, true);
+ if (member) {
+ this.groupMemberCache.get(groupCode)?.set(uid, member);
+ }
+ return member;
+ }
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
const groupCodeStr = groupCode.toString();
const memberUinOrUidStr = memberUinOrUid.toString();
@@ -339,9 +348,9 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl);
}
- async handleGroupRequest(notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
+ async handleGroupRequest(doubt: boolean, notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
return this.context.session.getGroupService().operateSysNotify(
- false,
+ doubt,
{
operateType: operateType,
targetMsg: {
diff --git a/src/core/apis/msg.ts b/src/core/apis/msg.ts
index dda4af1a..51b67951 100644
--- a/src/core/apis/msg.ts
+++ b/src/core/apis/msg.ts
@@ -136,6 +136,20 @@ export class NTQQMsgApi {
});
}
+ async queryFirstMsgBySender(peer: Peer, SendersUid: string[]) {
+ console.log(peer, SendersUid);
+ return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
+ chatInfo: peer,
+ filterMsgType: [],
+ filterSendersUid: SendersUid,
+ filterMsgToTime: '0',
+ filterMsgFromTime: '0',
+ isReverseOrder: true,
+ isIncludeCurrent: true,
+ pageLimit: 20000,
+ });
+ }
+
async setMsgRead(peer: Peer) {
return this.context.session.getMsgService().setMsgRead(peer);
}
diff --git a/src/core/external/appid.json b/src/core/external/appid.json
index 85a9118e..8a10c374 100644
--- a/src/core/external/appid.json
+++ b/src/core/external/appid.json
@@ -258,5 +258,37 @@
"9.9.17-31363": {
"appid": 537266500,
"qua": "V1_WIN_NQ_9.9.17_31363_GW_B"
+ },
+ "3.2.16-32690": {
+ "appid": 537271229,
+ "qua": "V1_LNX_NQ_3.2.16_32690_GW_B"
+ },
+ "9.9.18-32690": {
+ "appid": 537271194,
+ "qua": "V1_WIN_NQ_9.9.18_32690_GW_B"
+ },
+ "6.9.66-32690": {
+ "appid": 537271218,
+ "qua": "V1_MAC_NQ_6.9.66_32690_GW_B"
+ },
+ "3.2.16-32721": {
+ "appid": 537271229,
+ "qua": "V1_LNX_NQ_3.2.16_32721_GW_B"
+ },
+ "9.9.18-32793": {
+ "appid": 537271244,
+ "qua": "V1_WIN_NQ_9.9.18_32793_GW_B"
+ },
+ "3.2.16-32793": {
+ "appid": 537271279,
+ "qua": "V1_LNX_NQ_3.2.16_32793_GW_B"
+ },
+ "3.2.16-32869": {
+ "appid": 537271329,
+ "qua": "V1_LNX_NQ_3.2.16_32869_GW_B"
+ },
+ "9.9.18-32869": {
+ "appid": 537271294,
+ "qua": "V1_WIN_NQ_9.9.18_32869_GW_B"
}
}
\ No newline at end of file
diff --git a/src/core/external/napcat.json b/src/core/external/napcat.json
index 44952ac2..dcce2174 100644
--- a/src/core/external/napcat.json
+++ b/src/core/external/napcat.json
@@ -4,5 +4,6 @@
"fileLogLevel": "debug",
"consoleLogLevel": "info",
"packetBackend": "auto",
- "packetServer": ""
-}
+ "packetServer": "",
+ "o3HookMode": 1
+ }
\ No newline at end of file
diff --git a/src/core/external/offset.json b/src/core/external/offset.json
index 7b52b619..ed6bec37 100644
--- a/src/core/external/offset.json
+++ b/src/core/external/offset.json
@@ -246,5 +246,49 @@
"6.9.65-31363-arm64": {
"send": "422CEF8",
"recv": "422F710"
+ },
+ "9.9.18-32690-x64": {
+ "send": "39F9630",
+ "recv": "39FDE30"
+ },
+ "3.2.16-32690-x64": {
+ "send": "A5E24C0",
+ "recv": "A5E5EE0"
+ },
+ "3.2.16-32690-arm64": {
+ "send": "7226630",
+ "recv": "7229F60"
+ },
+ "3.2.16-32721-x64": {
+ "send": "A5E24C0",
+ "recv": "A5E5EE0"
+ },
+ "3.2.16-32721-arm64": {
+ "send": "7226630",
+ "recv": "7229F60"
+ },
+ "9.9.18-32793-x64": {
+ "send": "39F9A30",
+ "recv": "39FE230"
+ },
+ "3.2.16-32793-x64": {
+ "send": "A5E24C0",
+ "recv": "A5E5EE0"
+ },
+ "3.2.16-32793-arm64": {
+ "send": "7226630",
+ "recv": "7229F60"
+ },
+ "9.9.18-32869-x64": {
+ "send": "39F9A30",
+ "recv": "39FE230"
+ },
+ "3.2.16-32869-x64": {
+ "send": "A5E24C0",
+ "recv": "A5E5EE0"
+ },
+ "3.2.16-32869-arm64": {
+ "send": "7226630",
+ "recv": "7229F60"
}
-}
+}
\ No newline at end of file
diff --git a/src/core/helper/config.ts b/src/core/helper/config.ts
index bc6781da..0c2540c1 100644
--- a/src/core/helper/config.ts
+++ b/src/core/helper/config.ts
@@ -10,6 +10,7 @@ export const NapcatConfigSchema = Type.Object({
consoleLogLevel: Type.String({ default: 'info' }),
packetBackend: Type.String({ default: 'auto' }),
packetServer: Type.String({ default: '' }),
+ o3HookMode: Type.Number({ default: 0 }),
});
export type NapcatConfig = Static;
diff --git a/src/core/listeners/NodeIKernelLoginListener.ts b/src/core/listeners/NodeIKernelLoginListener.ts
index 91046e0b..51d05711 100644
--- a/src/core/listeners/NodeIKernelLoginListener.ts
+++ b/src/core/listeners/NodeIKernelLoginListener.ts
@@ -1,5 +1,5 @@
export class NodeIKernelLoginListener {
- onLoginConnected(...args: any[]): any {
+ onLoginConnected(): Promise | void {
}
onLoginDisConnected(...args: any[]): any {
diff --git a/src/core/listeners/NodeIKernelSearchListener.ts b/src/core/listeners/NodeIKernelSearchListener.ts
index cd7d3a10..e0a34e9d 100644
--- a/src/core/listeners/NodeIKernelSearchListener.ts
+++ b/src/core/listeners/NodeIKernelSearchListener.ts
@@ -1,4 +1,4 @@
-import { ChatType } from '@/core';
+import { ChatType, RawMessage } from '@/core';
export interface SearchGroupInfo {
groupCode: string;
ownerUid: string;
@@ -56,7 +56,7 @@ export interface GroupSearchResult {
nextPos: number;
}
export interface NodeIKernelSearchListener {
-
+
onSearchGroupResult(params: GroupSearchResult): any;
onSearchFileKeywordsResult(params: {
@@ -94,4 +94,27 @@ export interface NodeIKernelSearchListener {
}[]
}[]
}): any;
+
+ onSearchMsgKeywordsResult(params: {
+ searchId: string,
+ hasMore: boolean,
+ resultItems: Array<{
+ msgId: string,
+ msgSeq: string,
+ msgTime: string,
+ senderUid: string,
+ senderUin: string,
+ senderNick: string,
+ senderNickHits: unknown[],
+ senderRemark: string,
+ senderRemarkHits: unknown[],
+ senderCard: string,
+ senderCardHits: unknown[],
+ fieldType: number,
+ fieldText: string,
+ msgRecord: RawMessage;
+ hitsInfo: Array,
+ msgAbstract: unknown,
+ }>
+ }): void | Promise;
}
diff --git a/src/core/packet/client/nativeClient.ts b/src/core/packet/client/nativeClient.ts
index d031ec11..0804751c 100644
--- a/src/core/packet/client/nativeClient.ts
+++ b/src/core/packet/client/nativeClient.ts
@@ -12,7 +12,7 @@ import { ProtoBufDecode } from 'napcat.protobuf';
export const MsgData = new LRUCache(5000);
// 0 send 1 recv
export interface NativePacketExportType {
- InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
+ InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean;
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
}
@@ -43,6 +43,7 @@ export class NativePacketClient extends IPacketClient {
const platform = process.platform + '.' + process.arch;
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
+
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
if (type === 0 && this.cb.get(trace_id + 'recv')) {
@@ -69,7 +70,7 @@ export class NativePacketClient extends IPacketClient {
}
}
- });
+ }, this.napcore.config.o3HookMode == 1);
this.available = true;
}
diff --git a/src/core/packet/highway/highwayContext.ts b/src/core/packet/highway/highwayContext.ts
index 76904cfd..21413dae 100644
--- a/src/core/packet/highway/highwayContext.ts
+++ b/src/core/packet/highway/highwayContext.ts
@@ -144,7 +144,7 @@ export class PacketHighwayContext {
const ukey = preRespData.upload.uKey;
if (ukey && ukey != '') {
this.logger.debug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
+ const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
@@ -181,7 +181,7 @@ export class PacketHighwayContext {
const ukey = preRespData.upload.uKey;
if (ukey && ukey != '') {
this.logger.debug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
+ const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
@@ -219,7 +219,7 @@ export class PacketHighwayContext {
const ukey = preRespData.upload.uKey;
if (ukey && ukey != '') {
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
+ const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
@@ -244,16 +244,16 @@ export class PacketHighwayContext {
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
}
const subFile = preRespData.upload.subFileInfos[0];
- if (subFile.uKey && subFile.uKey != '') {
- this.logger.debug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
+ if (subFile!.uKey && subFile!.uKey != '') {
+ this.logger.debug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile!.uKey}, need upload!`);
+ const index = preRespData.upload.msgInfo.msgInfoBody[1]!.index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
- uKey: subFile.uKey,
+ uKey: subFile!.uKey,
network: {
- ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
+ ipv4S: oidbIpv4s2HighwayIpv4s(subFile!.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
@@ -269,7 +269,7 @@ export class PacketHighwayContext {
extend
);
} else {
- this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
+ this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile!.uKey}, don't need upload!`);
}
video.msgInfo = preRespData.upload.msgInfo;
}
@@ -284,7 +284,7 @@ export class PacketHighwayContext {
const ukey = preRespData.upload.uKey;
if (ukey && ukey != '') {
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
+ const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
@@ -309,16 +309,16 @@ export class PacketHighwayContext {
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
}
const subFile = preRespData.upload.subFileInfos[0];
- if (subFile.uKey && subFile.uKey != '') {
- this.logger.debug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
+ if (subFile!.uKey && subFile!.uKey != '') {
+ this.logger.debug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile!.uKey}, need upload!`);
+ const index = preRespData.upload.msgInfo.msgInfoBody[1]!.index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
fileUuid: index.fileUuid,
- uKey: subFile.uKey,
+ uKey: subFile!.uKey,
network: {
- ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
+ ipv4S: oidbIpv4s2HighwayIpv4s(subFile!.ipv4S)
},
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
blockSize: BlockSize,
@@ -334,7 +334,7 @@ export class PacketHighwayContext {
extend
);
} else {
- this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
+ this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile!.uKey}, don't need upload!`);
}
video.msgInfo = preRespData.upload.msgInfo;
}
@@ -347,7 +347,7 @@ export class PacketHighwayContext {
const ukey = preRespData.upload.uKey;
if (ukey && ukey != '') {
this.logger.debug(`[Highway] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
+ const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
@@ -383,7 +383,7 @@ export class PacketHighwayContext {
const ukey = preRespData.upload.uKey;
if (ukey && ukey != '') {
this.logger.debug(`[Highway] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
- const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
+ const index = preRespData.upload.msgInfo.msgInfoBody[0]!.index;
const md5 = Buffer.from(index.info.fileHash, 'hex');
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
diff --git a/src/core/packet/transformer/action/SetSpecialTitle.ts b/src/core/packet/transformer/action/SetSpecialTitle.ts
index c7dbb2ff..9edeb008 100644
--- a/src/core/packet/transformer/action/SetSpecialTitle.ts
+++ b/src/core/packet/transformer/action/SetSpecialTitle.ts
@@ -9,15 +9,14 @@ class SetSpecialTitle extends PacketTransformer
}
build(groupCode: number, uid: string, tittle: string): OidbPacket {
- const oidb_0x8FC_2_body = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2_Body).encode({
- targetUid: uid,
- specialTitle: tittle,
- expiredTime: -1,
- uinName: tittle
- });
const oidb_0x8FC_2 = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2).encode({
groupUin: +groupCode,
- body: oidb_0x8FC_2_body
+ body: {
+ targetUid: uid,
+ specialTitle: tittle,
+ expiredTime: -1,
+ uinName: tittle
+ }
});
return OidbBase.build(0x8FC, 2, oidb_0x8FC_2, false, false);
}
diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts b/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts
index e96dd951..4f96409b 100644
--- a/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts
+++ b/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts
@@ -4,12 +4,12 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core';
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
export const OidbSvcTrpcTcp0X8FC_2_Body = {
targetUid: ProtoField(1, ScalarType.STRING),
- specialTitle: ProtoField(5, ScalarType.STRING),
- expiredTime: ProtoField(6, ScalarType.SINT32),
- uinName: ProtoField(7, ScalarType.STRING),
+ specialTitle: ProtoField(5, ScalarType.STRING, true),
+ expiredTime: ProtoField(6, ScalarType.INT32),
+ uinName: ProtoField(7, ScalarType.STRING, true),
targetName: ProtoField(8, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0X8FC_2 = {
groupUin: ProtoField(1, ScalarType.UINT32),
- body: ProtoField(3, ScalarType.BYTES),
+ body: ProtoField(3, () => OidbSvcTrpcTcp0X8FC_2_Body),
};
diff --git a/src/core/services/NodeIKernelGroupService.ts b/src/core/services/NodeIKernelGroupService.ts
index b991c83b..babcad73 100644
--- a/src/core/services/NodeIKernelGroupService.ts
+++ b/src/core/services/NodeIKernelGroupService.ts
@@ -165,7 +165,7 @@ export interface NodeIKernelGroupService {
modifyGroupName(groupCode: string, groupName: string, isNormalMember: boolean): Promise;
- modifyGroupRemark(groupCode: string, remark: string): void;
+ modifyGroupRemark(groupCode: string, remark: string): Promise;
modifyGroupDetailInfo(groupCode: string, arg: unknown): void;
diff --git a/src/core/services/NodeIKernelLoginService.ts b/src/core/services/NodeIKernelLoginService.ts
index 8c90144a..c3f7602c 100644
--- a/src/core/services/NodeIKernelLoginService.ts
+++ b/src/core/services/NodeIKernelLoginService.ts
@@ -60,7 +60,10 @@ export interface QuickLoginResult {
}
export interface NodeIKernelLoginService {
+ getMsfStatus: () => number;
+
setLoginMiscData(arg0: string, value: string): unknown;
+
getMachineGuid(): string;
get(): NodeIKernelLoginService;
diff --git a/src/core/services/NodeIKernelSearchService.ts b/src/core/services/NodeIKernelSearchService.ts
index 56eeb992..7eda4f8a 100644
--- a/src/core/services/NodeIKernelSearchService.ts
+++ b/src/core/services/NodeIKernelSearchService.ts
@@ -1,4 +1,4 @@
-import { ChatType } from '@/core/types';
+import { ChatType, Peer } from '@/core/types';
import { GeneralCallResult } from './common';
export interface NodeIKernelSearchService {
@@ -54,7 +54,7 @@ export interface NodeIKernelSearchService {
cancelSearchChatMsgs(...args: unknown[]): unknown;// needs 3 arguments
- searchMsgWithKeywords(...args: unknown[]): unknown;// needs 2 arguments
+ searchMsgWithKeywords(keyWords: string[], param: Peer & { searchFields: number, pageLimit: number }): Promise;
searchMoreMsgWithKeywords(...args: unknown[]): unknown;// needs 1 arguments
diff --git a/src/framework/napcat.ts b/src/framework/napcat.ts
index b1451dc2..d96e1ff3 100644
--- a/src/framework/napcat.ts
+++ b/src/framework/napcat.ts
@@ -7,13 +7,13 @@ import { SelfInfo } from '@/core/types';
import { NodeIKernelLoginListener } from '@/core/listeners';
import { NodeIKernelLoginService } from '@/core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
-import { InitWebUi, WebUiConfig } from '@/webui';
+import { InitWebUi, WebUiConfig, webUiRuntimePort } from '@/webui';
import { NapCatOneBot11Adapter } from '@/onebot';
//Framework ES入口文件
export async function getWebUiUrl() {
const WebUiConfigData = (await WebUiConfig.GetWebUIConfig());
- return 'http://127.0.0.1:' + WebUiConfigData.port + '/webui/?token=' + WebUiConfigData.token;
+ return 'http://127.0.0.1:' + webUiRuntimePort + '/webui/?token=' + WebUiConfigData.token;
}
export async function NCoreInitFramework(
diff --git a/src/native/packet/MoeHoo.linux.arm64.node b/src/native/packet/MoeHoo.linux.arm64.node
index 99c45b31..bd2c2eee 100644
Binary files a/src/native/packet/MoeHoo.linux.arm64.node and b/src/native/packet/MoeHoo.linux.arm64.node differ
diff --git a/src/native/packet/MoeHoo.linux.x64.node b/src/native/packet/MoeHoo.linux.x64.node
index ac4c5b9d..851dd577 100644
Binary files a/src/native/packet/MoeHoo.linux.x64.node and b/src/native/packet/MoeHoo.linux.x64.node differ
diff --git a/src/native/packet/MoeHoo.win32.x64.node b/src/native/packet/MoeHoo.win32.x64.node
index ab1701c0..82bf80c2 100644
Binary files a/src/native/packet/MoeHoo.win32.x64.node and b/src/native/packet/MoeHoo.win32.x64.node differ
diff --git a/src/onebot/action/extends/GetUnidirectionalFriendList.ts b/src/onebot/action/extends/GetUnidirectionalFriendList.ts
new file mode 100644
index 00000000..96de0f7d
--- /dev/null
+++ b/src/onebot/action/extends/GetUnidirectionalFriendList.ts
@@ -0,0 +1,56 @@
+import { PacketHexStr } from '@/core/packet/transformer/base';
+import { OneBotAction } from '@/onebot/action/OneBotAction';
+import { ActionName } from '@/onebot/action/router';
+import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf';
+
+interface Friend {
+ uin: number;
+ uid: string;
+ nick_name: string;
+ age: number;
+ source: string;
+}
+
+interface Block {
+ str_uid: string;
+ bytes_source: string;
+ uint32_sex: number;
+ uint32_age: number;
+ bytes_nick: string;
+ uint64_uin: number;
+}
+
+export class GetUnidirectionalFriendList extends OneBotAction {
+ override actionName = ActionName.GetUnidirectionalFriendList;
+
+ async pack_data(data: string): Promise {
+ return ProtoBuf(class extends ProtoBufBase {
+ type = PBUint32(2, false, 0);
+ data = PBString(3, false, data);
+ }).encode();
+ }
+
+ async _handle(): Promise {
+ const self_id = this.core.selfInfo.uin;
+ const req_json = {
+ uint64_uin: self_id,
+ uint64_top: 0,
+ uint32_req_num: 99,
+ bytes_cookies: ""
+ };
+ const packed_data = await this.pack_data(JSON.stringify(req_json));
+ const data = Buffer.from(packed_data).toString('hex');
+ const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as PacketHexStr };
+ const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true);
+ const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data);
+ const block_list: Block[] = JSON.parse(block_json.data).rpt_block_list;
+
+ return block_list.map((block) => ({
+ uin: block.uint64_uin,
+ uid: block.str_uid,
+ nick_name: Buffer.from(block.bytes_nick, 'base64').toString(),
+ age: block.uint32_age,
+ source: Buffer.from(block.bytes_source, 'base64').toString()
+ }));
+ }
+}
\ No newline at end of file
diff --git a/src/onebot/action/extends/SetGroupRemark.ts b/src/onebot/action/extends/SetGroupRemark.ts
new file mode 100644
index 00000000..a8dbf5a9
--- /dev/null
+++ b/src/onebot/action/extends/SetGroupRemark.ts
@@ -0,0 +1,22 @@
+import { OneBotAction } from '@/onebot/action/OneBotAction';
+import { ActionName } from '@/onebot/action/router';
+import { Static, Type } from '@sinclair/typebox';
+
+const SchemaData = Type.Object({
+ group_id: Type.String(),
+ remark: Type.String(),
+});
+
+type Payload = Static;
+
+export default class SetGroupRemark extends OneBotAction {
+ override actionName = ActionName.SetGroupRemark;
+ override payloadSchema = SchemaData;
+ async _handle(payload: Payload): Promise {
+ let ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark);
+ if (ret.result != 0) {
+ throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`);
+ }
+ return null;
+ }
+}
diff --git a/src/onebot/action/extends/SetSpecialTittle.ts b/src/onebot/action/extends/SetSpecialTittle.ts
index c512c9bf..e344180b 100644
--- a/src/onebot/action/extends/SetSpecialTittle.ts
+++ b/src/onebot/action/extends/SetSpecialTittle.ts
@@ -5,7 +5,7 @@ import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]),
user_id: Type.Union([Type.Number(), Type.String()]),
- special_title: Type.String(),
+ special_title: Type.String({ default: '' }),
});
type Payload = Static;
@@ -16,7 +16,7 @@ export class SetSpecialTittle extends GetPacketStatusDepends {
async _handle(payload: Payload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
- if(!uid) throw new Error('User not found');
+ if (!uid) throw new Error('User not found');
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
}
}
diff --git a/src/onebot/action/group/GetGroupInfo.ts b/src/onebot/action/group/GetGroupInfo.ts
index 3c47a9df..98dbcecf 100644
--- a/src/onebot/action/group/GetGroupInfo.ts
+++ b/src/onebot/action/group/GetGroupInfo.ts
@@ -20,6 +20,7 @@ class GetGroupInfo extends OneBotAction {
const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString());
return {
...data,
+ group_remark: '',
group_id: +payload.group_id,
group_name: data.groupName,
member_count: data.memberNum,
diff --git a/src/onebot/action/group/SetGroupAddRequest.ts b/src/onebot/action/group/SetGroupAddRequest.ts
index 5f5f7cd2..8cd69bcd 100644
--- a/src/onebot/action/group/SetGroupAddRequest.ts
+++ b/src/onebot/action/group/SetGroupAddRequest.ts
@@ -20,11 +20,12 @@ export default class SetGroupAddRequest extends OneBotAction {
const approve = payload.approve?.toString() !== 'false';
const reason = payload.reason ?? ' ';
const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(flag);
- const notify = invite_notify ?? await this.findNotify(flag);
+ const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(flag);
if (!notify) {
throw new Error('No such request');
}
await this.core.apis.GroupApi.handleGroupRequest(
+ doubt,
notify,
approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
reason,
@@ -36,7 +37,8 @@ export default class SetGroupAddRequest extends OneBotAction {
let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag);
if (!notify) {
notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag);
+ return { doubt: true, notify };
}
- return notify;
+ return { doubt: false, notify };
}
}
\ No newline at end of file
diff --git a/src/onebot/action/group/SetGroupBan.ts b/src/onebot/action/group/SetGroupBan.ts
index aa5cdf26..3f30aa1f 100644
--- a/src/onebot/action/group/SetGroupBan.ts
+++ b/src/onebot/action/group/SetGroupBan.ts
@@ -16,6 +16,8 @@ export default class SetGroupBan extends OneBotAction {
async _handle(payload: Payload): Promise {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('uid error');
+ let member_role = (await this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, true))?.role;
+ if (member_role === 4) throw new Error('cannot ban owner');
// 例如无管理员权限时 result为 120101005 errMsg为 'ERR_NOT_GROUP_ADMIN'
let ret = await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
[{ uid: uid, timeStamp: +payload.duration }]);
diff --git a/src/onebot/action/index.ts b/src/onebot/action/index.ts
index c28a838e..fd2f74f9 100644
--- a/src/onebot/action/index.ts
+++ b/src/onebot/action/index.ts
@@ -107,10 +107,13 @@ import { SetDiyOnlineStatus } from './extends/SetDiyOnlineStatus';
import { BotExit } from './extends/BotExit';
import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton';
import { GetPrivateFileUrl } from './file/GetPrivateFileUrl';
+import { GetUnidirectionalFriendList } from './extends/GetUnidirectionalFriendList';
+import SetGroupRemark from './extends/SetGroupRemark';
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [
+ new SetGroupRemark(obContext, core),
new GetGroupInfoEx(obContext, core),
new FetchEmojiLike(obContext, core),
new GetFile(obContext, core),
@@ -226,7 +229,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetGroupSystemMsg(obContext, core),
new BotExit(obContext, core),
new ClickInlineKeyboardButton(obContext, core),
- new GetPrivateFileUrl(obContext,core)
+ new GetPrivateFileUrl(obContext, core),
+ new GetUnidirectionalFriendList(obContext, core),
];
type HandlerUnion = typeof actionHandlers[number];
diff --git a/src/onebot/action/router.ts b/src/onebot/action/router.ts
index 6bed2aa0..51b3892e 100644
--- a/src/onebot/action/router.ts
+++ b/src/onebot/action/router.ts
@@ -10,6 +10,7 @@ export interface InvalidCheckResult {
}
export const ActionName = {
+ SetGroupRemark: 'set_group_remark',
NapCat_GetPrivateFileUrl: 'get_private_file_url',
ClickInlineKeyboardButton: 'click_inline_keyboard_button',
GetUnidirectionalFriendList: 'get_unidirectional_friend_list',
diff --git a/src/onebot/api/group.ts b/src/onebot/api/group.ts
index 0018e05f..6f2f4314 100644
--- a/src/onebot/api/group.ts
+++ b/src/onebot/api/group.ts
@@ -49,6 +49,7 @@ export class OneBotGroupApi {
duration = -1;
}
}
+ await this.core.apis.GroupApi.refreshGroupMemberCachePartial(GroupCode, memberUid);
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, adminUid))?.uin;
if (memberUin && adminUin) {
return new OB11GroupBanEvent(
@@ -113,12 +114,16 @@ export class OneBotGroupApi {
async parseCardChangedEvent(msg: RawMessage) {
if (msg.senderUin && msg.senderUin !== '0') {
const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin);
+ await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid);
if (member && member.cardName !== msg.sendMemberName) {
const newCardName = msg.sendMemberName ?? '';
const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
member.cardName = newCardName;
return event;
}
+ if (member && member.nick !== msg.sendNickName) {
+ await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid);
+ }
}
return undefined;
}
diff --git a/src/onebot/api/msg.ts b/src/onebot/api/msg.ts
index c69f1cd1..73519ff1 100644
--- a/src/onebot/api/msg.ts
+++ b/src/onebot/api/msg.ts
@@ -355,6 +355,7 @@ export class OneBotMsgApi {
data: {
file: fileCode,
file_size: element.fileSize,
+ path: element.filePath,
},
};
},
@@ -654,6 +655,19 @@ export class OneBotMsgApi {
[OB11MessageDataType.node]: async () => undefined,
[OB11MessageDataType.forward]: async ({ data }, context) => {
+ // let id = data.id.toString();
+ // let peer: Peer | undefined = context.peer;
+ // if (isNumeric(id)) {
+ // let msgid = '';
+ // if (BigInt(data.id) > 2147483647n) {
+ // peer = MessageUnique.getPeerByMsgId(id)?.Peer;
+ // msgid = id;
+ // } else {
+ // let data = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id));
+ // msgid = data?.MsgId ?? '';
+ // peer = data?.Peer;
+ // }
+ // }
const jsonData = ForwardMsgBuilder.fromResId(data.id);
return this.ob11ToRawConverters.json({
data: { data: JSON.stringify(jsonData) },
@@ -795,6 +809,7 @@ export class OneBotMsgApi {
message_id: msg.id!,
message_seq: msg.id!,
real_id: msg.id!,
+ real_seq: msg.msgSeq,
message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private',
sender: {
user_id: +(msg.senderUin ?? 0),
diff --git a/src/onebot/api/quick-action.ts b/src/onebot/api/quick-action.ts
index aa81a88d..709c1f20 100644
--- a/src/onebot/api/quick-action.ts
+++ b/src/onebot/api/quick-action.ts
@@ -84,17 +84,19 @@ export class OneBotQuickActionApi {
let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag);
if (!notify) {
notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag);
+ return { doubt: true, notify };
}
- return notify;
+ return { doubt: false, notify };
}
async handleGroupRequest(request: OB11GroupRequestEvent, quickAction: QuickActionGroupRequest) {
const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(request.flag);
- const notify = invite_notify ?? await this.findNotify(request.flag);
+ const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(request.flag);
if (!isNull(quickAction.approve) && notify) {
this.core.apis.GroupApi.handleGroupRequest(
+ doubt,
notify,
quickAction.approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
quickAction.reason,
diff --git a/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts b/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts
index b64e5c7f..a5b58c5d 100644
--- a/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts
+++ b/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts
@@ -3,7 +3,7 @@ import { NapCatCore } from '@/core';
export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent {
notice_type = 'group_admin';
- sub_type: 'set' | 'unset';
+ sub_type: 'set' | 'unset';
constructor(core: NapCatCore, group_id: number, user_id: number, sub_type: 'set' | 'unset') {
super(core, group_id, user_id);
diff --git a/src/onebot/helper/data.ts b/src/onebot/helper/data.ts
index 0227661f..8fa7f3cd 100644
--- a/src/onebot/helper/data.ts
+++ b/src/onebot/helper/data.ts
@@ -73,6 +73,7 @@ export class OB11Construct {
static group(group: Group): OB11Group {
return {
+ group_remark: group.remarkName,
group_id: +group.groupCode,
group_name: group.groupName,
member_count: group.memberCount,
diff --git a/src/onebot/types/data.ts b/src/onebot/types/data.ts
index 52b97436..2957e22a 100644
--- a/src/onebot/types/data.ts
+++ b/src/onebot/types/data.ts
@@ -57,6 +57,7 @@ export interface OB11GroupMember {
}
export interface OB11Group {
+ group_remark: string; // 群备注
group_id: number; // 群ID
group_name: string; // 群名称
member_count?: number; // 成员数量
diff --git a/src/onebot/types/message.ts b/src/onebot/types/message.ts
index ff14e8fb..756d5b5f 100644
--- a/src/onebot/types/message.ts
+++ b/src/onebot/types/message.ts
@@ -10,6 +10,7 @@ export enum OB11MessageType {
// 消息接口定义
export interface OB11Message {
+ real_seq?: string;// 自行扩展
temp_source?: number;
message_sent_type?: string;
target_id?: number; // 自己发送消息/私聊消息
diff --git a/src/shell/base.ts b/src/shell/base.ts
index ecd8e0b4..51c730aa 100644
--- a/src/shell/base.ts
+++ b/src/shell/base.ts
@@ -30,6 +30,8 @@ import { InitWebUi } from '@/webui';
import { WebUiDataRuntime } from '@/webui/src/helper/Data';
import { napCatVersion } from '@/common/version';
import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener';
+import { sleep } from '@/common/helper';
+
// NapCat Shell App ES 入口文件
async function handleUncaughtExceptions(logger: LogWrapper) {
process.on('uncaughtException', (err) => {
@@ -113,120 +115,120 @@ async function handleLogin(
quickLoginUin: string | undefined,
historyLoginList: LoginListItem[]
): Promise {
- return new Promise((resolve) => {
- const loginListener = new NodeIKernelLoginListener();
- let isLogined = false;
+ let context = { isLogined: false };
+ let inner_resolve: (value: SelfInfo) => void;
+ let selfInfo: Promise = new Promise((resolve) => {
+ inner_resolve = resolve;
+ });
+ // 连接服务
- loginListener.onUserLoggedIn = (userid: string) => {
- logger.logError(`当前账号(${userid})已登录,无法重复登录`);
- };
-
- loginListener.onQRCodeLoginSucceed = async (loginResult) => {
- isLogined = true;
- resolve({
- uid: loginResult.uid,
- uin: loginResult.uin,
- nick: '',
- online: true,
- });
- };
-
- loginListener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => {
- WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl);
-
- const realBase64 = pngBase64QrcodeData.replace(/^data:image\/\w+;base64,/, '');
- const buffer = Buffer.from(realBase64, 'base64');
- logger.logWarn('请扫描下面的二维码,然后在手Q上授权登录:');
- const qrcodePath = path.join(pathWrapper.cachePath, 'qrcode.png');
- qrcode.generate(qrcodeUrl, { small: true }, (res) => {
- logger.logWarn([
- '\n',
- res,
- '二维码解码URL: ' + qrcodeUrl,
- '如果控制台二维码无法扫码,可以复制解码url到二维码生成网站生成二维码再扫码,也可以打开下方的二维码路径图片进行扫码。',
- ].join('\n'));
- fs.writeFile(qrcodePath, buffer, {}, () => {
- logger.logWarn('二维码已保存到', qrcodePath);
- });
- });
- };
-
- loginListener.onQRCodeSessionFailed = (errType: number, errCode: number) => {
- if (!isLogined) {
- logger.logError('[Core] [Login] Login Error,ErrType: ', errType, ' ErrCode:', errCode);
- if (errType == 1 && errCode == 3) {
- // 二维码过期刷新
- }
- loginService.getQRCodePicture();
- }
- };
-
- loginListener.onLoginFailed = (...args) => {
- logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args));
- };
-
- loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
- const isConnect = loginService.connect();
- if (!isConnect) {
- logger.logError('核心登录服务连接失败!');
- return;
- }
-
- logger.log('核心登录服务连接成功!');
-
- loginService.getLoginList().then((res) => {
- // 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList
- const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin);
- WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString()));
- WebUiDataRuntime.setQQNewLoginList(list);
+ const loginListener = new NodeIKernelLoginListener();
+ loginListener.onUserLoggedIn = (userid: string) => {
+ logger.logError(`当前账号(${userid})已登录,无法重复登录`);
+ };
+ loginListener.onQRCodeLoginSucceed = async (loginResult) => {
+ context.isLogined = true;
+ inner_resolve({
+ uid: loginResult.uid,
+ uin: loginResult.uin,
+ nick: '',
+ online: true,
});
- WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
- return await new Promise((resolve) => {
- if (uin) {
- logger.log('正在快速登录 ', uin);
- loginService.quickLoginWithUin(uin).then(res => {
- if (res.loginErrorInfo.errMsg) {
- resolve({ result: false, message: res.loginErrorInfo.errMsg });
- }
- resolve({ result: true, message: '' });
- }).catch((e) => {
- logger.logError(e);
- resolve({ result: false, message: '快速登录发生错误' });
- });
- } else {
- resolve({ result: false, message: '快速登录失败' });
- }
+ };
+ loginListener.onLoginConnected = () => {
+ waitForNetworkConnection(loginService, logger).then(() => {
+ handleLoginInner(context, logger, loginService, quickLoginUin, historyLoginList).then().catch(e => logger.logError(e));
+ });
+ }
+ loginListener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => {
+ WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl);
+
+ const realBase64 = pngBase64QrcodeData.replace(/^data:image\/\w+;base64,/, '');
+ const buffer = Buffer.from(realBase64, 'base64');
+ logger.logWarn('请扫描下面的二维码,然后在手Q上授权登录:');
+ const qrcodePath = path.join(pathWrapper.cachePath, 'qrcode.png');
+ qrcode.generate(qrcodeUrl, { small: true }, (res) => {
+ logger.logWarn([
+ '\n',
+ res,
+ '二维码解码URL: ' + qrcodeUrl,
+ '如果控制台二维码无法扫码,可以复制解码url到二维码生成网站生成二维码再扫码,也可以打开下方的二维码路径图片进行扫码。',
+ ].join('\n'));
+ fs.writeFile(qrcodePath, buffer, {}, () => {
+ logger.logWarn('二维码已保存到', qrcodePath);
});
});
+ };
- if (quickLoginUin) {
- if (historyLoginList.some(u => u.uin === quickLoginUin)) {
- logger.log('正在快速登录 ', quickLoginUin);
- setTimeout(() => {
- loginService.quickLoginWithUin(quickLoginUin)
- .then(result => {
- if (result.loginErrorInfo.errMsg) {
- logger.logError('快速登录错误:', result.loginErrorInfo.errMsg);
- if (!isLogined) loginService.getQRCodePicture();
- }
- })
- .catch();
- }, 1000);
- } else {
- logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式');
- if (!isLogined) loginService.getQRCodePicture();
- }
- } else {
- logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式');
- if (historyLoginList.length > 0) {
- logger.log(`可用于快速登录的 QQ:\n${historyLoginList
- .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
- .join('\n')
- }`);
+ loginListener.onQRCodeSessionFailed = (errType: number, errCode: number) => {
+ if (!context.isLogined) {
+ logger.logError('[Core] [Login] Login Error,ErrType: ', errType, ' ErrCode:', errCode);
+ if (errType == 1 && errCode == 3) {
+ // 二维码过期刷新
}
loginService.getQRCodePicture();
}
+ };
+
+ loginListener.onLoginFailed = (...args) => {
+ logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args));
+ };
+
+ loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
+ loginService.connect();
+ return await selfInfo;
+}
+async function handleLoginInner(context: { isLogined: boolean }, logger: LogWrapper, loginService: NodeIKernelLoginService, quickLoginUin: string | undefined, historyLoginList: LoginListItem[]) {
+ WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
+ return await new Promise((resolve) => {
+ if (uin) {
+ logger.log('正在快速登录 ', uin);
+ loginService.quickLoginWithUin(uin).then(res => {
+ if (res.loginErrorInfo.errMsg) {
+ resolve({ result: false, message: res.loginErrorInfo.errMsg });
+ }
+ resolve({ result: true, message: '' });
+ }).catch((e) => {
+ logger.logError(e);
+ resolve({ result: false, message: '快速登录发生错误' });
+ });
+ } else {
+ resolve({ result: false, message: '快速登录失败' });
+ }
+ });
+ });
+ if (quickLoginUin) {
+ if (historyLoginList.some(u => u.uin === quickLoginUin)) {
+ logger.log('正在快速登录 ', quickLoginUin);
+ loginService.quickLoginWithUin(quickLoginUin)
+ .then(result => {
+ if (result.loginErrorInfo.errMsg) {
+ logger.logError('快速登录错误:', result.loginErrorInfo.errMsg);
+ if (!context.isLogined) loginService.getQRCodePicture();
+ }
+ })
+ .catch();
+ } else {
+ logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式');
+ if (!context.isLogined) loginService.getQRCodePicture();
+ }
+ } else {
+ logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式');
+ if (historyLoginList.length > 0) {
+ logger.log(`可用于快速登录的 QQ:\n${historyLoginList
+ .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
+ .join('\n')
+ }`);
+ }
+ loginService.getQRCodePicture();
+ }
+
+ loginService.getLoginList().then((res) => {
+ // 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList
+ const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin);
+ WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString()));
+ WebUiDataRuntime.setQQNewLoginList(list);
});
}
@@ -284,6 +286,20 @@ async function handleProxy(session: NodeIQQNTWrapperSession, logger: LogWrapper)
});
}
}
+
+async function waitForNetworkConnection(loginService: NodeIKernelLoginService, logger: LogWrapper) {
+ let network_ok = false;
+ let tryCount = 0;
+ while (!network_ok) {
+ network_ok = loginService.getMsfStatus() !== 3;// win 11 0连接 1未连接
+ logger.log('等待网络连接...');
+ await sleep(500);
+ tryCount++;
+ }
+ logger.log('网络已连接');
+ return network_ok;
+}
+
export async function NCoreInitShell() {
console.log('NapCat Shell App Loading...');
const pathWrapper = new NapCatPathWrapper();
diff --git a/src/webui/index.ts b/src/webui/index.ts
index a4eae968..716e84fc 100644
--- a/src/webui/index.ts
+++ b/src/webui/index.ts
@@ -29,7 +29,7 @@ export let webUiPathWrapper: NapCatPathWrapper;
const MAX_PORT_TRY = 100;
import * as net from 'node:net';
import { WebUiDataRuntime } from './src/helper/Data';
-
+export let webUiRuntimePort = 6099;
export async function InitPort(parsedConfig: WebUiConfigType): Promise<[string, number, string]> {
try {
await tryUseHost(parsedConfig.host);
@@ -45,6 +45,7 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
webUiPathWrapper = pathWrapper;
WebUiConfig = new WebUiConfigWrapper();
const [host, port, token] = await InitPort(await WebUiConfig.GetWebUIConfig());
+ webUiRuntimePort = port;
if (port == 0) {
logger.log('[NapCat] [WebUi] Current WebUi is not run.');
return;
diff --git a/vite.config.ts b/vite.config.ts
index 6b7f0d41..28fe0ffd 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -9,7 +9,6 @@ const external = [
'ws',
'express',
'@ffmpeg.wasm/core-mt',
- 'piscina',
'@napi-rs/canvas'
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();