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 + ![NapCatQQ](https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Fnewlogo.png&name=1&owner=1&pattern=Diagonal+Stripes&stargazers=1&theme=Auto) - + +_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 | [![Github.IO](https://img.shields.io/badge/docs%20on-Github.IO-orange)](https://napneko.github.io/) | [![Cloudflare.Worker](https://img.shields.io/badge/docs%20on-Cloudflare.Worker-black)](https://doc.napneko.icu/) | [![Cloudflare.HKServer](https://img.shields.io/badge/docs%20on-Cloudflare.HKServer-informational)](https://napcat.napneko.icu/) | +|:-:|:-:|:-:|:-:| -[Cloudflare.HKServer](https://napcat.napneko.icu/) +| Docs | [![Cloudflare.Pages](https://img.shields.io/badge/docs%20on-Cloudflare.Pages-blue)](https://napneko.pages.dev/) | [![Server.Other](https://img.shields.io/badge/docs%20on-Server.Other-green)](https://docs.napcat.cyou/) | [![NapCat.Wiki](https://img.shields.io/badge/docs%20on-NapCat.Wiki-red)](https://www.napcat.wiki) | +|:-:|:-:|:-:|:-:| -[Github.IO](https://napneko.github.io/) +| Contact | [![QQ Group#1](https://img.shields.io/badge/QQ%20Group%231-Join-blue)](https://qm.qq.com/q/I6LU87a0Yq) | [![QQ Group#2](https://img.shields.io/badge/QQ%20Group%232-Join-blue)](https://qm.qq.com/q/HaRcfrHpUk) | [![Telegram](https://img.shields.io/badge/Telegram-MelodicMoonlight-blue)](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();