Compare commits

...

81 Commits

Author SHA1 Message Date
手瓜一十雪
264cb6bbd2 release: 2.3.7 2024-09-07 21:29:22 +08:00
手瓜一十雪
b7772e867b Merge pull request #344 from qhy040404/patch-1
feat: 允许保存的token自动填写
2024-09-07 20:05:25 +08:00
Alen
cc0e77abfb Merge pull request #345 from cnxysoft/upmain
fix: 群成员列表获取
2024-09-07 19:55:41 +08:00
Alen
537d1c6f4f fix: 群成员列表获取
修复部分群成员列表返回为空的问题
2024-09-07 19:54:46 +08:00
Seijo Cecilia
80facadd67 chore: fix prefer-const 2024-09-07 19:49:54 +08:00
Seijo Cecilia
ba097dad23 update: recallMsgV2 2024-09-07 19:49:18 +08:00
qhy040404
c13c15d046 feat: 允许保存的token自动填写 2024-09-07 19:37:39 +08:00
手瓜一十雪
4f52128a06 fix 2024-09-07 17:17:07 +08:00
手瓜一十雪
500b2d0e6d release: 2.3.6 2024-09-07 13:31:32 +08:00
手瓜一十雪
e59d094feb feat: 提升使用体验 2024-09-07 10:45:45 +08:00
手瓜一十雪
a8372f14f8 fix: member info 2024-09-07 09:18:29 +08:00
Wesley F. Young
5174ff422d update: optimize message logging to console 2024-09-07 01:29:50 +08:00
Wesley F. Young
5c06751c3b fix: reference error 2024-09-07 00:04:35 +08:00
手瓜一十雪
ac2b0118a6 release: 2.3.5 2024-09-06 12:13:22 +08:00
手瓜一十雪
3eb8fd4abe Merge pull request #343 from drsanwujiang/patch-1
Fix bug: active HTTP adapter
2024-09-06 11:42:05 +08:00
Drsanwujiang
48b389ebe3 Fix bug: active HTTP adapter 2024-09-06 11:02:41 +08:00
手瓜一十雪
065adeb2cd fix 2024-09-06 07:40:33 +08:00
Wesley F. Young
269d0a06fe docs: deprecated features 2024-09-06 01:03:04 +08:00
手瓜一十雪
8eca26b1a5 fix 2024-09-05 18:03:32 +08:00
手瓜一十雪
3019ef7de4 release: 2.3.4 2024-09-05 16:15:21 +08:00
手瓜一十雪
522311b547 release: 2.3.4 2024-09-05 16:00:13 +08:00
手瓜一十雪
21061561ec fix: 字段兼容 2024-09-05 15:06:11 +08:00
手瓜一十雪
b83c41ad56 fix: #339 2024-09-05 14:55:11 +08:00
Version
e80a1cc64a chore:version change 2024-09-05 06:03:22 +00:00
手瓜一十雪
a01e4ca89f release: 2.3.1 2024-09-05 14:03:03 +08:00
手瓜一十雪
c20362e9b6 release: 2.3.0 2024-09-05 14:00:57 +08:00
手瓜一十雪
c90cfb99bd Merge pull request #340 from 123233513/main
%RetString% 增加引号,解决QQ目录包含空格的问题
2024-09-05 13:56:58 +08:00
123233513
7bcea14799 Update launcher.bat
%RetString% 增加引号,解决QQ目录包含空格的问题,比如安装在:C:\Program Files\Tencent\QQNT时,获取不到正确的路径
2024-09-05 10:41:04 +08:00
Wesley F. Young
b415c1a6d1 fix: file encoding 2024-09-04 23:26:55 +08:00
手瓜一十雪
452c72d280 release: 2.2.47 2024-09-04 23:12:38 +08:00
手瓜一十雪
48350be625 fix: 进一步筛选 2024-09-04 18:05:44 +08:00
手瓜一十雪
ab824fb219 Revert "update: normalize log"
This reverts commit d36a28fa81.
2024-09-04 18:05:01 +08:00
手瓜一十雪
043d8a1861 Revert "rollback unlink -> unlinkSync"
This reverts commit 074ac15d0f.
2024-09-04 18:04:58 +08:00
Seijo Cecilia
074ac15d0f rollback unlink -> unlinkSync 2024-09-04 15:50:23 +08:00
Seijo Cecilia
d36a28fa81 update: normalize log 2024-09-04 15:47:14 +08:00
手瓜一十雪
ba12bc6c91 docs: fix 2024-09-04 14:03:55 +08:00
手瓜一十雪
87332778e5 docs: fix 2024-09-04 13:26:38 +08:00
手瓜一十雪
453feb8473 release: 2.2.46 2024-09-04 13:21:40 +08:00
手瓜一十雪
8ff469974c build: test2 2024-09-04 13:19:02 +08:00
手瓜一十雪
994ec5ac0f fix: 极端情况下uin暴毙的情况 2024-09-04 12:40:59 +08:00
手瓜一十雪
43f7f9a363 build: test 2024-09-04 12:37:23 +08:00
Wesley F. Young
4a11ebc9b9 Revert "chore: optimize vite.config.ts"
This reverts commit 6a0d592491.
2024-09-04 10:38:10 +08:00
Wesley F. Young
d76a1305e7 chore: optimize import 2024-09-04 10:06:09 +08:00
Wesley F. Young
6a0d592491 chore: optimize vite.config.ts 2024-09-04 10:05:24 +08:00
Wesley F. Young
9898c2196d chore: optimize tsconfig.json 2024-09-04 00:29:55 +08:00
Wesley F. Young
41a8dc840f chore: completely comment onRecvSysMsg 2024-09-03 23:54:46 +08:00
Wesley F. Young
c3eaae9d88 chore: optimize imports 2024-09-03 23:46:10 +08:00
手瓜一十雪
3ca959b7a6 docs: update 2024-09-03 21:51:16 +08:00
手瓜一十雪
1d2e2b6e5c release: 2.2.44 2024-09-03 21:33:48 +08:00
手瓜一十雪
31d963c4d1 Merge pull request #336 from hguandl/feat/macos
feat: 支持 macOS
2024-09-03 21:16:03 +08:00
Hao Guan
7e96118cdc feat: support macOS 2024-09-03 20:17:10 +08:00
Hao Guan
709a0744bd chore: refactor path config 2024-09-03 20:14:35 +08:00
手瓜一十雪
f59248cc5a release: 2.2.43 2024-09-03 18:49:45 +08:00
手瓜一十雪
8647c5c607 fix: echo丢失问题 2024-09-03 18:37:28 +08:00
手瓜一十雪
6699ff38a1 Revert "fix: Error Handle"
This reverts commit d79b98bd55.
2024-09-03 18:36:20 +08:00
手瓜一十雪
d79b98bd55 fix: Error Handle 2024-09-03 18:34:33 +08:00
手瓜一十雪
5065a052fb release: 2.2.42 2024-09-03 18:12:59 +08:00
Wesley F. Young
45603bb78c fix(docs): unexpected spaces 2024-09-03 15:48:08 +08:00
手瓜一十雪
40948995b4 build: test 2024-09-03 14:09:17 +08:00
手瓜一十雪
4ccdd8d1d3 docs: update 2024-09-03 13:37:19 +08:00
手瓜一十雪
30d0174f47 fix: #334 2024-09-03 13:20:10 +08:00
手瓜一十雪
5a986ba25c release: 2.2.40 2024-09-03 13:01:37 +08:00
手瓜一十雪
fe63c24ac3 release: 2.2.39 2024-09-03 12:40:13 +08:00
手瓜一十雪
c384bd6875 release: 2.2.38 2024-09-02 17:31:27 +08:00
手瓜一十雪
dcbff3f569 release: 2.2.37 2024-09-02 16:31:42 +08:00
手瓜一十雪
7d91e05a69 fix: #332 2024-09-02 16:31:18 +08:00
手瓜一十雪
a5ce424a40 release: 2.2.36 2024-09-01 18:44:23 +08:00
手瓜一十雪
47c36ca062 release: 2.2.35 2024-09-01 18:24:49 +08:00
手瓜一十雪
c4c5b3bf8b fix: remark 2024-09-01 18:24:29 +08:00
手瓜一十雪
b1a81b0d12 release: 2.2.32 2024-09-01 16:32:13 +08:00
手瓜一十雪
ad9fe64850 release: 2.2.32 2024-09-01 16:13:41 +08:00
手瓜一十雪
f236349dc6 Revert "release:2.2.31"
This reverts commit 309d8a9f18.
2024-09-01 16:13:14 +08:00
手瓜一十雪
5f56c8a7d4 fix 2024-09-01 16:10:16 +08:00
手瓜一十雪
309d8a9f18 release:2.2.31 2024-09-01 15:59:02 +08:00
手瓜一十雪
2981799803 fix: file api 2024-09-01 14:11:28 +08:00
手瓜一十雪
00f8e1c0da Revert "fix: fileId"
This reverts commit ae009f98c1.
2024-09-01 13:41:19 +08:00
手瓜一十雪
e9482e2ec4 Revert "fix: encode fileId"
This reverts commit 9bff327377.
2024-09-01 13:41:14 +08:00
手瓜一十雪
9bff327377 fix: encode fileId 2024-09-01 12:17:42 +08:00
手瓜一十雪
ae009f98c1 fix: fileId 2024-09-01 12:17:17 +08:00
手瓜一十雪
77505a6f5b release: 2.2.31 2024-09-01 09:31:59 +08:00
手瓜一十雪
19c729aa23 chore: appid 2024-09-01 09:30:38 +08:00
60 changed files with 516 additions and 653 deletions

View File

@@ -17,5 +17,8 @@ charset = utf-8
indent_style = space
indent_size = 4
[*.bat]
charset = latin1
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.

View File

@@ -10,6 +10,7 @@ body:
在提交新的 Bug 反馈前,请确保您:
* 已经搜索了现有的 issues并且没有找到可以解决您问题的方法
* 不与现有的某一 issue 重复
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
- type: input
id: system-version
attributes:
@@ -78,4 +79,4 @@ body:
attributes:
label: OneBot 客户端运行日志
description: 粘贴 OneBot 客户端的相关日志内容到此处
render: shell
render: shell

View File

@@ -7,10 +7,14 @@
NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现。
## 猫猫技能
- [x] **高性能**1K+ 群聊数目、20 线程并行发送消息毫无压力
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**实现大部分Onebot接口上扩展了一套私有API
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
- [x] **低故障率**:快速适配最新版本,日常保证 0 Issue
## 使用猫猫
@@ -24,14 +28,16 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 猫猫朋友
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供部分参考
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
不过最最重要的 还是需要感谢屏幕前的你哦~
---
## 约法三章
> [!CAUTION]\
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,40 @@
@echo off
chcp 65001
net session >nul 2>&1
if %errorLevel% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
exit
)
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456

41
launcher/launcher.bat Normal file
View File

@@ -0,0 +1,41 @@
@echo off
chcp 65001
net session >nul 2>&1
if %errorLevel% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
exit
)
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set RetString=%%b
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456

5
launcher/loadNapCat.js Normal file
View File

@@ -0,0 +1,5 @@
const path = require('path');
const CurrentPath = path.dirname(__filename);
(async () => {
await import("file://" + path.join(CurrentPath, './napcat/napcat.mjs'));
})();

1
launcher/patchNapCat.js Normal file
View File

@@ -0,0 +1 @@
require('./launcher.node').load('external_index', module);

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.2.30",
"version": "2.3.7",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",

Binary file not shown.

View File

@@ -22,9 +22,14 @@ export abstract class ConfigBase<T> {
}
getConfigPath(pathName: string | undefined): string {
const suffix = pathName ? `_${pathName}` : '';
const filename = `${this.name}${suffix}.json`;
return path.join(this.configPath, filename);
if (!pathName) {
const filename = `${this.name}.json`;
const mainPath = this.core.context.pathWrapper.binaryPath;
return path.join(mainPath, 'config', filename);
} else {
const filename = `${this.name}_${pathName}.json`;
return path.join(this.configPath, filename);
}
}
read(): T {

View File

@@ -25,29 +25,31 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
}
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string): string {
return `NapCatOneBot-ModelIdFile-${peer.chatType}-${peer.peerUid}-${modelId}`;
static encodeModelId(peer: Peer, modelId: string, fileId: string): string {
return `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
}
static decodeModelId(uuid: string): undefined | {
peer: Peer,
modelId: string
modelId: string,
fileId: string
} {
if (!uuid.startsWith('NapCatOneBot-ModelIdFile-')) return undefined;
const data = uuid.split('-');
if (data.length !== 5) return undefined;
const [, , chatType, peerUid, modelId] = data;
if (!uuid.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = uuid.split('|');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, modelId, fileId] = data;
return {
peer: {
chatType: chatType as any,
peerUid: peerUid,
},
modelId,
fileId
};
}
static encode(peer: Peer, msgId: string, elementId: string): string {
return `NapCatOneBot-MsgFile-${peer.chatType}-${peer.peerUid}-${msgId}-${elementId}`;
return `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
}
static decode(uuid: string): undefined | {
@@ -55,8 +57,8 @@ export class FileNapCatOneBotUUID {
msgId: string,
elementId: string
} {
if (!uuid.startsWith('NapCatOneBot-MsgFile-')) return undefined;
const data = uuid.split('-');
if (!uuid.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = uuid.split('|');
if (data.length !== 6) return undefined;
const [, , chatType, peerUid, msgId, elementId] = data;
return {
@@ -138,26 +140,38 @@ export function isEqual(obj1: any, obj2: any) {
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-27254',
curVersion: '3.2.12-27254',
baseVersion: '3.2.12-27597',
curVersion: '3.2.12-27597',
prevVersion: '',
onErrorVersions: [],
buildId: '27254',
buildId: '27597',
};
}
return {
baseVersion: '9.9.15-27391',
curVersion: '9.9.15-27391',
baseVersion: '9.9.15-27597',
curVersion: '9.9.15-27597',
prevVersion: '',
onErrorVersions: [],
buildId: '27391',
buildId: '27597',
};
}
export function getQQPackageInfoPath(exePath: string = ''): string {
if (os.platform() === 'darwin') {
return path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
} else {
return path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
}
}
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath;
if (os.platform() !== 'linux') {
if (os.platform() === 'win32') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
} else if (os.platform() === 'darwin') {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
} else {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './.config/QQ');

View File

@@ -167,7 +167,8 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
function msgElementToText(element: MessageElement) {
if (element.textElement) {
if (element.textElement.atType === AtType.notAt) {
return element.textElement.content;
const originalContentLines = element.textElement.content.split('\n');
return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`;
} else if (element.textElement.atType === AtType.atAll) {
return `@全体成员`;
} else if (element.textElement.atType === AtType.atUser) {
@@ -189,7 +190,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.picElement) {
return `[图片 ${element.picElement.fileName}]`;
return '[图片]';
}
if (element.fileElement) {
@@ -197,7 +198,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.videoElement) {
return `[视频 ${element.videoElement.fileName}]`;
return '[视频]';
}
if (element.pttElement) {
@@ -205,7 +206,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.arkElement) {
return `[卡片消息 ${element.arkElement.bytesData}]`;
return '[卡片消息]';
}
if (element.faceElement) {
@@ -213,19 +214,19 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string {
}
if (element.marketFaceElement) {
return `[商城表情 ${element.marketFaceElement.faceName}]`;
return element.marketFaceElement.faceName;
}
if (element.markdownElement) {
return `[Markdown ${element.markdownElement.content}]`;
return '[Markdown 消息]';
}
if (element.multiForwardMsgElement) {
return `[转发消息]`;
return '[转发消息]';
}
if (element.elementType === ElementType.GreyTip) {
return `[灰条消息]`; // TODO: resolve the text
return '[灰条消息]';
}
return `[未实现 (ElementType = ${element.elementType})]`;

View File

@@ -1,6 +1,7 @@
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import os from 'os';
export class NapCatPathWrapper {
binaryPath: string;
@@ -11,17 +12,23 @@ export class NapCatPathWrapper {
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
this.binaryPath = mainPath;
this.logsPath = path.join(this.binaryPath, 'logs');
this.configPath = path.join(this.binaryPath, 'config');
this.cachePath = path.join(this.binaryPath, 'cache');
let writePath: string;
if (os.platform() === 'darwin') {
writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat');
} else {
writePath = this.binaryPath;
}
this.logsPath = path.join(writePath, 'logs');
this.configPath = path.join(writePath, 'config');
this.cachePath = path.join(writePath, 'cache');
this.staticPath = path.join(this.binaryPath, 'static');
if (fs.existsSync(this.logsPath)) {
if (!fs.existsSync(this.logsPath)) {
fs.mkdirSync(this.logsPath, { recursive: true });
}
if (fs.existsSync(this.configPath)) {
if (!fs.existsSync(this.configPath)) {
fs.mkdirSync(this.configPath, { recursive: true });
}
if (fs.existsSync(this.cachePath)) {
if (!fs.existsSync(this.cachePath)) {
fs.mkdirSync(this.cachePath, { recursive: true });
}
}

View File

@@ -1,7 +1,6 @@
import path from 'node:path';
import fs from 'node:fs';
import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper';
import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log';
@@ -20,7 +19,7 @@ export class QQBasicInfoWrapper {
//基础目录获取
this.context = context;
this.QQMainPath = process.execPath;
this.QQPackageInfoPath = path.join(path.dirname(this.QQMainPath), 'resources', 'app', 'package.json');
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath);
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
//基础信息获取 无快更则启用默认模板填充
@@ -53,9 +52,25 @@ export class QQBasicInfoWrapper {
//此方法不要直接使用
getQUAInternal() {
return systemPlatform === 'linux'
? `V1_LNX_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`
: `V1_WIN_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
switch (systemPlatform) {
case 'linux':
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
case 'darwin':
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
default:
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
}
}
getAppidInternal() {
switch (systemPlatform) {
case 'linux':
return '537243600';
case 'darwin':
return '537243441';
default:
return '537243538';
}
}
getAppidV2(): { appid: string; qua: string } {
@@ -71,6 +86,6 @@ export class QQBasicInfoWrapper {
// else
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
return { appid: systemPlatform === 'linux' ? '537240795' : '537240709', qua: this.getQUAInternal() };
return { appid: this.getAppidInternal(), qua: this.getQUAInternal() };
}
}

View File

@@ -1 +1 @@
export const napCatVersion = '2.2.30';
export const napCatVersion = '2.3.7';

View File

@@ -298,65 +298,6 @@ export class NTQQFileApi {
});
}
async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
let GroupData;
let BuddyData;
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
GroupData =
[{
groupCode: peer.peerUid,
isConf: false,
hasModifyConfGroupFace: true,
hasModifyConfGroupName: true,
groupName: 'NapCat.Cached',
remark: 'NapCat.Cached',
}];
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
BuddyData = [{
category_name: 'NapCat.Cached',
peerUid: peer.peerUid,
peerUin: peer.peerUid,
remark: 'NapCat.Cached',
}];
} else {
return undefined;
}
return this.context.session.getSearchService().addSearchHistory({
type: 4,
contactList: [],
id: -1,
groupInfos: [],
msgs: [],
fileInfos: [
{
chatType: peer.chatType,
buddyChatInfo: BuddyData || [],
discussChatInfo: [],
groupChatInfo: GroupData || [],
dataLineChatInfo: [],
tmpChatInfo: [],
msgId: msgId,
msgSeq: msgSeq,
msgTime: Math.floor(Date.now() / 1000).toString(),
senderUid: senderUid,
senderNick: 'NapCat.Cached',
senderRemark: 'NapCat.Cached',
senderCard: 'NapCat.Cached',
elemId: elemId,
elemType: elemType,
fileSize: fileSize,
filePath: '',
fileName: fileName,
hits: [{
start: 12,
end: 14,
}],
},
],
});
}
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
let searchId = 0;

View File

@@ -1,5 +1,4 @@
import {
ChatType,
GeneralCallResult,
Group,
GroupMember,
@@ -10,8 +9,9 @@ import {
MemberExtSourceType,
NapCatCore,
} from '@/core';
import { isNumeric, runAllWithTimeout } from '@/common/helper';
import { isNumeric, solveAsyncProblem } from '@/common/helper';
import { LimitedHashTable } from '@/common/message-unique';
import { NTEventWrapper } from '@/common/event';
export class NTQQGroupApi {
context: InstanceContext;
@@ -121,51 +121,6 @@ export class NTQQGroupApi {
return group;
}
async getGroupMemberLatestSendTimeCache(GroupCode: string, uids: string[]) {
return this.getGroupMemberLatestSendTime(GroupCode, uids);
}
async getGroupMemberLatestSendTime(GroupCode: string, uids: string[]) {
const getData = async (uid: string) => {
const msgListWrapper = await this.getLatestMsgByUids(GroupCode, [uid]);
if (msgListWrapper.result !== 0 && msgListWrapper.msgList.length < 1) {
return undefined;
}
return { sendUin: msgListWrapper.msgList[0].senderUin, sendTime: msgListWrapper.msgList[0].msgTime };
};
const PromiseData: Promise<({
sendUin: string;
sendTime: string;
} | undefined)>[] = [];
const ret: Map<string, string> = new Map();
for (const uid of uids) {
PromiseData.push(getData(uid).catch(() => undefined));
}
const allRet = await runAllWithTimeout(PromiseData, 2500);
for (const PromiseDo of allRet) {
if (PromiseDo) {
ret.set(PromiseDo.sendUin, PromiseDo.sendTime);
}
}
return ret;
}
async getLatestMsgByUids(GroupCode: string, uids: string[]) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.KCHATTYPEGROUP,
},
filterMsgType: [],
filterSendersUid: uids,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async getGroupMemberAll(GroupCode: string, forced = false) {
return this.context.session.getGroupService().getAllMemberList(GroupCode, forced);
}
@@ -202,30 +157,6 @@ export class NTQQGroupApi {
}
return member;
}
async getLatestMsg(GroupCode: string, uins: string[]) {
const uids: Array<string> = [];
for (const uin of uins) {
const uid = await this.core.apis.UserApi.getUidByUinV2(uin);
if (uid) {
uids.push(uid);
}
}
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: {
peerUid: GroupCode,
chatType: ChatType.KCHATTYPEGROUP,
},
filterMsgType: [],
filterSendersUid: uids,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async getGroupRecommendContactArkJson(GroupCode: string) {
return this.context.session.getGroupService().getGroupRecommendContactArkJson(GroupCode);
}
@@ -317,7 +248,7 @@ export class NTQQGroupApi {
'NodeIKernelGroupListener/onMemberInfoChange',
1,
forced ? 5000 : 250,
(params) => params === GroupCode,
(params, _, members) => params === GroupCode && members.size > 0,
);
const retData = await (
this.core.eventWrapper
@@ -334,14 +265,34 @@ export class NTQQGroupApi {
}
return member;
}
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
let data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
return eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
[GroupCode, [uid], forced],
(ret) => ret.result === 0,
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
1,
forced ? 2500 : 250
);
}, this.core.eventWrapper, GroupCode, uid, forced);
if (data && data[3] instanceof Map && data[3].has(uid)) {
return data[3].get(uid);
}
if (retry > 0) {
let trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
if (trydata) return trydata;
}
return undefined;
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const groupService = this.context.session.getGroupService();
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
const listener = this.core.eventWrapper.registerListen(
'NodeIKernelGroupListener/onMemberListChange',
1,
500,
5000,
(params) => params.sceneId === sceneId,
);
try {
@@ -383,9 +334,6 @@ export class NTQQGroupApi {
return this.context.session.getRichMediaService().batchGetGroupFileCount(Gids);
}
async getGroupIgnoreNotifies() {
}
async getArkJsonGroupShare(GroupCode: string) {
const ret = await this.core.eventWrapper.callNoListenerEvent(
'NodeIKernelGroupService/getGroupRecommendContactArkJson',

View File

@@ -54,19 +54,6 @@ export class NTQQMsgApi {
return this.context.session.getMsgService().forwardMsg(msgIds, peer, [peer], new Map());
}
async getLatestMsgByUids(peer: Peer, count: number = 20, isReverseOrder: boolean = false) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: isReverseOrder,//此参数有点离谱 注意不是本次查询的排序 而是全部消历史信息的排序 默认false 从新消息拉取到旧消息
isIncludeCurrent: true,
pageLimit: count,
});
}
async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
if (!peer) throw new Error('peer is not allowed');
if (!msgIds) throw new Error('msgIds is not allowed');
@@ -94,11 +81,41 @@ export class NTQQMsgApi {
pageLimit: 1,
});
}
async queryMsgsWithFilterExWithSeqV2(peer: Peer, msgSeq: string, MsgTime: string, SendersUid: string[]) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: SendersUid,
filterMsgToTime: MsgTime,
filterMsgFromTime: MsgTime,
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: true,
isIncludeCurrent: true,
pageLimit: 1,
});
}
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
}
async getMsgBySeqList(peer: Peer, msgSeqList: string[]) {
//坏的
return await this.context.session.getMsgService().getMsgsBySeqList(peer, msgSeqList);
}
async getMsgBySeqExFirstMsg(peer: Peer, rootMsgId: string, replyMsgId: string) {
const reply = await this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, rootMsgId, replyMsgId);
console.log(reply);
}
async getMsgExBySeq(peer: Peer, msgSeq: string) {
const DateNow = Math.floor(Date.now() / 1000);
const filterMsgFromTime = (DateNow - 300).toString();
@@ -147,6 +164,18 @@ export class NTQQMsgApi {
}, msgIds);
}
async recallMsgV2(peer: Peer, msgId: string) {
await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelMsgService/recallMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
[peer, [msgId]],
() => true,
(updatedList) => updatedList.find(m => m.msgId === msgId && m.recallTime !== '0') !== undefined,
1,
1000,
);
}
async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
return this.context.session.getMsgService().prepareTempChat({
chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP,
@@ -233,7 +262,7 @@ export class NTQQMsgApi {
() => true,
(msgRecords) => msgRecords.some(
msgRecord => msgRecord.peerUid === destPeer.peerUid
&& msgRecord.senderUid === this.core.selfInfo.uid
&& msgRecord.senderUid === this.core.selfInfo.uid
),
);
for (const msg of msgList) {

View File

@@ -75,7 +75,6 @@ export class NTQQUserApi {
(profile) => profile.uid === uid,
);
const RetUser: User = {
...profile.simpleInfo.coreInfo,
...profile.simpleInfo.status,
...profile.simpleInfo.vasInfo,
...profile.commonExt,
@@ -83,17 +82,22 @@ export class NTQQUserApi {
qqLevel: profile.commonExt?.qqLevel,
age: profile.simpleInfo.baseInfo.age,
pendantId: '',
...profile.simpleInfo.coreInfo
};
return RetUser;
}
async getUserDetailInfo(uid: string): Promise<User> {
const retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
let retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
if (retUser && retUser.uin !== '0') {
return retUser;
}
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
return this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
retUser = await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
if (retUser && retUser.uin === '0') {
retUser.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0';
}
return retUser;
}
async modifySelfProfile(param: ModifyProfileParams) {

View File

@@ -131,20 +131,53 @@ export class NTQQWebApi {
// return await res.json();
// }
async setGroupNotice(GroupCode: string, Content: string) {
async setGroupNotice(
GroupCode: string,
Content: string,
pinned: number = 0,
type: number = 1,
is_show_edit_card: number = 1,
tip_window_type: number = 1,
confirm_required: number = 1,
picId: string = '',
imgWidth: number = 540,
imgHeight: number = 300,
) {
interface SetNoticeRetSuccess {
ec: number;
em: string;
id: number;
ltsm: number;
new_fid: string;
read_only: number;
role: number;
srv_code: number;
}
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
let ret: any = undefined;
try {
ret = await RequestUtil.HttpGetJson<any>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${new URLSearchParams({
let settings = JSON.stringify({
is_show_edit_card: is_show_edit_card,
tip_window_type: tip_window_type,
confirm_required: confirm_required
});
const externalParam = {
pic: picId,
imgWidth: imgWidth.toString(),
imgHeight: imgHeight.toString(),
};
let ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
bkn: this.getBknFromCookie(cookieObject),
qid: GroupCode,
text: Content,
pinned: '0',
type: '1',
settings: '{"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}',
pinned: pinned.toString(),
type: type.toString(),
settings: settings,
...(picId === '' ? {} : externalParam)
}).toString()}`,
'GET',
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
);

View File

@@ -370,7 +370,8 @@ export interface ReplyElement {
replayMsgSeq: string;
replayMsgId: string;
senderUin: string;
senderUinStr: string;
senderUidStr?: string;
replyMsgTime?: string;
}
export interface SendReplyElement {

View File

@@ -1,42 +1,14 @@
{
"3.2.12-27187": {
"appid": 537240645,
"qua": "V1_LNX_NQ_3.2.12_27187_GW_B"
},
"3.2.12-27206": {
"appid": 537240645,
"qua": "V1_LNX_NQ_3.2.12_27206_GW_B"
},
"3.2.12-27254": {
"appid": 537240795,
"qua": "V1_LNX_NQ_3.2.12_27254_GW_B"
},
"3.2.12-27556": {
"3.2.12-27597": {
"appid": 537243600,
"qua": "V1_LNX_NQ_3.2.12_27556_GW_B"
},
"9.9.15-27187": {
"appid": 537240610,
"qua": "V1_WIN_NQ_9.9.15_27187_GW_B"
},
"9.9.15-27206": {
"appid": 537240610,
"qua": "V1_WIN_NQ_9.9.15_27206_GW_B"
},
"9.9.15-27254": {
"appid": 537240709,
"qua": "V1_WIN_NQ_9.9.15_27254_GW_B"
},
"9.9.15-27333": {
"appid": 537240709,
"qua": "V1_WIN_NQ_9.9.15_27333_GW_B"
},
"9.9.15-27391": {
"appid": 537240709,
"qua": "V1_WIN_NQ_9.9.15_27333_GW_B"
"qua": "V1_LNX_NQ_3.2.12_27597_GW_B"
},
"9.9.15-27597": {
"appid": 537243441,
"qua": "V1_WIN_NQ_9.9.15_27597_GW_B"
},
"6.9.53-27597": {
"appid": 537243538,
"qua": "V1_MAC_NQ_6.9.53_27597_GW_B"
}
}

View File

@@ -24,7 +24,7 @@ import path from 'node:path';
import fs from 'node:fs';
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
import { NTEventWrapper } from '@/common/event';
import { DataSource, GroupMember, SelfInfo } from '@/core/entities';
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
import { NapCatConfigLoader } from '@/core/helper/config';
import os from 'node:os';
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
@@ -42,9 +42,15 @@ export enum NapCatCoreWorkingEnv {
}
export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node');
let appPath;
if (os.platform() === 'darwin') {
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
} else {
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
}
let wrapperNodePath = path.resolve(appPath, 'wrapper.node');
if (!fs.existsSync(wrapperNodePath)) {
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${QQVersion}/wrapper.node`);
wrapperNodePath = path.join(appPath, `versions/${QQVersion}/wrapper.node`);
}
const nativemodule: any = { exports: {} };
process.dlopen(nativemodule, wrapperNodePath);
@@ -113,6 +119,11 @@ export class NapCatCore {
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
// 下线通知
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
this.selfInfo.online = false;
};
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
@@ -130,10 +141,12 @@ export class NapCatCore {
Object.assign(this.selfInfo, profile);
}
};
profileListener.onSelfStatusChanged = (/* Info: SelfStatusInfo */) => {
// if (Info.status == 20) {
// log("账号状态变更为离线")
// }
profileListener.onSelfStatusChanged = (Info: SelfStatusInfo) => {
if (Info.status == 20) {
this.selfInfo.online = false;
this.context.logger.log("账号状态变更为离线");
}
this.selfInfo.online = true;
};
this.context.session.getProfileService().addKernelProfileListener(
proxiedListenerOf(profileListener, this.context.logger),

View File

@@ -54,7 +54,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
public async websocketHandle(payload: PayloadType, echo: any): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload);
if (!result.valid) {
return OB11Response.error(result.message, 1400);
return OB11Response.error(result.message, 1400, echo);
}
try {
const resData = await this._handle(payload);

View File

@@ -1,6 +1,6 @@
import { OB11Return } from '../types';
import { isNull } from '../../common/helper';
import { isNull } from '@/common/helper';
export class OB11Response {
static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {

View File

@@ -11,7 +11,7 @@ export default class GetGroupAddRequest extends BaseAction<null, OB11GroupReques
actionName = ActionName.GetGroupIgnoreAddRequest;
async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> {
const data = await this.core.apis.GroupApi.getGroupIgnoreNotifies();
// const data = await this.core.apis.GroupApi.getGroupIgnoreNotifies();
// log(data);
// const notifies: GroupNotify[] = data.notifies.filter(notify => notify.status === GroupNotifyStatus.WAIT_HANDLE);
// const returnData: OB11GroupRequestNotify[] = [];

View File

@@ -1,23 +0,0 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
file_id: { type: 'string' },
},
required: ['group_id', 'file_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class DelGroupFile extends BaseAction<Payload, any> {
actionName = ActionName.DelGroupFile;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.GroupApi.DelGroupFile(payload.group_id.toString(), [payload.file_id]);
}
}

View File

@@ -1,24 +0,0 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
folder_id: { type: 'string' },
},
required: ['group_id', 'folder_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class DelGroupFileFolder extends BaseAction<Payload, any> {
actionName = ActionName.DelGroupFileFolder;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return (await this.core.apis.GroupApi.DelGroupFileFolder(
payload.group_id.toString(), payload.folder_id)).groupFileCommonResult;
}
}

View File

@@ -3,6 +3,7 @@ import fs from 'fs/promises';
import { FileNapCatOneBotUUID } from '@/common/helper';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
export interface GetFilePayload {
file: string; // 文件名或者fileUuid
@@ -33,16 +34,27 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
if (contextMsgFile) {
const { peer, msgId, elementId } = contextMsgFile;
const downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
const mixElement = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList
.find(msg => msg.msgId === msgId)?.elements.find(e => e.elementId === elementId);
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList
.find(msg => msg.msgId === msgId);
const mixElement = rawMessage?.elements.find(e => e.elementId === elementId);
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
if (!mixElementInner) throw new Error('element not found');
const fileSize = mixElementInner.fileSize?.toString() ?? '';
const fileName = mixElementInner.fileName ?? '';
let url = '';
if (mixElement?.picElement && rawMessage) {
let tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement) as OB11MessageImage | undefined;
url = tempData?.data.url ?? '';
}
if (mixElement?.videoElement && rawMessage) {
let tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement) as OB11MessageVideo | undefined;
url = tempData?.data.url ?? '';
}
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
url: url !== '' ? url : downloadPath,
file_size: fileSize,
file_name: fileName,
};

View File

@@ -1,23 +0,0 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupFileCount extends BaseAction<Payload, { count: number }> {
actionName = ActionName.GetGroupFileCount;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const ret = await this.core.apis.GroupApi.getGroupFileCount([payload.group_id?.toString()]);
return { count: ret.groupFileCounts[0] };
}
}

View File

@@ -1,50 +0,0 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FileNapCatOneBotUUID } from '@/common/helper';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
start_index: { type: ['string', 'number'] },
file_count: { type: ['string', 'number'] },
folder_id: { type: ['string', 'number'] },
},
required: ['group_id', 'start_index', 'file_count'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupFileList extends BaseAction<Payload, { FileList: Array<any> }> {
actionName = ActionName.GetGroupFileList;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
let param = {};
if (payload.folder_id) {
param = {
folderId: payload.folder_id.toString(),
};
}
const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: +payload.file_count,
startIndex: +payload.start_index,
sortOrder: 2,
showOnlinedocFolder: 0,
...param
}).catch(() => {
return [];
});
ret.forEach((e) => {
const fileModelId = e?.fileInfo?.fileModelId;
if (fileModelId)
e.fileInfo!.fileId = FileNapCatOneBotUUID.encodeModelId({
chatType: 2,
peerUid: payload.group_id.toString()
}, fileModelId);
});
return { FileList: ret };
}
}

View File

@@ -1,23 +0,0 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
folder_name: { type: 'string' },
},
required: ['group_id', 'folder_name'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetGroupFileFolder extends BaseAction<Payload, any> {
actionName = ActionName.SetGroupFileFolder;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
return (await this.core.apis.GroupApi.CreatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
}
}

View File

@@ -1,9 +1,6 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NapCatOneBot11Adapter } from '@/onebot';
import { NapCatCore } from '@/core';
import { SetGroupFileFolder } from '@/onebot/action/file/SetGroupFileFolder';
const SchemaData = {
type: 'object',
@@ -16,17 +13,10 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>;
export class CreateGroupFileFolder extends BaseAction<Payload, null> {
export class CreateGroupFileFolder extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
payloadSchema = SchemaData;
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore,
private ncSetGroupFileFolderImpl: SetGroupFileFolder) {
super(obContext, core);
}
async _handle(payload: Payload) {
await this.ncSetGroupFileFolderImpl._handle(payload);
return null;
return (await this.core.apis.GroupApi.CreatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
}
}

View File

@@ -1,9 +1,7 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { DelGroupFile } from '@/onebot/action/file/DelGroupFile';
import { FileNapCatOneBotUUID } from '@/common/helper';
const SchemaData = {
type: 'object',
@@ -16,17 +14,12 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>;
export class DeleteGroupFile extends BaseAction<Payload, null> {
export class DeleteGroupFile extends BaseAction<Payload, any> {
actionName = ActionName.GOCQHTTP_DeleteGroupFile;
payloadSchema = SchemaData;
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore,
private ncDelGroupFileImpl: DelGroupFile) {
super(obContext, core);
}
async _handle(payload: Payload) {
await this.ncDelGroupFileImpl._handle(payload);
return null;
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
if (!data) throw new Error('Invalid file_id');
return await this.core.apis.GroupApi.DelGroupFile(payload.group_id.toString(), [data.fileId]);
}
}

View File

@@ -1,9 +1,6 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { DelGroupFileFolder } from '@/onebot/action/file/DelGroupFileFolder';
import BaseAction from '../BaseAction';
const SchemaData = {
type: 'object',
@@ -16,17 +13,11 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>;
export class DeleteGroupFileFolder extends BaseAction<Payload, null> {
export class DeleteGroupFileFolder extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder;
payloadSchema = SchemaData;
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore,
private ncDelGroupFileFolderImpl: DelGroupFileFolder) {
super(obContext, core);
}
async _handle(payload: Payload) {
await this.ncDelGroupFileFolderImpl._handle(payload);
return null;
return (await this.core.apis.GroupApi.DelGroupFileFolder(
payload.group_id.toString(), payload.folder_id)).groupFileCommonResult;
}
}

View File

@@ -1,9 +1,6 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NapCatOneBot11Adapter, OB11GroupFile } from '@/onebot';
import { NapCatCore } from '@/core';
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
import { OB11Entities } from '@/onebot/entities';
const SchemaData = {
@@ -11,33 +8,21 @@ const SchemaData = {
properties: {
group_id: { type: ['string', 'number'] },
folder_id: { type: 'string' },
file_count: { type: ['string', 'number'] },
},
required: ['group_id', 'folder_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetGroupFilesByFolder extends BaseAction<Payload, {
files: OB11GroupFile[],
folders: [] // QQ does not allow nested folders
}> {
export class GetGroupFilesByFolder extends BaseAction<any, any> {
actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder;
payloadSchema = SchemaData;
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore,
private getGroupRootFilesImpl: GetGroupRootFiles) {
super(obContext, core);
}
async _handle(payload: Payload) {
const folder = (await this.getGroupRootFilesImpl._handle({ group_id: payload.group_id }))
.folders.find(folder => folder.folder_id === payload.folder_id);
if (!folder) {
throw new Error('Folder not found');
}
const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: folder.total_file_count,
fileCount: +(payload.file_count ?? 50),
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,

View File

@@ -1,15 +1,14 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NapCatOneBot11Adapter, OB11GroupFile, OB11GroupFileFolder } from '@/onebot';
import { NapCatCore } from '@/core';
import { GetGroupFileCount } from '@/onebot/action/file/GetGroupFileCount';
import { OB11GroupFile, OB11GroupFileFolder } from '@/onebot';
import { OB11Entities } from '@/onebot/entities';
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['string', 'number'] },
file_count: { type: ['string', 'number'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
@@ -22,16 +21,10 @@ export class GetGroupRootFiles extends BaseAction<Payload, {
}> {
actionName = ActionName.GoCQHTTP_GetGroupRootFiles;
payloadSchema = SchemaData;
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore,
private ncGetGroupFileCountImpl: GetGroupFileCount) {
super(obContext, core);
}
async _handle(payload: Payload) {
const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: (await this.ncGetGroupFileCountImpl._handle({ group_id: payload.group_id.toString() })).count,
fileCount: +(payload.file_count ?? 50),
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,

View File

@@ -11,7 +11,10 @@ const SchemaData = {
content: { type: 'string' },
image: { type: 'string' },
pinned: { type: ['number', 'string'] },
type: { type: ['number', 'string'] },
confirm_required: { type: ['number', 'string'] },
is_show_edit_card: { type: ['number', 'string'] },
tip_window_type: { type: ['number', 'string'] },
},
required: ['group_id', 'content'],
} as const satisfies JSONSchema;
@@ -22,6 +25,7 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SendGroupNotice;
async _handle(payload: Payload) {
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
if (payload.image) {
//公告图逻辑
@@ -47,12 +51,28 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
}
UploadImage = ImageUploadResult.picInfo;
}
const noticePinned = +(payload.pinned ?? 0);
const noticeConfirmRequired = +(payload.confirm_required ?? 0);
const publishGroupBulletinResult = await this.core.apis.GroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
if (publishGroupBulletinResult.result != 0) {
throw `设置群公告失败,错误信息:${publishGroupBulletinResult.errMsg}`;
const noticeType = +(payload.type ?? 1);
const noticePinned = +(payload.pinned ?? 0);
const noticeShowEditCard = +(payload.is_show_edit_card ?? 0);
const noticeTipWindowType = +(payload.tip_window_type ?? 0);
const noticeConfirmRequired = +(payload.confirm_required ?? 1);
//const publishGroupBulletinResult = await this.core.apis.GroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
const publishGroupBulletinResult = await this.core.apis.WebApi.setGroupNotice(
payload.group_id.toString(),
payload.content,
noticePinned,
noticeType,
noticeShowEditCard,
noticeTipWindowType,
noticeConfirmRequired,
UploadImage?.id,
UploadImage?.width,
UploadImage?.height
);
if (!publishGroupBulletinResult || publishGroupBulletinResult.ec != 0) {
throw `设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`;
}
return null;
}

View File

@@ -26,7 +26,7 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error(`Uin2Uid Error ${payload.user_id}不存在`);
const [member, info] = await Promise.allSettled([
this.core.apis.GroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache),
this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, isNocache),
this.core.apis.UserApi.getUserDetailInfo(uid),
]);
if (member.status !== 'fulfilled') throw new Error(`群(${payload.group_id})成员${payload.user_id}获取失败 ${member.reason}`);

View File

@@ -55,12 +55,7 @@ import { GoCQHTTPHandleQuickAction } from './go-cqhttp/QuickAction';
import { GetGroupIgnoredNotifies } from './group/GetGroupIgnoredNotifies';
import { GetOnlineClient } from './go-cqhttp/GetOnlineClient';
import { IOCRImage, OCRImage } from './extends/OCRImage';
import { GetGroupFileCount } from './file/GetGroupFileCount';
import { GetGroupFileList } from './file/GetGroupFileList';
import { TranslateEnWordToZn } from './extends/TranslateEnWordToZn';
import { SetGroupFileFolder } from './file/SetGroupFileFolder';
import { DelGroupFile } from './file/DelGroupFile';
import { DelGroupFileFolder } from './file/DelGroupFileFolder';
import { SetQQProfile } from './go-cqhttp/SetQQProfile';
import { ShareGroupEx, SharePeer } from './extends/ShareContact';
import { CreateCollection } from './extends/CreateCollection';
@@ -92,11 +87,6 @@ import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesBy
export type ActionMap = Map<string, BaseAction<any, any>>;
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore): ActionMap {
const ncDelGroupFile = new DelGroupFile(obContext, core);
const ncSetGroupFileFolder = new SetGroupFileFolder(obContext, core);
const ncDelGroupFileFolder = new DelGroupFileFolder(obContext, core);
const ncGetGroupFileCount = new GetGroupFileCount(obContext, core);
const goCqHttpGetGroupRootFiles = new GetGroupRootFiles(obContext, core, ncGetGroupFileCount);
const actionHandlers = [
new GetGroupInfoEx(obContext, core),
@@ -113,11 +103,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new MarkPrivateMsgAsRead(obContext, core),
new SetQQAvatar(obContext, core),
new TranslateEnWordToZn(obContext, core),
ncGetGroupFileCount,
new GetGroupFileList(obContext, core),
ncSetGroupFileFolder,
ncDelGroupFile,
ncDelGroupFileFolder,
new GetGroupRootFiles(obContext, core),
// onebot11
new SendLike(obContext, core),
new GetMsg(obContext, core),
@@ -185,12 +171,11 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new SetInputStatus(obContext, core),
new GetCSRF(obContext, core),
new DelGroupNotice(obContext, core),
new DeleteGroupFile(obContext, core, ncDelGroupFile),
new CreateGroupFileFolder(obContext, core, ncSetGroupFileFolder),
new DeleteGroupFileFolder(obContext, core, ncDelGroupFileFolder),
new DeleteGroupFile(obContext, core),
new CreateGroupFileFolder(obContext, core),
new DeleteGroupFileFolder(obContext, core),
new GetGroupFileSystemInfo(obContext, core),
goCqHttpGetGroupRootFiles,
new GetGroupFilesByFolder(obContext, core, goCqHttpGetGroupRootFiles),
new GetGroupFilesByFolder(obContext, core),
];
const actionMap = new Map();
for (const action of actionHandlers) {

View File

@@ -28,18 +28,14 @@ class DeleteMsg extends BaseAction<Payload, void> {
const ret = this.core.eventWrapper.registerListen(
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
1000,
(msgs) => !!msgs.find(m => m.msgId === msg.MsgId && m.recallTime !== '0'),
).catch(() => new Promise<undefined>((resolve) => {
resolve(undefined);
}));
).catch(() => undefined);
await this.core.apis.MsgApi.recallMsg(msg.Peer, [msg.MsgId]);
const data = await ret;
if (!data) {
//throw new Error('Recall failed');
}
//await sleep(100);
//await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId]);
if (!data) throw new Error('Recall failed');
} else {
throw new Error('Recall failed');
}
}
}

View File

@@ -173,9 +173,18 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
const { sendElements } = await this.obContext.apis.MsgApi
.createSendElements(OB11Data, destPeer);
//拆分消息
const MixElement = sendElements.filter(element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO);
const SingleElement = sendElements.filter(element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO).map(e => [e]);
const MixElement = sendElements.filter(
element =>
element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO && element.elementType !== ElementType.ARK
);
const SingleElement = sendElements.filter(
element =>
element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO || element.elementType === ElementType.ARK
).map(e => [e]);
const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0);
const MsgNodeList: Promise<RawMessage | undefined>[] = [];
for (const sendElementsSplitElement of AllElement) {

View File

@@ -213,7 +213,7 @@ export class OneBotGroupApi {
async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined;
const member = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.memberUid);
const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
const memberUin = member?.uin;
const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
if (memberUin) {
@@ -271,14 +271,11 @@ export class OneBotGroupApi {
guildId: '',
peerUid: groupCode,
};
const replyMsgList = (await this.core.apis.MsgApi.getMsgExBySeq(peer, msgSeq)).msgList;
const replyMsgList = (await this.core.apis.MsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList;
if (replyMsgList.length < 1) {
return;
}
const replyMsg = replyMsgList
.filter(e => e.msgSeq == msgSeq)
.sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
//console.log("表情回应消息长度检测", msgSeq, replyMsg.elements);
const replyMsg = replyMsgList[0];
if (!replyMsg) {
this.core.context.logger.logError('解析表情回应消息失败: 未找到回应消息');
return undefined;

View File

@@ -115,6 +115,7 @@ export class OneBotMsgApi {
file_id: encodedFileId,
url: await this.core.apis.FileApi.getImageUrl(element),
file_size: element.fileSize,
file_unique: element.fileName
},
};
} catch (e: any) {
@@ -129,20 +130,6 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
await this.core.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize,
element.fileName,
);
return {
type: OB11MessageDataType.file,
data: {
@@ -151,6 +138,7 @@ export class OneBotMsgApi {
url: element.filePath,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
file_size: element.fileSize,
file_unique: element.fileName,
},
};
},
@@ -182,20 +170,6 @@ export class OneBotMsgApi {
},
marketFaceElement: async (_, msg, elementWrapper) => {
await this.core.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
'0',
'marketface',
);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
@@ -208,6 +182,7 @@ export class OneBotMsgApi {
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
path: elementWrapper.elementId,
url: elementWrapper.elementId,
file_unique: _.key
},
};
},
@@ -219,7 +194,7 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
if (!records) {
if (!records || !element.replyMsgTime || !element.senderUidStr) {
this.core.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
return null;
}
@@ -234,29 +209,13 @@ export class OneBotMsgApi {
if (records.peerUin === '284840486') {
return createReplyData(records.msgId);
}
let replyMsg: RawMessage | undefined;
// Attempt 1
replyMsg = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer,element.replayMsgSeq, 1, true, true))
.msgList
.find(msg => msg.msgRandom === records.msgRandom);
let replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
.msgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// Attempt 2
replyMsg = (await this.core.apis.MsgApi.getSingleMsg(peer, element.replayMsgSeq)).msgList[0];
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// Attempt 3
const replyMsgList = (await this.core.apis.MsgApi.getMsgExBySeq(peer, records.msgSeq)).msgList;
if (replyMsgList.length < 1) {
this.core.context.logger.logError('回复消息消息验证失败', element.replayMsgSeq);
return null;
}
replyMsg = replyMsgList.filter(e => e.msgSeq == records.msgSeq)
.sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
}
this.core.context.logger.logError('获取不到引用的消息', element.replayMsgSeq);
return null;
}
return createReplyData(replyMsg.msgId);
},
@@ -299,21 +258,6 @@ export class OneBotMsgApi {
videoDownUrl = element.filePath;
}
await this.core.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize ?? '0',
element.fileName,
);
return {
type: OB11MessageDataType.video,
data: {
@@ -322,6 +266,7 @@ export class OneBotMsgApi {
url: videoDownUrl,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
file_size: element.fileSize,
file_unique: element.fileName,
},
};
},
@@ -332,20 +277,6 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
await this.core.apis.FileApi.addFileCache(
{
peerUid: msg.peerUid,
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
elementWrapper.elementId,
elementWrapper.elementType.toString(),
element.fileSize || '0',
element.fileUuid || '',
);
return {
type: OB11MessageDataType.voice,
data: {
@@ -725,6 +656,9 @@ export class OneBotMsgApi {
message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: this.core.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
};
if (this.core.selfInfo.uin == msg.senderUin) {
resMsg.message_sent_type = 'self';
}
if (msg.chatType == ChatType.KCHATTYPEGROUP) {
resMsg.sub_type = 'normal'; // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼
resMsg.group_id = parseInt(msg.peerUin);
@@ -743,8 +677,10 @@ export class OneBotMsgApi {
if (ret.result === 0) {
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
resMsg.temp_source = resMsg.group_id;
} else {
resMsg.group_id = 284840486; //兜底数据
resMsg.temp_source = resMsg.group_id;
resMsg.sender.nickname = '临时会话';
}
}

View File

@@ -66,10 +66,7 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type;
for (const name in data.data) {
const value =
// eslint-disable-next-line
// @ts-ignore
data.data[name];
const value = (data.data as any)[name];
if (value === undefined) {
continue;
}

View File

@@ -24,7 +24,7 @@ export class OB11Entities {
...rawFriend.coreInfo,
user_id: parseInt(rawFriend.coreInfo.uin),
nickname: rawFriend.coreInfo.nick,
remark: rawFriend.coreInfo.nick,
remark: rawFriend.coreInfo.remark ?? rawFriend.coreInfo.nick,
sex: this.sex(rawFriend.baseInfo.sex!),
level: 0,
}));
@@ -109,7 +109,7 @@ export class OB11Entities {
static file(peerId: string, file: Exclude<GroupFileInfoUpdateParamType['item'][0]['fileInfo'], undefined>): OB11GroupFile {
return {
group_id: parseInt(peerId),
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId),
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId),
file_name: file.fileName,
busid: file.busId,
size: parseInt(file.fileSize),

View File

@@ -43,9 +43,6 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
import { LRUCache } from '@/common/lru-cache';
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
import { SysMessage } from '@/core/proto/SysMessage';
import { GreyTipWrapper } from '@/core/proto/GreyTipWrapper';
import { EmojiLikeToOthersWrapper1 } from '@/core/proto/EmojiLikeToOthers';
//OneBot实现类
export class NapCatOneBot11Adapter {
@@ -101,7 +98,7 @@ export class NapCatOneBot11Adapter {
if (ob11Config.http.enablePost) {
ob11Config.http.postUrls.forEach(url => {
this.networkManager.registerAdapter(new OB11ActiveHttpAdapter(
url, ob11Config.token, this.core, this,
url, ob11Config.http.secret, this.core, this,
));
});
}
@@ -171,7 +168,7 @@ export class NapCatOneBot11Adapter {
if (now.http.enablePost) {
now.http.postUrls.forEach(url => {
this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
url, now.token, this.core, this,
url, now.http.secret, this.core, this,
));
});
} else {
@@ -185,7 +182,7 @@ export class NapCatOneBot11Adapter {
);
for (const url of added) {
await this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
url, now.token, this.core, this,
url, now.http.secret, this.core, this,
));
}
}
@@ -241,7 +238,8 @@ export class NapCatOneBot11Adapter {
private initMsgListener() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onRecvSysMsg = async msg => {
/*
msgListener.onRecvSysMsg = async () => {
const sysMsg = SysMessage.fromBinary(Uint8Array.from(msg));
if (sysMsg.msgSpec.length === 0) {
return;
@@ -268,6 +266,7 @@ export class NapCatOneBot11Adapter {
}
}
};
*/
msgListener.onInputStatusPush = async data => {
const uin = await this.core.apis.UserApi.getUinByUidV2(data.fromUin);

View File

@@ -131,23 +131,24 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
private async handleMessage(message: any) {
let receiveData: { action: ActionName, params?: any, echo?: any } = { action: ActionName.Unknown, params: {} };
let echo = undefined;
try {
try {
receiveData = JSON.parse(message.toString());
echo = receiveData.echo;
this.logger.logDebug('[OneBot] [WebSocket Client] 收到正向Websocket消息', receiveData);
} catch (e) {
this.checkStateAndReply<any>(OB11Response.error('json解析失败,请检查数据格式', 1400, echo));
return;
}
receiveData.params = (receiveData?.params) ? receiveData.params : {};//兼容类型验证
const retdata = await this.actions.get(receiveData.action)
?.websocketHandle(receiveData.params, echo ?? '');
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet);
receiveData = JSON.parse(message.toString());
echo = receiveData.echo;
this.logger.logDebug('[OneBot] [WebSocket Client] 收到正向Websocket消息', receiveData);
} catch (e) {
this.logger.logError('[OneBot] [WebSocket Client] 发生错误', e);
this.checkStateAndReply<any>(OB11Response.error('不支持的api ' + receiveData.action, 1404, echo));
this.checkStateAndReply<any>(OB11Response.error('json解析失败,请检查数据格式', 1400, echo));
return;
}
receiveData.params = (receiveData?.params) ? receiveData.params : {};//兼容类型验证
const action = this.actions.get(receiveData.action);
if (!action) {
this.logger.logError('[OneBot] [WebSocket Client] 发生错误', '不支持的api ' + receiveData.action);
this.checkStateAndReply<any>(OB11Response.error('不支持的api ' + receiveData.action, 1404, echo));
return;
}
const retdata = await action.websocketHandle(receiveData.params, echo ?? '');
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet);
}
}

View File

@@ -146,22 +146,23 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
let receiveData: { action: ActionName, params?: any, echo?: any } = { action: ActionName.Unknown, params: {} };
let echo = undefined;
try {
try {
receiveData = JSON.parse(message.toString());
echo = receiveData.echo;
//this.logger.logDebug('收到正向Websocket消息', receiveData);
} catch (e) {
this.checkStateAndReply<any>(OB11Response.error('json解析失败,请检查数据格式', 1400, echo), wsClient);
return;
}
receiveData.params = (receiveData?.params) ? receiveData.params : {};//兼容类型验证
const retdata = await this.actions.get(receiveData.action)?.websocketHandle(receiveData.params, echo ?? '');
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet, wsClient);
receiveData = JSON.parse(message.toString());
echo = receiveData.echo;
//this.logger.logDebug('收到正向Websocket消息', receiveData);
} catch (e) {
this.checkStateAndReply<any>(OB11Response.error('不支持的api ' + receiveData.action, 1404, echo), wsClient);
this.checkStateAndReply<any>(OB11Response.error('json解析失败,请检查数据格式', 1400, echo), wsClient);
return;
}
receiveData.params = (receiveData?.params) ? receiveData.params : {};//兼容类型验证
const action = this.actions.get(receiveData.action);
if (!action) {
this.logger.logError('[OneBot] [WebSocket Client] 发生错误', '不支持的api ' + receiveData.action);
this.checkStateAndReply<any>(OB11Response.error('不支持的api ' + receiveData.action, 1404, echo), wsClient);
return;
}
const retdata = await action.websocketHandle(receiveData.params, echo ?? '');
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet, wsClient);
}
}

View File

@@ -8,6 +8,8 @@ export enum OB11MessageType {
}
export interface OB11Message {
temp_source?: number;
message_sent_type?: string;
target_id?: number; // 自己发送的消息才有此字段
self_id?: number,
time: number,

View File

@@ -49,12 +49,20 @@ export async function NCoreInitShell() {
const session = new wrapper.NodeIQQNTWrapperSession();
// from get dataPath
let dataPath = wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!dataPath) {
dataPath = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(dataPath, { recursive: true });
}
const dataPathGlobal = path.resolve(dataPath, './nt_qq/global');
const [dataPath, dataPathGlobal] = (() => {
if (os.platform() === 'darwin') {
const userPath = os.homedir();
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
return [appDataPath, path.join(appDataPath, 'global')];
}
let dataPath = wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!dataPath) {
dataPath = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(dataPath, { recursive: true });
}
const dataPathGlobal = path.resolve(dataPath, './nt_qq/global');
return [dataPath, dataPathGlobal];
})();
// from initConfig
engine.initWithDeskTopConfig(
@@ -115,7 +123,7 @@ export async function NCoreInitShell() {
const realBase64 = pngBase64QrcodeData.replace(/^data:image\/\w+;base64,/, '');
const buffer = Buffer.from(realBase64, 'base64');
logger.logWarn('请扫描下面的二维码然后在手Q上授权登录');
const qrcodePath = path.join(pathWrapper.binaryPath, 'qrcode.png');
const qrcodePath = path.join(pathWrapper.cachePath, 'qrcode.png');
qrcode.generate(qrcodeUrl, { small: true }, (res) => {
logger.logWarn([
'\n',
@@ -191,7 +199,7 @@ export async function NCoreInitShell() {
logger.log(`可用于快速登录的 QQ\n${historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n')
}`);
}`);
}
loginService.getQRCodePicture();
}

View File

@@ -3,6 +3,7 @@ import { ALLRouter } from './src/router';
import { LogWrapper } from '@/common/log';
import { NapCatPathWrapper } from '@/common/path';
import { WebUiConfigWrapper } from './src/helper/config';
import { RequestUtil } from '@/common/request';
const app = express();
@@ -38,7 +39,23 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
app.use(config.prefix + '/api', ALLRouter);
app.listen(config.port, config.host, async () => {
log(`[NapCat] [WebUi] Current WebUi is running at http://${config.host}:${config.port}${config.prefix}`);
log(`[NapCat] [WebUi] Login URL is http://${config.host}:${config.port}${config.prefix}/webui`);
log(`[NapCat] [WebUi] Login Token is ${config.token}`);
log(`[NapCat] [WebUi] WebUi User Panel Url: http://${config.host}:${config.port}${config.prefix}/webui?token=${config.token}`);
log(`[NapCat] [WebUi] WebUi Local Panel Url: http://127.0.0.1:${config.port}${config.prefix}/webui?token=${config.token}`);
//获取上网Ip
//https://www.ip.cn/api/index?ip&type=0
RequestUtil.HttpGetJson<{ IP: {IP:string} }>(
'https://ip.011102.xyz/',
'GET',
{},
{},
true,
true
).then((data) => {
log(`[NapCat] [WebUi] WebUi Publish Panel Url: http://${data.IP.IP}:${config.port}${config.prefix}/webui/?token=${config.token}`);
}).catch((err) => {
logger.logError(`[NapCat] [WebUi] Get Publish Panel Url Error: ${err}`);
});
});
}

View File

@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V2.2.30', 'napcat-update-button', 'secondary'),
SettingButton('V2.3.7', 'napcat-update-button', 'secondary'),
),
]),
SettingList([

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V2.2.30", "napcat-update-button", "secondary")
SettingButton("V2.3.7", "napcat-update-button", "secondary")
)
]),
SettingList([

View File

@@ -29,7 +29,7 @@
margin-bottom: 20px;
}
input[type="text"] {
input[type="password"] {
width: 90%;
padding: 10px;
font-size: 16px;
@@ -64,7 +64,7 @@
<form id="token-form" onsubmit="event.preventDefault(); submitToken();">
<div class="input-group">
<label for="token-input">Enter Token:</label>
<input type="text" id="token-input" required>
<input type="password" id="token-input" required>
</div>
<p class="error-message hidden" id="error-message"></p>
<button type="submit">Login</button>
@@ -133,4 +133,4 @@
</script>
</body>
</html>
</html>

View File

@@ -25,12 +25,6 @@
"paths": {
"@*": [
"./src*"
],
"@/core": [
"./src/core/index",
],
"@/core/*": [
"./src/core/*"
]
}
},

View File

@@ -11,7 +11,6 @@ const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].fl
function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };
}
let startScripts: string[] | undefined = undefined;
if (process.env.NAPCAT_BUILDSYS == 'linux') {
if (process.env.NAPCAT_BUILDARCH == 'x64') {
@@ -82,6 +81,7 @@ const ShellBaseConfigPlugin: PluginOption[] = [
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
{ src: './package.json', dest: 'dist' },
{ src: './launcher/', dest: 'dist', flatten: true },
// { src: './README.md', dest: 'dist' },
// { src: './logo.png', dest: 'dist/logs' },
...(startScripts.map((startScript) => {