Compare commits

...

112 Commits

Author SHA1 Message Date
手瓜一十雪
68216415b6 release: 2.1.0 2024-08-21 08:16:59 +08:00
手瓜一十雪
ba53da18d1 release: 2.1.0 2024-08-21 08:13:53 +08:00
手瓜一十雪
9b76fa3582 chore: LLNC Deprecated 2024-08-21 08:12:52 +08:00
手瓜一十雪
13d8d10a7f Merge pull request #289 from NapNeko/extend
Support: 9.9.15-27254
2024-08-21 08:09:37 +08:00
手瓜一十雪
5c6c1bb09d Merge branch 'main' into extend 2024-08-20 20:36:53 +08:00
手瓜一十雪
12105d96ea release: v2.0.37 2024-08-20 20:33:06 +08:00
手瓜一十雪
4054756035 Merge branch 'main' into extend 2024-08-20 20:27:22 +08:00
手瓜一十雪
16769c7838 fix: 提高兼容性 2024-08-20 20:26:49 +08:00
手瓜一十雪
cd076c5959 fix 2024-08-20 20:22:44 +08:00
手瓜一十雪
f52e1aa131 Merge branch 'main' into extend 2024-08-20 20:07:02 +08:00
手瓜一十雪
fdc1ef7e9a fix: error 2024-08-20 20:03:41 +08:00
手瓜一十雪
9cccf2d47b Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-20 20:03:20 +08:00
手瓜一十雪
0796f27f2a fix: file ext and blank data 2024-08-20 20:03:02 +08:00
手瓜一十雪
6c84014e0d doc: 2024-08-20 19:36:13 +08:00
手瓜一十雪
cd496a22bf chore: 调整appid 2024-08-20 17:47:14 +08:00
手瓜一十雪
0200343780 chore: 调整appid 2024-08-20 17:12:07 +08:00
手瓜一十雪
47fb629d26 Merge branch 'main' into extend 2024-08-20 16:59:17 +08:00
Alen
71ae08706b release: v2.0.35 2024-08-20 16:49:16 +08:00
Alen
50dd798757 Merge pull request #285 from cnxysoft/upmain
refactor: 接口兼容
2024-08-20 16:39:51 +08:00
Alen
0c8cf73746 refactor: 接口兼容
调整SetGroupHeader接口为SetGroupPortrait,使其兼容gocq标准
2024-08-20 16:35:23 +08:00
Alen
1bee811312 refactor: 接口兼容
调整SetSelfProfile接口为SetQQProfile,使其兼容gocq标准
2024-08-20 16:05:23 +08:00
Alen
b4c0068637 Merge pull request #284 from cnxysoft/upmain
fix: 多处修复
2024-08-20 14:31:02 +08:00
Alen
f484c6e5fe fix: 多处修复
1.修复group_card事件上报
2.修复group_admin事件上报
2024-08-20 14:28:32 +08:00
Alen
7a08187c5f fix: Uid转Uin
修复Uid转Uin兜底逻辑
2024-08-20 07:28:25 +08:00
Alen
c4d7d5a0d4 fix: 多处修改
1.修改(疑似)旧设备回复消息验证失败的解决方案
2.修复Base64发送文件失败
3.修复群时间监听器未注册
2024-08-20 01:02:03 +08:00
手瓜一十雪
5b75e753a7 Util 2024-08-19 21:38:31 +08:00
手瓜一十雪
326e9b86ce Merge branch 'main' into extend 2024-08-19 20:44:49 +08:00
手瓜一十雪
d22f5d369c releas: v2.0.34 2024-08-19 20:27:04 +08:00
手瓜一十雪
d76503995c fix: ws心跳实现 2024-08-19 20:24:26 +08:00
手瓜一十雪
eab930c083 doc: 小tip 2024-08-19 19:33:58 +08:00
手瓜一十雪
e430cc54f2 Merge branch 'main' into extend 2024-08-19 19:03:31 +08:00
手瓜一十雪
fd26a9c698 fix 2024-08-19 18:53:47 +08:00
手瓜一十雪
e79b608f77 support: 27206 2024-08-19 18:47:12 +08:00
Wesley F. Young
42b23a6c9c docs: clarify version range 2024-08-19 16:17:33 +08:00
Alen
8d94f24c71 release: v2.0.33 2024-08-19 15:43:41 +08:00
Alen
6ac74c39d9 Merge pull request #279 from cnxysoft/upmain
fix: 多处修复
2024-08-19 15:41:10 +08:00
Alen
836eb7b708 fix: 多处修复
1.修复部分Uin转换失败导致的相关API错误
2.继续改进get_group_member_info效率
3.增加fetchUserDetailInfo容错(暂时性/待观察)
2024-08-19 15:39:34 +08:00
Alen
698624b4dc release: v2.0.32 2024-08-19 10:09:53 +08:00
Alen
5c1df82076 Merge pull request #278 from cnxysoft/upmain
fix: 多处修改
2024-08-19 10:07:16 +08:00
Alen
5d649b3687 perf: 优化API效率
优化get_group_member_info效率
2024-08-19 00:55:32 +08:00
Alen
a6a3d71155 Revert "perf: API优化"
This reverts commit 1cc9d501ab.
2024-08-18 18:49:46 +08:00
Alen
1cc9d501ab perf: API优化
优化get_group_member_info在查询非群员时的效率
2024-08-18 14:33:35 +08:00
Alen
7a98025df8 fix: 引用消息失败
修复 (疑似)旧设备引用消息验证失败
2024-08-18 01:40:14 +08:00
Alen
44d6ed5e80 release: v2.0.31 2024-08-17 23:15:52 +08:00
手瓜一十雪
b5f2226bef Merge pull request #270 from cnxysoft/upmain
fix: Uid转Uin
2024-08-17 22:53:42 +08:00
Alen
ddbffe55d2 Merge branch 'main' into upmain 2024-08-17 22:19:49 +08:00
手瓜一十雪
9676b1d0e9 fix 2024-08-17 18:13:43 +08:00
手瓜一十雪
8142d3bfeb fix: 打错啦 2024-08-17 18:12:43 +08:00
手瓜一十雪
755ad27a0a Merge pull request #269 from gfhdhytghd/patch-1
修改许可证以禁止宣传
2024-08-17 15:45:20 +08:00
手瓜一十雪
5afa2dcdf1 chore: 复活赛打赢啦 2024-08-17 15:37:19 +08:00
手瓜一十雪
03098ee024 chore: util 2024-08-17 15:21:47 +08:00
手瓜一十雪
a2bfdd003c fix: getNTUserDataInfoConfig 2024-08-17 15:18:33 +08:00
lin
7eb80646ba Update LICENSE
修改License,从法律层面禁止在公共社交媒体宣传
2024-08-17 14:11:57 +08:00
Alen
6fd24e57d3 Merge branch 'main' into upmain 2024-08-17 13:24:36 +08:00
手瓜一十雪
22c90adb47 chore: docs 2024-08-17 12:27:53 +08:00
Alen
df0c6fafbe fix: Uid转Uin
修复设置管理、禁言等数个API失效
对查询企业陌生人信息进行容错
2024-08-17 00:17:51 +08:00
手瓜一十雪
dc30321b04 Update README.md 2024-08-17 00:16:23 +08:00
手瓜一十雪
63dd98d2df release: 2.0.30 2024-08-16 21:44:34 +08:00
手瓜一十雪
caaa6ed506 feat: support SetInputStatus 2024-08-16 20:35:05 +08:00
手瓜一十雪
caf23792cb Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-16 13:01:38 +08:00
手瓜一十雪
e430db20aa release: 2.0.29 2024-08-16 13:01:30 +08:00
手瓜一十雪
6fc5da9b67 Merge pull request #267 from Fripine/fix/OB11GroupRequestEvent
fix: wrong user_id in GroupRequestEvent
2024-08-16 13:00:32 +08:00
Fripine
f428e57724 fix: GroupRequestEvent 2024-08-16 12:57:42 +08:00
手瓜一十雪
14ab21fe9a Merge pull request #266 from Fripine/fix/OB11FriendRequestEvent
fix: wrong comment words in FriendRequestEvent
2024-08-16 12:44:33 +08:00
手瓜一十雪
85626e19da release: 2.0.28 2024-08-16 12:44:11 +08:00
Fripine
8712160fd7 fix: FriendRequestEvent 2024-08-16 12:21:33 +08:00
手瓜一十雪
75b33f5cb1 chore: fix 2024-08-16 12:16:21 +08:00
手瓜一十雪
f5e8ede847 release: 2.0.27 2024-08-16 10:49:22 +08:00
手瓜一十雪
3b3f684a8c chore: 清除废弃代码 2024-08-16 09:52:50 +08:00
手瓜一十雪
a78b60d40e chore: 进一步识别会话 2024-08-16 09:37:36 +08:00
手瓜一十雪
9ff06a3c44 release: 2.0.26 2024-08-15 23:08:31 +08:00
手瓜一十雪
8532dc486c fix: error 2024-08-15 23:07:59 +08:00
手瓜一十雪
861340f4bf release: 2.0.25 2024-08-15 22:17:58 +08:00
手瓜一十雪
cdcb51ebe4 build: v2.0.24 2024-08-15 20:11:45 +08:00
手瓜一十雪
0b11786d7d fix 2024-08-15 20:10:35 +08:00
手瓜一十雪
1742247a9a build: fix 2024-08-15 19:40:33 +08:00
手瓜一十雪
42bad123b2 chore: 优化一处逻辑 2024-08-15 19:37:06 +08:00
手瓜一十雪
2d1e87defc chore: 代码质量提高 2024-08-15 19:34:05 +08:00
手瓜一十雪
1c6f783a07 chore: 移除错误推断 2024-08-15 19:27:28 +08:00
手瓜一十雪
6aafc097d5 chore: 废弃无用代码 2024-08-15 19:26:32 +08:00
手瓜一十雪
4010f233dd chore: 移除废弃函数 2024-08-15 19:04:14 +08:00
手瓜一十雪
75f67caa1b release: v2.0.23 2024-08-15 18:46:26 +08:00
手瓜一十雪
d760ce54b7 chore: 重构文件处理 2024-08-15 18:44:29 +08:00
手瓜一十雪
956976ebd5 fix 2024-08-15 17:10:18 +08:00
手瓜一十雪
f9c2d4ca6c fix 2024-08-15 17:07:26 +08:00
手瓜一十雪
dd5cc3c38c fix 2024-08-15 17:04:34 +08:00
手瓜一十雪
daed4cc13e fix 2024-08-15 17:03:55 +08:00
手瓜一十雪
6ff614dd18 fix 2024-08-15 16:56:03 +08:00
手瓜一十雪
eb70ac4266 release 2024-08-15 16:51:31 +08:00
手瓜一十雪
a3a431adb7 fix 2024-08-15 16:45:24 +08:00
手瓜一十雪
e12c72ab98 action fix 2024-08-15 16:41:47 +08:00
手瓜一十雪
9f8549b831 build: 2.0.22 2024-08-15 16:34:50 +08:00
手瓜一十雪
b2de256f87 release: 2.0.22 2024-08-15 16:15:33 +08:00
手瓜一十雪
7f32a5cf9e build: test 2024-08-15 12:28:51 +08:00
手瓜一十雪
56f8314d29 chore: 进一步清理无用代码 2024-08-15 11:10:02 +08:00
手瓜一十雪
4ceb2a8669 release: 2.0.21 2024-08-15 09:35:38 +08:00
手瓜一十雪
c778d3b699 chore: 提高兼容性 2024-08-15 00:33:20 +08:00
手瓜一十雪
47eda9cdf2 chore: boolen值校验 2024-08-15 00:14:31 +08:00
手瓜一十雪
dcaec4d356 style: lint 2024-08-15 00:10:13 +08:00
手瓜一十雪
aee4f349c6 chore: 丢弃废弃代码 2024-08-15 00:06:41 +08:00
手瓜一十雪
daa2c39902 chore: 兼容性提高 2024-08-15 00:00:21 +08:00
手瓜一十雪
5770fc02a1 chore: extend get兼容 2024-08-14 23:56:45 +08:00
手瓜一十雪
47cafd295b chore: 清理无用代码 2024-08-14 23:46:17 +08:00
手瓜一十雪
3296f2daf8 chore: 弃用无用代码 2024-08-14 23:41:34 +08:00
手瓜一十雪
962616545c fix: 显示自身消息 2024-08-14 23:09:07 +08:00
手瓜一十雪
11ea92c078 chore: logger 2024-08-14 23:03:24 +08:00
Seijo Cecilia
1d64fa4817 Merge remote-tracking branch 'origin/main' 2024-08-14 22:04:38 +08:00
Seijo Cecilia
c46f2956c2 fix: plugin slug 2024-08-14 22:04:29 +08:00
Wesley F. Young
8f6d4298be Merge pull request #256 from canxin121/main
typo: OB11InputStatusEvent event_type
2024-08-14 21:44:21 +08:00
canxin121
3bce81326e typo: OB11InputStatusEvent event_type 2024-08-14 21:42:12 +08:00
Seijo Cecilia
2ae9f6d0fe docs: name, slug and description of plugin manifest 2024-08-14 20:11:06 +08:00
手瓜一十雪
9266828278 chore: logger 2024-08-14 19:42:22 +08:00
63 changed files with 694 additions and 782 deletions

View File

@@ -30,34 +30,34 @@ jobs:
ls
node ./script/checkVersion.cjs
sh ./checkVersion.sh
Build-LiteLoader:
needs: [check-version]
runs-on: ubuntu-latest
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
with:
repository: 'NapNeko/NapCatQQ'
submodules: true
ref: main
token: ${{ secrets.NAPCAT_BUILD }}
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
# Build-LiteLoader:
# needs: [check-version]
# runs-on: ubuntu-latest
# steps:
# - name: Clone Main Repository
# uses: actions/checkout@v4
# with:
# repository: 'NapNeko/NapCatQQ'
# submodules: true
# ref: main
# token: ${{ secrets.NAPCAT_BUILD }}
# - name: Use Node.js 20.X
# uses: actions/setup-node@v4
# with:
# node-version: 20.x
- name: Build NuCat Framework
run: |
npm i
npm run build:framework
cd dist
npm i --omit=dev
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.Framework
path: dist
# - name: Build NuCat Framework
# run: |
# npm i
# npm run build:framework
# cd dist
# npm i --omit=dev
# cd ..
# - name: Upload Artifact
# uses: actions/upload-artifact@v4
# with:
# name: NapCat.Framework
# path: dist
Build-Shell:
runs-on: ubuntu-latest
needs: [check-version]
@@ -90,19 +90,31 @@ jobs:
path: dist
release-napcat:
needs: [Build-LiteLoader,Build-Shell]
needs: [Build-Shell]
# needs: [Build-LiteLoader,Build-Shell]
runs-on: ubuntu-latest
steps:
- name: Download All Artifact
uses: actions/download-artifact@v4
- name: Compress subdirectories
run: |
for dir in */; do
base=$(basename "$dir")
zip -r "${base}.zip" "$dir"
done
cd ./NapCat.Shell/
zip -q -r NapCat.Shell.zip *
cd ..
rm ./NapCat.Shell.zip -rf
mv ./NapCat.Shell/NapCat.Shell.zip ./
# - name: Compress subdirectories
# run: |
# cd ./NapCat.Shell/
# zip -q -r NapCat.Shell.zip *
# cd ..
# cd ./NapCat.Framework/
# zip -q -r NapCat.Framework.zip *
# cd ..
# rm ./NapCat.Shell.zip -rf
# rm ./NapCat.Framework.zip -rf
# mv ./NapCat.Shell/NapCat.Shell.zip ./
# mv ./NapCat.Framework/NapCat.Framework.zip ./
- name: Extract version from tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
@@ -116,8 +128,5 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
body_path: CHANGELOG.md
files: |
NapCat.Framework.zip
NapCat.Shell.zip
# NapCat.darwin.x64.zip
# NapCat.darwin.arm64.zip
draft: true

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC Without Social media promotion LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
@@ -111,6 +111,10 @@ above, provided that you also meet all of these conditions:
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
dYou may use this software in accordance with the above terms,
but you are not allowed to promote this project or your projects
based on this project on any public social media.
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in

View File

@@ -3,6 +3,10 @@
</div>
---
## To Be Continued
当前版本请使用内核构建版本(版本号最后的五位数)为 26702 至 26909 的 PC NTQQ 运行。
高版本QQ NapCat已完成兼容 暂时不发布 直至版本2.0.x结束。
## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
@@ -10,6 +14,7 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
## 项目优势
- [x] **多种启动方式**支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **超多接口**在实现大部分Onebot接口上扩展了一套私有API
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
## 如何使用

View File

@@ -1,10 +1,10 @@
{
"manifest_version": 4,
"type": "extension",
"name": "NapCat",
"slug": "NapCat",
"description": "现代化的 OneBot 11 协议实现",
"version": "2.0.20",
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "2.1.0",
"icon": "./logo.png",
"authors": [
{

View File

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

View File

@@ -16,16 +16,13 @@ export interface ListenerIBase {
}
export class LegacyNTEventWrapper {
private listenerMapping: Record<string, ListenerIBase>; //ListenerName-Unique -> Listener构造函数
private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession
private listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); //ListenerName-Unique -> Listener实例
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
constructor(
listenerMapping: Record<string, ListenerIBase>,
wrapperSession: NodeIQQNTWrapperSession,
wrapperSession: NodeIQQNTWrapperSession
) {
this.listenerMapping = listenerMapping;
this.WrapperSession = wrapperSession;
}
@@ -72,18 +69,17 @@ export class LegacyNTEventWrapper {
}
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
const ListenerType = this.listenerMapping![listenerMainName];
let Listener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
let existListener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!existListener) {
let Listener = this.createProxyDispatch(listenerMainName);
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1];
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
addfunc!(Listener as T);
//console.log(addfunc!(Listener as T));
this.listenerManager.set(listenerMainName + uniqueCode, Listener);
return Listener as T;
}
return Listener as T;
return existListener as T;
}
//统一回调清理事件

View File

@@ -2,7 +2,7 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.0.20';
export const napcat_version = '2.1.0';
export class NapCatPathWrapper {
binaryPath: string;

View File

@@ -74,7 +74,7 @@ export class QQBasicInfoWrapper {
this.context.logger.log(
`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`,
);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: this.getQUAInternal() };
return { appid: systemPlatform === 'linux' ? '537240645' : '537240610', qua: this.getQUAInternal() };
}
}

View File

@@ -1,10 +1,9 @@
import fs from 'fs';
import fsPromise, { stat } from 'fs/promises';
import { stat } from 'fs/promises';
import crypto, { randomUUID } from 'crypto';
import util from 'util';
import path from 'node:path';
import * as fileType from 'file-type';
import { LogWrapper } from './log';
export function isGIF(path: string) {
const buffer = Buffer.alloc(4);
@@ -13,7 +12,6 @@ export function isGIF(path: string) {
fs.closeSync(fd);
return buffer.toString() === 'GIF8';
}
// 定义一个异步函数来检查文件是否存在
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => {
@@ -94,7 +92,6 @@ export async function file2base64(path: string) {
return result;
}
export function calculateFileMD5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
// 创建一个流式读取器
@@ -166,136 +163,105 @@ type Uri2LocalRes = {
isLocal: boolean
}
export async function uri2local(TempDir: string, UriOrPath: string, fileName: string | null = null): Promise<Uri2LocalRes> {
const res = {
success: false,
errMsg: '',
fileName: '',
ext: '',
path: '',
isLocal: false,
};
if (!fileName) fileName = randomUUID();
let filePath = path.join(TempDir, fileName);//临时目录
let url = null;
//区分path和uri
export async function checkFileV2(filePath: string) {
try {
if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath);
} catch (error: any) {
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
if (ext) {
fs.renameSync(filePath, filePath + `.${ext}`);
filePath += `.${ext}`;
return { success: true, ext: ext, path: filePath };
}
} catch (e) {
// log("获取文件类型失败", filePath,e.stack)
}
return { success: false, ext: '', path: filePath };
}
export enum FileUriType {
Unknown = 0,
Local = 1,
Remote = 2,
Base64 = 3
}
export async function checkUriType(Uri: string) {
//先判断是否是本地文件
try {
if (fs.existsSync(Uri)) return { Uri: Uri, Type: FileUriType.Local };
} catch (error) {
}
try {
url = new URL(UriOrPath);
} catch (error: any) {
}
//验证url
if (!url) {
res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`;
return res;
}
if (url.protocol == 'base64:') {
// base64转成文件
const base64Data = UriOrPath.split('base64://')[1];
try {
const buffer = Buffer.from(base64Data, 'base64');
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = 'base64文件下载失败,' + e.toString();
return res;
//再判断是否是Http
if (Uri.startsWith('http://') || Uri.startsWith('https://')) {
return { Uri: Uri, Type: FileUriType.Remote };
}
} else if (url.protocol == 'http:' || url.protocol == 'https:') {
// 下载文件
let buffer: Buffer | null = null;
try {
buffer = await httpDownload(UriOrPath);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
//再判断是否是Base64
if (Uri.startsWith('base64://')) {
return { Uri: Uri, Type: FileUriType.Base64 };
}
try {
const pathInfo = path.parse(decodeURIComponent(url.pathname));
if (pathInfo.name) {
fileName = pathInfo.name;
if (pathInfo.ext) {
fileName += pathInfo.ext;
// res.ext = pathInfo.ext
}
}
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_');
res.fileName = fileName;
filePath = path.join(TempDir, randomUUID() + fileName);
fs.writeFileSync(filePath, buffer);
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString();
return res;
}
} else {
let pathname: string;
if (url.protocol === 'file:') {
if (Uri.startsWith('file://')) {
let pathname: string;
let filePath: string;
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(url.pathname);
pathname = decodeURIComponent(new URL(Uri).pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
} else {
// 26702执行forword file文件操作 不应该在这里乱来
// const cache = await dbUtil.getFileCacheByName(uri);
// if (cache) {
// filePath = cache.path;
// } else {
// filePath = uri;
// }
}
res.isLocal = true;
}
// else{
// res.errMsg = `不支持的file协议,` + url.protocol
// return res
// }
// if (isGIF(filePath) && !res.isLocal) {
// await fs.rename(filePath, filePath + ".gif");
// filePath += ".gif";
// }
if (!res.isLocal && !res.ext) {
try {
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
if (ext) {
fs.renameSync(filePath, filePath + `.${ext}`);
filePath += `.${ext}`;
res.fileName += `.${ext}`;
res.ext = ext;
}
} catch (e) {
// log("获取文件类型失败", filePath,e.stack)
}
}
res.success = true;
res.path = filePath;
return res;
}
export async function copyFolder(sourcePath: string, destPath: string, logger: LogWrapper) {
try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true });
await fsPromise.mkdir(destPath, { recursive: true });
for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name);
const dstPath = path.join(destPath, entry.name);
if (entry.isDirectory()) {
await copyFolder(srcPath, dstPath, logger);
} else {
try {
await fsPromise.copyFile(srcPath, dstPath);
} catch (error) {
logger.logError(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`);
// 这里可以决定是否要继续复制其他文件
}
}
return { Uri: filePath, Type: FileUriType.Local };
}
} catch (error) {
logger.logError('复制文件夹时出错:', error);
}
return { Uri: Uri, Type: FileUriType.Unknown };
}
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
//解析失败
if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
}
//解析File协议和本地文件
if (UriType == FileUriType.Local) {
const fileExt = path.extname(HandledUri);
const filename = path.basename(HandledUri, fileExt);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
}
//接下来都要有文件名
if (!filename) filename = randomUUID();
//解析Http和Https协议
if (UriType == FileUriType.Remote) {
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
if (pathInfo.name) {
filename = pathInfo.name;
if (pathInfo.ext) {
filename += pathInfo.ext;
}
}
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
const fileExt = path.extname(HandledUri);
const filePath = path.join(dir, filename);
const buffer = await httpDownload(HandledUri);
fs.writeFileSync(filePath, buffer);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
}
//解析Base64
if (UriType == FileUriType.Base64) {
const base64 = HandledUri.replace(/^base64:\/\//, '');
const buffer = Buffer.from(base64, 'base64');
let filePath = path.join(dir, filename);
let fileExt = '';
fs.writeFileSync(filePath, buffer);
const { success, ext, path: fileTypePath } = await checkFileV2(filePath);
if (success) {
filePath = fileTypePath;
fileExt = ext;
filename = filename + '.' + ext;
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
}

View File

@@ -53,12 +53,6 @@ export async function runAllWithTimeout<T>(tasks: Promise<T>[], timeout: number)
.map((result) => (result as { status: 'fulfilled'; value: T }).value);
}
export function getMd5(s: string) {
const h = crypto.createHash('md5');
h.update(s);
return h.digest('hex');
}
export function isNull(value: any) {
return value === undefined || value === null;
}
@@ -103,19 +97,19 @@ export function isEqual(obj1: any, obj2: any) {
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
if (os.platform() === 'linux') {
return {
baseVersion: '3.2.12-26702',
curVersion: '3.2.12-26702',
baseVersion: '3.2.12-27206',
curVersion: '3.2.12-27206',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
buildId: '27206',
};
}
return {
baseVersion: '9.9.15-26702',
curVersion: '9.9.15-26702',
baseVersion: '9.9.15-27206',
curVersion: '9.9.15-27206',
prevVersion: '',
onErrorVersions: [],
buildId: '26702',
buildId: '27206',
};
}
@@ -137,28 +131,6 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined
return configVersionInfoPath;
}
export async function deleteOldFiles(directoryPath: string, daysThreshold: number) {
try {
const files = await fsPromise.readdir(directoryPath);
for (const file of files) {
const filePath = path.join(directoryPath, file);
const stats = await fsPromise.stat(filePath);
const lastModifiedTime = stats.mtimeMs;
const currentTime = Date.now();
const timeDifference = currentTime - lastModifiedTime;
const daysDifference = timeDifference / (1000 * 60 * 60 * 24);
if (daysDifference > daysThreshold) {
await fsPromise.unlink(filePath); // Delete the file
//console.log(`Deleted: ${filePath}`);
}
}
} catch (error) {
//console.error('Error deleting files:', error);
}
}
export function calcQQLevel(level: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level;
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;

File diff suppressed because one or more lines are too long

View File

@@ -26,10 +26,6 @@ export class NTQQFriendApi {
return Array.from(data.values());
}
async getBuddyIdMapCache(refresh = false): Promise<LimitedHashTable<string, string>> {
return await this.getBuddyIdMap(refresh);
}
async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
const uids: string[] = [];
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000);

View File

@@ -253,22 +253,45 @@ export class NTQQGroupApi {
}
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
//type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
type EventType = NodeIKernelGroupService['getMemberInfo'];
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced);
const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
const Listener = this.core.eventWrapper.RegisterListen<(params: any) => void>
(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
1,
5000,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
return groupCode == GroupCode && members.has(uid);
forced ? 5000 : 250,
(params) => {
return params === GroupCode;
},
GroupCode, [uid], forced,
);
return _members.get(uid);
const EventFunc = this.core.eventWrapper.createEventFunction<EventType>('NodeIKernelGroupService/getMemberInfo');
const retData = await EventFunc!(GroupCode, [uid], forced);
if (retData.result !== 0) {
throw new Error(`${retData.errMsg}`);
}
const result = await Listener as unknown;
let member: GroupMember | undefined;
if (Array.isArray(result) && result?.[2] instanceof Map) {
let members = result[2] as Map<string, GroupMember>;
member = members.get(uid);
};
return member;
// 原本的方法: (no_cache 下效率很高, cache 下效率一致)
// const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
// (
// 'NodeIKernelGroupService/getMemberInfo',
// 'NodeIKernelGroupListener/onMemberInfoChange',
// 1,
// 5000,
// (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
// return groupCode == GroupCode && members.has(uid);
// },
// GroupCode, [uid], forced,
// );
// return _members.get(uid);
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {

View File

@@ -1,4 +1,4 @@
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement } from '@/core/entities';
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/entities';
import { InstanceContext, NapCatCore } from '@/core';
import { onGroupFileInfoUpdateParamType } from '@/core/listeners';
import { GeneralCallResult } from '@/core/services/common';
@@ -16,6 +16,9 @@ export class NTQQMsgApi {
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
}
async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//console.log(peer, msgSeq, emojiId, emojiType, count);
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa
@@ -37,7 +40,6 @@ export class NTQQMsgApi {
emojiId = emojiId.toString();
return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set);
}
async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise<GeneralCallResult & {
msgList: RawMessage[]
} | undefined> {
@@ -94,7 +96,22 @@ export class NTQQMsgApi {
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 getMsgExBySeq(peer: Peer, msgSeq: string) {
const DateNow = Math.floor(Date.now() / 1000);
const filterMsgFromTime = (DateNow - 300).toString();
const filterMsgToTime = DateNow.toString();
const ret = await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: filterMsgToTime,
filterMsgFromTime: filterMsgFromTime,
isReverseOrder: false,
isIncludeCurrent: true,
pageLimit: 100,
});
return ret;
}
async setMsgRead(peer: Peer) {
return this.context.session.getMsgService().setMsgRead(peer);
}
@@ -103,18 +120,18 @@ export class NTQQMsgApi {
const data = await this.core.eventWrapper.CallNormalEvent<
(GroupCode: string, params: GetFileListParam) => Promise<unknown>,
(groupFileListResult: onGroupFileInfoUpdateParamType) => void
>(
'NodeIKernelRichMediaService/getGroupFileList',
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
1,
5000,
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
>(
'NodeIKernelRichMediaService/getGroupFileList',
'NodeIKernelMsgListener/onGroupFileInfoUpdate',
1,
5000,
(groupFileListResult: onGroupFileInfoUpdateParamType) => {
//Developer Mlikiowa Todo: 此处有问题 无法判断是否成功
return true;
},
GroupCode,
params,
);
return true;
},
GroupCode,
params,
);
return data[1].item;
}
@@ -129,61 +146,6 @@ export class NTQQMsgApi {
peerUid: peer.peerUid,
}, msgIds);
}
async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
function generateMsgId() {
const timestamp = Math.floor(Date.now() / 1000);
const random = Math.floor(Math.random() * Math.pow(2, 32));
const buffer = Buffer.alloc(8);
buffer.writeUInt32BE(timestamp, 0);
buffer.writeUInt32BE(random, 4);
const msgId = BigInt('0x' + buffer.toString('hex')).toString();
return msgId;
}
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId: string;
try {
msgId = await this.getMsgUnique(peer.chatType, await this.getServerTime());
} catch (error) {
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
//兜底识别策略V2
msgId = generateMsgId().toString();
}
const data = await this.core.eventWrapper.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) {
return true;
}
}
return false;
},
msgId,
peer,
msgElements,
new Map(),
);
const retMsg = data[1].find(msgRecord => {
if (msgRecord.msgId === msgId) {
return true;
}
});
return retMsg;
}
sendMsgEx(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
//return NTQQMsgApi.sendMsgV1(peer, msgElements, waitComplete, timeout);
}
async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) {
//By Jadx/Ida Mlikiowa
const TempGameSession = {
@@ -215,29 +177,29 @@ export class NTQQMsgApi {
await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick);
}
}
const msgId = await this.getMsgUnique(peer.chatType, await this.getServerTime());
const msgId = await this.generateMsgUniqueId(peer.chatType, await this.getServerTime());
peer.guildId = msgId;
const data = await this.core.eventWrapper.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
return true;
}
>(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === SendStatusType.KSEND_STATUS_SUCCESS) {
return true;
}
return false;
},
'0',
peer,
msgElements,
new Map(),
);
}
return false;
},
'0',
peer,
msgElements,
new Map(),
);
const retMsg = data[1].find(msgRecord => {
if (msgRecord.guildId === msgId) {
return true;
@@ -246,21 +208,14 @@ export class NTQQMsgApi {
return retMsg;
}
async getMsgUnique(chatType: number, time: string) {
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
return this.context.session.getMsgService().generateMsgUniqueId(chatType, time);
}
return this.context.session.getMsgService().getMsgUniqueId(time);
async generateMsgUniqueId(chatType: number, time: string) {
return this.context.session.getMsgService().generateMsgUniqueId(chatType, time);
}
async getServerTime() {
return this.context.session.getMSFService().getServerTime();
}
async getServerTimeV2() {
return this.core.eventWrapper.callNoListenerEvent<() => string>('NodeIKernelMsgService/getServerTime', 5000);
}
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
return this.context.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map());
}
@@ -272,25 +227,25 @@ export class NTQQMsgApi {
const data = await this.core.eventWrapper.CallNormalEvent<
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/multiForwardMsgWithComment',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == this.core.selfInfo.uid) {
return true;
}
>(
'NodeIKernelMsgService/multiForwardMsgWithComment',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgRecords: RawMessage[]) => {
for (const msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == this.core.selfInfo.uid) {
return true;
}
return false;
},
msgInfos,
srcPeer,
destPeer,
[],
new Map(),
);
}
return false;
},
msgInfos,
srcPeer,
destPeer,
[],
new Map(),
);
for (const msg of data[1]) {
const arkElement = msg.elements.find(ele => ele.arkElement);
if (!arkElement) {

View File

@@ -100,7 +100,7 @@ export class NTQQUserApi {
return retData;
}
async fetchUserDetailInfo(uid: string) {
async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
@@ -111,7 +111,7 @@ export class NTQQUserApi {
(profile) => profile.uid === uid,
'BuddyProfileStore',
[uid],
UserDetailSource.KSERVER,
mode,
[ProfileBizType.KALL],
);
const RetUser: User = {
@@ -120,32 +120,23 @@ export class NTQQUserApi {
...profile.simpleInfo.vasInfo,
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
qqLevel: profile.commonExt?.qqLevel,
age: profile.simpleInfo.baseInfo.age,
pendantId: '',
};
return RetUser;
}
async getUserDetailInfo(uid: string) {
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
return this.fetchUserDetailInfo(uid);
async getUserDetailInfo(uid: string): Promise<User> {
try {
let retUser = await this.fetchUserDetailInfo(uid, UserDetailSource.KDB);
if (retUser.uin !== '0') {
return retUser;
}
} catch (e) {
}
return this.getUserDetailInfoOld(uid);
}
async getUserDetailInfoOld(uid: string) {
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo'];
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged'];
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
2,
5000,
(profile) => profile.uid === uid,
uid,
[0],
);
return profile;
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
return this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
}
async modifySelfProfile(param: ModifyProfileParams) {
@@ -203,39 +194,14 @@ export class NTQQUserApi {
return skey;
}
/**
* @deprecated
*/
async getUidByUin(Uin: string) {
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
return await this.getUidByUinV2(Uin);
}
return await this.getUidByUinV1(Uin);
}
/**
* @deprecated
*/
async getUinByUid(Uid: string) {
if (this.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
return await this.getUinByUidV2(Uid);
}
return await this.getUinByUidV1(Uid);
}
//后期改成流水线处理
async getUidByUinV2(Uin: string) {
let uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
let uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
if (uid) return uid;
uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
if (uid) return uid;
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;
// console.log((await this.core.getApiContext().FriendApi.getBuddyIdMapCache(true)));
uid = (await this.core.apis.FriendApi.getBuddyIdMapCache(true)).getValue(Uin);//从Buddy缓存获取Uid
if (uid) return uid;
uid = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getValue(Uin);
if (uid) return uid;
const unveifyUid = (await this.getUserDetailInfoByUinV2(Uin)).detail.uid;//从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) uid = unveifyUid;
//if (uid) return uid;
@@ -244,50 +210,18 @@ export class NTQQUserApi {
//后期改成流水线处理
async getUinByUidV2(Uid: string) {
let uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
if (uin) return uin;
uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
if (uin) return uin;
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
if (uin) return uin;
uin = (await this.core.apis.FriendApi.getBuddyIdMapCache(true)).getKey(Uid);//从Buddy缓存获取Uin
if (uin) return uin;
uin = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getKey(Uid);
if (uin) return uin;
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
return uin;
}
async getUidByUinV1(Uin: string) {
// 通用转换开始尝试
let uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (!uid) {
const unveifyUid = (await this.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
if (unveifyUid.indexOf('*') == -1) {
uid = unveifyUid;
}
}
return uid;
}
async getUinByUidV1(Uid: string) {
const ret = await this.core.eventWrapper.callNoListenerEvent<(Uin: string[]) => Promise<{
uinInfo: Map<string, string>
}>>
('NodeIKernelUixConvertService/getUin', 5000, [Uid]);
let uin = ret.uinInfo.get(Uid);
if (!uin) {
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
}
// if (!uin) {
// uin = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uid == Uid })?.uin; //从QQ Native 缓存转换
// }
// if (!uin) {
// uin = (await NTQQFriendApi.getFriends(true)).find((t) => { t.uid == Uid })?.uin; //从QQ Native 非缓存转换
// }
return uin;
}
async getRecentContactListSnapShot(count: number) {
return await this.context.session.getRecentContactService().getRecentContactListSnapShot(count);
}
@@ -309,11 +243,6 @@ export class NTQQUserApi {
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
}
async getUserDetailInfoByUin(Uin: string) {
return this.core.eventWrapper.callNoListenerEvent<(Uin: string) => Promise<UserDetailInfoByUin>>
('NodeIKernelProfileService/getUserDetailInfoByUin', 5000, Uin);
}
async forceFetchClientKey() {
return await this.context.session.getTicketService().forceFetchClientKey('');
}

View File

@@ -44,8 +44,8 @@ export class NapCatCore {
constructor(context: InstanceContext, selfInfo: SelfInfo) {
this.selfInfo = selfInfo;
this.context = context;
this.util = new this.context.wrapper.NodeQQNTWrapperUtil();
this.eventWrapper = new LegacyNTEventWrapper(context.wrapper, context.session);
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new LegacyNTEventWrapper(context.session);
this.apis = {
FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this),
@@ -79,7 +79,7 @@ export class NapCatCore {
}
get dataPath(): string {
let result = this.util.getNTUserDataInfoConfig();
let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!result) {
result = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(result, { recursive: true });
@@ -93,9 +93,12 @@ export class NapCatCore {
msgListener.onRecvMsg = (msgs) => {
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
};
msgListener.onAddSendMsg = (msg) => {
this.context.logger.logMessage(msg, this.selfInfo);
};
//await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener(
new this.context.wrapper.NodeIKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)),
proxiedListenerOf(msgListener, this.context.logger) as any
);
const profileListener = new ProfileListener();
@@ -110,7 +113,7 @@ export class NapCatCore {
// }
};
this.context.session.getProfileService().addKernelProfileListener(
new this.context.wrapper.NodeIKernelProfileListener(proxiedListenerOf(profileListener, this.context.logger)),
proxiedListenerOf(profileListener, this.context.logger),
);
// 群相关
@@ -193,6 +196,9 @@ export class NapCatCore {
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
}
};
this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(profileListener, this.context.logger) as any
);
}
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
if (memberNew.role !== memberOld?.role) {

View File

@@ -1,4 +1,4 @@
import { QQLevel, Sex } from './user';
import { QQLevel, Sex, User } from './user';
export enum GroupListUpdateType {
REFRESHALL,
@@ -65,6 +65,7 @@ export interface GroupMember {
uin: string; // QQ号
isRobot: boolean;
sex?: Sex;
age?: number;
qqLevel?: QQLevel;
isChangeRole: boolean;
joinTime: string;

View File

@@ -614,13 +614,28 @@ export interface PicElement {
originImageUrl?: string; // http url, 没有hosthost是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
}
export enum GrayTipElementSubType {
INVITE_NEW_MEMBER = 12,
MEMBER_NEW_TITLE = 17
export enum NTGrayTipElementSubTypeV2 {
GRAYTIP_ELEMENT_SUBTYPE_AIOOP = 15,
GRAYTIP_ELEMENT_SUBTYPE_BLOCK = 14,
GRAYTIP_ELEMENT_SUBTYPE_BUDDY = 5,
GRAYTIP_ELEMENT_SUBTYPE_BUDDYNOTIFY = 9,
GRAYTIP_ELEMENT_SUBTYPE_EMOJIREPLY = 3,
GRAYTIP_ELEMENT_SUBTYPE_ESSENCE = 7,
GRAYTIP_ELEMENT_SUBTYPE_FEED = 6,
GRAYTIP_ELEMENT_SUBTYPE_FEEDCHANNELMSG = 11,
GRAYTIP_ELEMENT_SUBTYPE_FILE = 10,
GRAYTIP_ELEMENT_SUBTYPE_GROUP = 4,
GRAYTIP_ELEMENT_SUBTYPE_GROUPNOTIFY = 8,
GRAYTIP_ELEMENT_SUBTYPE_JSON = 17,
GRAYTIP_ELEMENT_SUBTYPE_LOCALMSG = 13,
GRAYTIP_ELEMENT_SUBTYPE_PROCLAMATION = 2,
GRAYTIP_ELEMENT_SUBTYPE_REVOKE = 1,
GRAYTIP_ELEMENT_SUBTYPE_UNKNOWN = 0,
GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16,
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
}
export interface GrayTipElement {
subElementType: GrayTipElementSubType;
subElementType: NTGrayTipElementSubTypeV2;
revokeElement: {
operatorRole: string;
operatorUid: string;
@@ -876,6 +891,12 @@ export enum NTSubMsgType {
KMSGSUBTYPEMIXTEXT = 0,
KMSGSUBTYPETENCENTDOC = 6
}
export enum SendStatusType {
KSEND_STATUS_FAILED = 0,
KSEND_STATUS_SENDING = 1,
KSEND_STATUS_SUCCESS = 2,
KSEND_STATUS_SUCCESS_NOSEQ = 3
}
export interface RawMessage {
parentMsgPeer: Peer;
@@ -935,7 +956,7 @@ export interface RawMessage {
/**
* 消息状态,别人发的 2 是已撤回,自己发的 2 是已发送
*/
sendStatus?: number;
sendStatus?: SendStatusType;
/**
* 撤回时间,"0" 是没有撤回

View File

@@ -231,6 +231,7 @@ export interface User {
longNick?: string; // 签名
remark?: string;
sex?: Sex;
age?: number;
qqLevel?: QQLevel;
qid?: string;
birthday_year?: number;

View File

@@ -1,58 +1,26 @@
{
"3.1.2-13107": {
"appid": 537146866,
"qua": "V1_LNX_NQ_3.1.2-13107_RDM_B"
"3.2.12-27187": {
"appid": 537240645,
"qua": "V1_LNX_NQ_3.2.12_27187_GW_B"
},
"3.2.10-25765": {
"appid": 537234773,
"qua": "V1_LNX_NQ_3.2.10_25765_GW_B"
"3.2.12-27206": {
"appid": 537240645,
"qua": "V1_LNX_NQ_3.2.12_27206_GW_B"
},
"3.2.12-26702": {
"appid": 537237950,
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
"3.2.12-27254":{
"appid": 537240795,
"qua": "V1_LNX_NQ_3.2.12_27254_GW_B"
},
"3.2.12-26740": {
"appid": 537237950,
"qua": "V1_WIN_NQ_9.9.15_26740_GW_B"
"9.9.15-27187":{
"appid": 537240610,
"qua": "V1_WIN_NQ_9.9.15_27187_GW_B"
},
"3.2.12-26909": {
"appid": 537237923,
"qua": "V1_LNX_NQ_3.2.12_26909_GW_B"
"9.9.15-27206":{
"appid": 537240610,
"qua": "V1_WIN_NQ_9.9.15_27206_GW_B"
},
"9.9.11-24815": {
"appid": 537226656,
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
},
"9.9.12-25493": {
"appid": 537231759,
"qua": "V1_WIN_NQ_9.9.12_25493_GW_B"
},
"9.9.12-25765": {
"appid": 537234702,
"qua": "V1_WIN_NQ_9.9.12_25765_GW_B"
},
"9.9.12-26299": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26299_GW_B"
},
"9.9.12-26339": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26339_GW_B"
},
"9.9.12-26466": {
"appid": 537234826,
"qua": "V1_WIN_NQ_9.9.12_26466_GW_B"
},
"9.9.15-26702": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26740": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26909": {
"appid": 537237802,
"qua": "V1_WIN_NQ_9.9.15_26909_GW_B"
"9.9.15-27254":{
"appid": 537240709,
"qua": "V1_WIN_NQ_9.9.15_27254_GW_B"
}
}

View File

@@ -1,6 +1,6 @@
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities';
interface IGroupListener {
export interface IGroupListener {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void;
onGroupExtListUpdate(...args: unknown[]): void;

View File

@@ -1,4 +1,4 @@
import { NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListener';
import { IGroupListener, NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListener';
import {
GroupExtParam,
GroupMember,
@@ -104,7 +104,7 @@ export interface NodeIKernelGroupService {
setHeader(uid: string, path: string): unknown;
addKernelGroupListener(listener: NodeIKernelGroupListener): number;
addKernelGroupListener(listener: IGroupListener): number;
removeKernelGroupListener(listenerId: unknown): void;

View File

@@ -1,6 +1,6 @@
import { AnyCnameRecord } from 'node:dns';
import { BizKey, ModifyProfileParams, SimpleInfo, UserDetailInfoByUin } from '../entities';
import { NodeIKernelProfileListener } from '../listeners';
import { NodeIKernelProfileListener, ProfileListener } from '../listeners';
import { GeneralCallResult } from '@/core/services/common';
export enum UserDetailSource {
@@ -35,7 +35,7 @@ export interface NodeIKernelProfileService {
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>;
addKernelProfileListener(listener: NodeIKernelProfileListener): number;
addKernelProfileListener(listener: ProfileListener): number;
removeKernelProfileListener(listenerId: number): void;

View File

@@ -35,6 +35,7 @@ import { NodeIkernelTestPerformanceService } from '../services/NodeIkernelTestPe
import { NodeIKernelECDHService } from '../services/NodeIKernelECDHService';
export interface NodeQQNTWrapperUtil {
get(): unknown;
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(): NodeQQNTWrapperUtil;

View File

@@ -41,8 +41,7 @@ export async function NCoreInitFramework(
online: true,
});
};
loginService.addKernelLoginListener(new wrapper.NodeIKernelLoginListener(
proxiedListenerOf(loginListener, logger)));
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
});
// 过早进入会导致addKernelMsgListener等Listener添加失败
// await sleep(2500);

View File

@@ -5,7 +5,7 @@ import { ActionName } from '../types';
const SchemaData = {
type: 'object',
properties: {
count: { type: 'number' },
count: { type: ['number', 'string'] },
},
} as const satisfies JSONSchema;
@@ -17,7 +17,7 @@ export class FetchCustomFace extends BaseAction<Payload, string[]> {
async _handle(payload: Payload) {
//48 可能正好是QQ需要的一个页面的数量 Tagged Mlikiowa
const ret = await this.CoreContext.apis.MsgApi.fetchFavEmojiList(payload.count || 48);
const ret = await this.CoreContext.apis.MsgApi.fetchFavEmojiList(parseInt((payload.count || '0').toString()) || 48);
return ret.emojiInfoList.map(e => e.url);
}
}

View File

@@ -12,7 +12,7 @@ const SchemaData = {
emojiId: { type: 'string' },
emojiType: { type: 'string' },
message_id: { type: ['string', 'number'] },
count: { type: 'number' },
count: { type: ['string', 'number'] },
},
required: ['emojiId', 'emojiType', 'message_id'],
} as const satisfies JSONSchema;
@@ -22,12 +22,11 @@ type Payload = FromSchema<typeof SchemaData>;
export class FetchEmojiLike extends BaseAction<Payload, any> {
actionName = ActionName.FetchEmojiLike;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msgIdPeer) throw new Error('消息不存在');
const msg = (await NTQQMsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
return await NTQQMsgApi.getMsgEmojiLikesList(msgIdPeer.Peer, msg.msgSeq, payload.emojiId, payload.emojiType, payload.count);
return await NTQQMsgApi.getMsgEmojiLikesList(msgIdPeer.Peer, msg.msgSeq, payload.emojiId, payload.emojiType, parseInt((payload.count || '0').toString()) || 20);
}
}

View File

@@ -5,8 +5,8 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
category: { type: 'number' },
count: { type: 'number' },
category: { type: ['number', 'string'] },
count: { type: ['number', 'string'] },
},
required: ['category', 'count'],
} as const satisfies JSONSchema;
@@ -16,9 +16,8 @@ type Payload = FromSchema<typeof SchemaData>;
export class GetCollectionList extends BaseAction<Payload, any> {
actionName = ActionName.GetCollectionList;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQCollectionApi = this.CoreContext.apis.CollectionApi;
return await NTQQCollectionApi.getAllCollection(payload.category, payload.count);
return await NTQQCollectionApi.getAllCollection(parseInt(payload.category.toString()), parseInt(payload.count.toString()));
}
}

View File

@@ -4,7 +4,6 @@ import { ActionName } from '../types';
export class GetFriendWithCategory extends BaseAction<void, any> {
actionName = ActionName.GetFriendsWithCategory;
async _handle(payload: void) {
return (await this.CoreContext.apis.FriendApi.getBuddyV2ExWithCate(true)).map(category => ({
...category,

View File

@@ -9,7 +9,7 @@ export class GetProfileLike extends BaseAction<void, any> {
const ret = await NTQQUserApi.getProfileLike(this.CoreContext.selfInfo.uid);
const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos;
for (let i = 0; i < listdata.length; i++) {
listdata[i].uin = parseInt((await NTQQUserApi.getUinByUid(listdata[i].uid)) || '');
listdata[i].uin = parseInt((await NTQQUserApi.getUinByUidV2(listdata[i].uid)) || '');
}
return listdata;
}

View File

@@ -3,7 +3,6 @@ import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { checkFileReceived, uri2local } from '@/common/utils/file';
import fs from 'fs';
const SchemaData = {
type: 'object',
properties: {

View File

@@ -0,0 +1,44 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType, Peer } from '@/core';
const SchemaData = {
type: 'object',
properties: {
eventType: { type: 'string' },
group_id: { type: 'string' },
user_id: { type: 'string' }
},
required: ['eventType'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetInputStatus extends BaseAction<Payload, any> {
actionName = ActionName.SetInputStatus;
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
let peer: Peer;
if (payload.group_id) {
peer = {
chatType: ChatType.group,
peerUid: payload.group_id
}
} else if (payload.user_id) {
let uid = await NTQQUserApi.getUidByUinV2(payload.user_id);
if (!uid) throw new Error('uid is empty');
peer = {
chatType: ChatType.friend,
peerUid: uid
}
} else {
throw new Error('请指定 group_id 或 user_id');
}
const ret = await NTQQMsgApi.sendShowInputStatusReq(peer, parseInt(payload.eventType));
return ret;
}
}

View File

@@ -6,9 +6,9 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
status: { type: 'number' },
extStatus: { type: 'number' },
batteryStatus: { type: 'number' },
status: { type: ['number', 'string'] },
extStatus: { type: ['number', 'string'] },
batteryStatus: { type: ['number', 'string'] },
},
required: ['status', 'extStatus', 'batteryStatus'],
} as const satisfies JSONSchema;
@@ -20,14 +20,12 @@ export class SetOnlineStatus extends BaseAction<Payload, null> {
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
// 可设置状态
// { status: 10, extStatus: 1027, batteryStatus: 0 }
// { status: 30, extStatus: 0, batteryStatus: 0 }
// { status: 50, extStatus: 0, batteryStatus: 0 }
// { status: 60, extStatus: 0, batteryStatus: 0 }
// { status: 70, extStatus: 0, batteryStatus: 0 }
const NTQQUserApi = this.CoreContext.apis.UserApi;
const ret = await NTQQUserApi.setSelfOnlineStatus(payload.status, payload.extStatus, payload.batteryStatus);
const ret = await NTQQUserApi.setSelfOnlineStatus(
parseInt(payload.status.toString()),
parseInt(payload.extStatus.toString()),
parseInt(payload.batteryStatus.toString())
);
if (ret.result !== 0) {
throw new Error('设置在线状态失败');
}

View File

@@ -1,32 +0,0 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
nick: { type: 'string' },
longNick: { type: 'string' },
sex: { type: 'number' },//传Sex值建议传0
},
required: ['nick', 'longNick', 'sex'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetSelfProfile extends BaseAction<Payload, any | null> {
actionName = ActionName.SetSelfProfile;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const ret = await NTQQUserApi.modifySelfProfile({
nick: payload.nick,
longNick: payload.longNick,
sex: payload.sex,
birthday: { birthday_year: '', birthday_month: '', birthday_day: '' },
location: undefined,
});
return ret;
}
}

View File

@@ -13,7 +13,7 @@ interface FileResponse {
const SchemaData = {
type: 'object',
properties: {
thread_count: { type: 'number' },
thread_count: { type: ['number', 'string'] },
url: { type: 'string' },
base64: { type: 'string' },
name: { type: 'string' },

View File

@@ -21,20 +21,23 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
async _handle(payload: Payload): Promise<OB11User> {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const user_id = payload.user_id.toString();
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
const extendData = await NTQQUserApi.getUserDetailInfoByUinV2(user_id);
const uid = (await NTQQUserApi.getUidByUinV2(user_id))!;
if (!uid || uid.indexOf('*') != -1) {
const ret = {
...extendData,
user_id: parseInt(extendData.info.uin) || 0,
nickname: extendData.info.nick,
...extendData.detail.simpleInfo.coreInfo,
...extendData.detail.commonExt,
...extendData.detail.simpleInfo.baseInfo,
...extendData.detail.simpleInfo.relationFlags,
user_id: parseInt(extendData.detail.uin) || 0,
nickname: extendData.detail.simpleInfo.coreInfo.nick,
sex: OB11UserSex.unknown,
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
qid: extendData.info.qid,
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
age: extendData.detail.simpleInfo.baseInfo.age || 0,
qid: extendData.detail.simpleInfo.baseInfo.qid,
level: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? 0) || 0,
login_days: 0,
uid: '',
};
uid: ''
};
return ret;
}
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };

View File

@@ -7,18 +7,18 @@ import { checkFileReceived, uri2local } from '@/common/utils/file';
interface Payload {
file: string,
groupCode: string
group_id: number
}
export default class SetGroupHeader extends BaseAction<Payload, any> {
actionName = ActionName.SetGroupHeader;
export default class SetGroupPortrait extends BaseAction<Payload, any> {
actionName = ActionName.SetGroupPortrait;
// 用不着复杂检测
protected async check(payload: Payload): Promise<BaseCheckResult> {
if (!payload.file || typeof payload.file != 'string' || !payload.groupCode || typeof payload.groupCode != 'string') {
if (!payload.file || typeof payload.file != 'string' || !payload.group_id || typeof payload.group_id != 'number') {
return {
valid: false,
message: 'file和groupCode字段不能为空或者类型错误',
message: 'file和group_id字段不能为空或者类型错误',
};
}
return {
@@ -34,7 +34,7 @@ export default class SetGroupHeader extends BaseAction<Payload, any> {
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await NTQQGroupApi.setGroupAvatar(payload.groupCode, path);
const ret = await NTQQGroupApi.setGroupAvatar(payload.group_id.toString(), path) as any;
if (!isLocal) {
fs.unlink(path, () => {
});
@@ -43,11 +43,11 @@ export default class SetGroupHeader extends BaseAction<Payload, any> {
throw `头像${payload.file}设置失败,api无返回`;
}
// log(`头像设置返回:${JSON.stringify(ret)}`)
// if (ret['result'] == 1004022) {
// throw `头像${payload.file}设置失败,文件可能不是图片格式`;
// } else if (ret['result'] != 0) {
// throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
// }
if (ret['result'] == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
} else if (ret['result'] != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
}
return ret;
} else {
if (!isLocal) {

View File

@@ -0,0 +1,34 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = {
type: 'object',
properties: {
nickname: { type: 'string' },
personal_note: { type: 'string' },
sex: { type: ['number', 'string'] },//传Sex值建议传0
},
required: ['nickname'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SetQQProfile extends BaseAction<Payload, any | null> {
actionName = ActionName.SetQQProfile;
PayloadSchema = SchemaData;
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const self = this.CoreContext.selfInfo;
const OldProfile = await NTQQUserApi.getUserDetailInfo(self.uid);
const ret = await NTQQUserApi.modifySelfProfile({
nick: payload.nickname,
longNick: payload?.personal_note ?? OldProfile?.longNick!,
sex: parseInt(payload?.sex ? payload?.sex.toString() : OldProfile?.sex!.toString()),
birthday: { birthday_year: OldProfile?.birthday_year!.toString(), birthday_month: OldProfile?.birthday_month!.toString(), birthday_day: OldProfile?.birthday_day!.toString() },
location: undefined,
});
return ret;
}
}

View File

@@ -21,11 +21,8 @@ class GetGroupInfo extends BaseAction<Payload, OB11Group> {
async _handle(payload: Payload) {
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const group = (await NTQQGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString());
if (group) {
return OB11Constructor.group(group);
} else {
throw `${payload.group_id}不存在`;
}
if (!group) throw `${payload.group_id}不存在`;
return OB11Constructor.group(group);
}
}

View File

@@ -20,7 +20,7 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
async _handle(payload: Payload) {
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const groupList: Group[] = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload.no_cache === 'true');
const groupList: Group[] = await NTQQGroupApi.getGroups(typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache);
return OB11Constructor.groups(groupList);
}
}

View File

@@ -3,6 +3,7 @@ import { OB11Constructor } from '../../helper/data';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { GroupMember } from '@/core';
const SchemaData = {
type: 'object',
@@ -23,56 +24,25 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi;
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const NTQQWebApi = this.CoreContext.apis.WebApi;
const isNocache = payload.no_cache == true || payload.no_cache === 'true';
const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache;
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) {
throw (`Uin2Uid Error ${payload.user_id}不存在`);
}
const member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache);
if (!member) {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
}
try {
const info = (await NTQQUserApi.getUserDetailInfo(member.uid));
this.CoreContext.context.logger.logDebug('群成员详细信息结果', info);
Object.assign(member, info);
} catch (e) {
this.CoreContext.context.logger.logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
if (!uid) throw (`Uin2Uid Error ${payload.user_id}不存在`);
const [member, info] = await Promise.allSettled([
NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache),
NTQQUserApi.getUserDetailInfo(uid),
]);
if (member.status !== 'fulfilled') throw (`群(${payload.group_id})成员${payload.user_id}不存在 ${member.reason}`);
if (info.status === 'fulfilled') {
this.CoreContext.context.logger.logDebug("群成员详细信息结果", info.value);
Object.assign(member, info.value);
} else {
this.CoreContext.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息 ${info.reason}`);
}
const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
if (!this.CoreContext.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
const SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), this.CoreContext.selfInfo.uid, isNocache);
let isPrivilege = false;
if (SelfInfoInGroup) {
isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4;
}
if (isPrivilege) {
const webGroupMembers = await NTQQWebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.join_time = webGroupMembers[i]?.join_time;
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLatestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
}
} else {
// Mlikiowa V2.0.20 Refactor Todo
// retMember.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
// retMember.join_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
}
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member.value as GroupMember);
const Member = await this.CoreContext.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id);
retMember.last_sent_time = parseInt(Member?.lastSpeakTime || date.toString());
retMember.join_time = parseInt(Member?.joinTime || date.toString());
return retMember;
}
}

View File

@@ -22,23 +22,16 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
async _handle(payload: Payload) {
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const NTQQWebApi = this.CoreContext.apis.WebApi;
const isNocache = payload.no_cache == true || payload.no_cache === 'true';
// const GroupList = await NTQQGroupApi.getGroups(isNocache);
// const group = GroupList.find(item => item.groupCode == payload.group_id);
// if (!group) {
// throw (`群${payload.group_id}不存在`);
// }
const groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
const groupMembersArr = Array.from(groupMembers.values());
const groupMembersUids = groupMembersArr.map(e => e.uid);
let _groupMembers = groupMembersArr.map(item => {
return OB11Constructor.groupMember(payload.group_id.toString(), item);
});
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>();
// 转为Map 方便索引
const GroupMemberUids: string[] = [];
const date = Math.round(Date.now() / 1000);
for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date;
@@ -46,59 +39,31 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
}
if (!this.CoreContext.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
const selfRole = groupMembers.get(this.CoreContext.selfInfo.uid)?.role;
const isPrivilege = selfRole === 3 || selfRole === 4;
const selfRole = groupMembers.get(this.CoreContext.selfInfo.uid)?.role;
const isPrivilege = selfRole === 3 || selfRole === 4;
if (isPrivilege) {
const webGroupMembers = await NTQQWebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
}
_groupMembers.forEach(item => {
item.last_sent_time = date;
item.join_time = date;
});
if (isPrivilege) {
const webGroupMembers = await NTQQWebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue;
}
} else {
if (isNocache) {
const DateMap = await NTQQGroupApi.getGroupMemberLatestSendTimeCache(payload.group_id.toString(), groupMembersUids);//开始从本地拉取
for (const DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
//join_time 有基础数据兜底
}
}
} else {
_groupMembers.forEach(item => {
item.last_sent_time = date;
item.join_time = date;
});
const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
}
}
} else {
// Mlikiowa V2.0.20 Refactor Todo
// _groupMembers.forEach(async item => {
// item.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.lastSpeakTime || date.toString());
// item.join_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.joinTime || date.toString());
// });
}
// 还原索引到Array 一同返回
// let retData: any[] = [];
// for (let retMem of MemberMap.values()) {
// retMem.level = TypeConvert.toString(retMem.level) as any;
// retData.push(retMem)
// }
// _groupMembers = Array.from(retData);
_groupMembers = Array.from(MemberMap.values());
return _groupMembers;
}

View File

@@ -24,22 +24,22 @@ export class GetGroupSystemMsg extends BaseAction<void, any> {
if (SSNotify.type == 1) {
retData.InvitedRequest.push({
request_id: SSNotify.seq,
invitor_uin: await NTQQUserApi.getUinByUid(SSNotify.user1?.uid),
invitor_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
invitor_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === 1 ? false : true,
actor: await NTQQUserApi.getUinByUid(SSNotify.user2?.uid) || 0,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
} else if (SSNotify.type == 7) {
retData.join_requests.push({
request_id: SSNotify.seq,
requester_uin: await NTQQUserApi.getUinByUid(SSNotify.user1?.uid),
requester_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
requester_nick: SSNotify.user1?.nickName,
group_id: SSNotify.group?.groupCode,
group_name: SSNotify.group?.groupName,
checked: SSNotify.status === 1 ? false : true,
actor: await NTQQUserApi.getUinByUid(SSNotify.user2?.uid) || 0,
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
});
}
}

View File

@@ -8,7 +8,7 @@ const SchemaData = {
properties: {
group_id: { type: ['number', 'string'] },
user_id: { type: ['number', 'string'] },
enable: { type: 'boolean' },
enable: { type: ['boolean', 'string'] },
},
required: ['group_id', 'user_id'],
} as const satisfies JSONSchema;
@@ -20,11 +20,12 @@ export default class SetGroupAdmin extends BaseAction<Payload, null> {
PayloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
const enable = typeof payload.enable === 'string' ? payload.enable === 'true' : !!payload.enable;
const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const NTQQUserApi = this.CoreContext.apis.UserApi;
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('get Uid Error');
await NTQQGroupApi.setMemberRole(payload.group_id.toString(), uid, payload.enable ? GroupMemberRole.admin : GroupMemberRole.normal);
await NTQQGroupApi.setMemberRole(payload.group_id.toString(), uid, enable ? GroupMemberRole.admin : GroupMemberRole.normal);
return null;
}
}

View File

@@ -6,7 +6,7 @@ const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
is_dismiss: { type: 'boolean' },
is_dismiss: { type: ['boolean', 'string'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;

View File

@@ -61,7 +61,7 @@ import { TranslateEnWordToZn } from './extends/TranslateEnWordToZn';
import { SetGroupFileFolder } from './file/SetGroupFileFolder';
import { DelGroupFile } from './file/DelGroupFile';
import { DelGroupFileFolder } from './file/DelGroupFileFolder';
import { SetSelfProfile } from './extends/SetSelfProfile';
import { SetQQProfile } from './go-cqhttp/SetQQProfile'
import { ShareGroupEx, SharePeer } from './extends/ShareContact';
import { CreateCollection } from './extends/CreateCollection';
import { SetLongNick } from './extends/SetLongNick';
@@ -69,7 +69,7 @@ import DelEssenceMsg from './group/DelEssenceMsg';
import SetEssenceMsg from './group/SetEssenceMsg';
import GetRecentContact from './user/GetRecentContact';
import { GetProfileLike } from './extends/GetProfileLike';
import SetGroupHeader from './extends/SetGroupHeader';
import SetGroupPortrait from './go-cqhttp/SetGroupPortrait';
import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile';
import { FetchEmojiLike } from './extends/FetchEmojiLike';
@@ -78,6 +78,8 @@ import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import GetGuildProfile from './guild/GetGuildProfile';
import SetModelShow from './go-cqhttp/SetModelShow';
import { SetInputStatus } from './extends/SetInputStatus';
import { GetCSRF } from './system/GetCSRF';
export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -85,7 +87,7 @@ export function createActionMap(onebotContext: NapCatOneBot11Adapter, coreContex
const actionHandlers = [
new FetchEmojiLike(onebotContext, coreContext),
new GetFile(onebotContext, coreContext),
new SetSelfProfile(onebotContext, coreContext),
new SetQQProfile(onebotContext, coreContext),
new ShareGroupEx(onebotContext, coreContext),
new SharePeer(onebotContext, coreContext),
new CreateCollection(onebotContext, coreContext),
@@ -160,11 +162,13 @@ export function createActionMap(onebotContext: NapCatOneBot11Adapter, coreContex
new GetRecentContact(onebotContext, coreContext),
new MarkAllMsgAsRead(onebotContext, coreContext),
new GetProfileLike(onebotContext, coreContext),
new SetGroupHeader(onebotContext, coreContext),
new SetGroupPortrait(onebotContext, coreContext),
new FetchCustomFace(onebotContext, coreContext),
new GoCQHTTPUploadPrivateFile(onebotContext, coreContext),
new GetGuildProfile(onebotContext, coreContext),
new SetModelShow(onebotContext, coreContext),
new SetInputStatus(onebotContext, coreContext),
new GetCSRF(onebotContext, coreContext),
];
const actionMap = new Map();
for (const action of actionHandlers) {

View File

@@ -7,7 +7,7 @@ import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = {
type: 'object',
properties: {
message_id: { type: 'number' },
message_id: { type: ['number', 'string'] },
group_id: { type: ['number', 'string'] },
user_id: { type: ['number', 'string'] },
},
@@ -31,7 +31,7 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
async _handle(payload: Payload): Promise<null> {
const NTQQMsgApi = this.CoreContext.apis.MsgApi;
const msg = MessageUnique.getMsgIdAndPeerByShortId(payload.message_id);
const msg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if (!msg) {
throw new Error(`无法找到消息${payload.message_id}`);
}

View File

@@ -56,7 +56,7 @@ const _handlers: {
if (atQQ === 'all') return SendMsgElementConstructor.at(coreContext, atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member
// Mlikiowa V2.0.19 Refactor Todo
// Mlikiowa V2.1.0 Refactor Todo
const uid = await coreContext.apis.UserApi.getUidByUinV2(`${atQQ}`);
if (!uid) throw new Error('Get Uid Error');
return SendMsgElementConstructor.at(coreContext, atQQ, uid, AtType.atUser, '');
@@ -161,7 +161,7 @@ const _handlers: {
} else {
postData = data;
}
// Mlikiowa V2.0.19 Refactor Todo
// Mlikiowa V2.1.0 Refactor Todo
const signUrl = obContext.configLoader.configData.musicSignUrl;
if (!signUrl) {
if (data.type === 'qq') {

View File

@@ -88,11 +88,9 @@ async function createContext(coreContext: NapCatCore, payload: OB11PostSendMsg,
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
const NTQQGroupApi = coreContext.apis.GroupApi;
const NTQQFriendApi = coreContext.apis.FriendApi;
const NTQQUserApi = coreContext.apis.UserApi;
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
const group = (await NTQQGroupApi.getGroups()).find(e => e.groupCode == payload.group_id?.toString());
return {
chatType: ChatType.group,
peerUid: payload.group_id.toString(),
@@ -134,26 +132,22 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
};
}
// if (payload.message_type !== 'private' && payload.group_id && !(await getGroup(payload.group_id))) {
// return { valid: false, message: `群${payload.group_id}不存在` };
// }
if (payload.user_id && payload.message_type !== 'group') {
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
const isBuddy = await NTQQFriendApi.isBuddy(uid!);
// 此处有问题
if (!isBuddy) {
//return { valid: false, message: '异常消息' };
}
if (!isBuddy) {}
}
return { valid: true };
}
async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> {
if (payload.message_type === 'group') this.contextMode = ContextMode.Group;
if (payload.message_type === 'private') this.contextMode = ContextMode.Private;
const peer = await createContext(this.CoreContext, payload, this.contextMode);
const messages = normalize(
payload.message,
payload.auto_escape === true || payload.auto_escape === 'true',
typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape
);
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {

View File

@@ -0,0 +1,12 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
export class GetCSRF extends BaseAction<any, any> {
actionName = ActionName.GetCSRF;
async _handle(payload: any) {
return {
token: "",
};
}
}

View File

@@ -91,7 +91,7 @@ export enum ActionName {
GetOnlineClient = 'get_online_clients',
OCRImage = 'ocr_image',
IOCRImage = '.ocr_image',
SetSelfProfile = 'set_self_profile',
SetQQProfile = 'set_qq_profile',
CreateCollection = 'create_collection',
GetCollectionList = 'get_collection_list',
SetLongNick = 'set_self_longnick',
@@ -100,11 +100,13 @@ export enum ActionName {
GetRecentContact = 'get_recent_contact',
_MarkAllMsgAsRead = '_mark_all_as_read',
GetProfileLike = 'get_profile_like',
SetGroupHeader = 'set_group_head',
SetGroupPortrait = 'set_group_portrait',
FetchCustomFace = 'fetch_custom_face',
GOCQHTTP_UploadPrivateFile = 'upload_private_file',
TestApi01 = 'test_api_01',
FetchEmojiLike = 'fetch_emoji_like',
GetGuildProfile = "get_guild_service_profile",
SetModelShow = "_set_model_show"
SetModelShow = "_set_model_show",
SetInputStatus = "set_input_status",
GetCSRF = "get_csrf_token",
}

View File

@@ -20,6 +20,6 @@ export default class GetFriendList extends BaseAction<Payload, OB11User[]> {
async _handle(payload: Payload) {
//全新逻辑
const NTQQFriendApi = this.CoreContext.apis.FriendApi;
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2(payload?.no_cache === true || payload?.no_cache === 'true'));
return OB11Constructor.friendsV2(await NTQQFriendApi.getBuddyV2(typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache));
}
}

View File

@@ -6,14 +6,14 @@ export class OB11InputStatusEvent extends OB11BaseNoticeEvent {
notice_type = 'notify';
sub_type = 'input_status';
status_text = '对方正在输入...';
eventType = 1;
event_type = 1;
user_id = 0;
group_id = 0;
constructor(core: NapCatCore, user_id: number, eventType: number, status_text: string) {
super(core);
this.user_id = user_id;
this.eventType = eventType;
this.event_type = eventType;
this.status_text = status_text;
}
}

View File

@@ -15,9 +15,9 @@ import {
FaceIndex,
Friend,
FriendV2,
GrayTipElementSubType,
Group,
GroupMember,
NTGrayTipElementSubTypeV2,
Peer,
RawMessage,
SelfInfo,
@@ -30,6 +30,7 @@ import { EventType } from '../event/OB11BaseEvent';
import { encodeCQCode } from './cqcode';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { OB11GroupBanEvent } from '../event/notice/OB11GroupBanEvent';
import { OB11GroupCardEvent } from '../event/notice/OB11GroupCardEvent';
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
import { OB11GroupNoticeEvent } from '../event/notice/OB11GroupNoticeEvent';
import { calcQQLevel, sleep, UUIDConverter } from '@/common/utils/helper';
@@ -119,7 +120,7 @@ export class OB11Constructor {
const { atNtUid, content } = element.textElement;
let atQQ = element.textElement.atUid;
if (!atQQ || atQQ === '0') {
atQQ = await NTQQUserApi.getUinByUid(atNtUid);
atQQ = await NTQQUserApi.getUinByUidV2(atNtUid);
}
if (atQQ) {
qq = atQQ as `${number}`;
@@ -161,7 +162,7 @@ export class OB11Constructor {
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, element.replyElement.replayMsgSeq, 1, true, true)).msgList[0];
}, element.replyElement.replayMsgSeq, 1, true, true)).msgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
}
@@ -169,7 +170,11 @@ export class OB11Constructor {
//合并消息内侧 消息具体定位不到
}
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
throw new Error('回复消息消息验证失败');
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, records.msgSeq)).msgList;
if (replyMsgList.length < 1) {
throw new Error('回复消息消息验证失败');
}
replyMsg = replyMsgList.filter(e => e.msgSeq == records.msgSeq).sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
}
message_data['data']['id'] = MessageUnique.createMsg({
peerUid: msg.peerUid,
@@ -286,13 +291,13 @@ export class OB11Constructor {
chatType: msg.chatType,
guildId: '',
},
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
element.pttElement.fileSize || '0',
element.pttElement.fileUuid || '',
msg.msgId,
msg.msgSeq,
msg.senderUid,
element.elementId,
element.elementType.toString(),
element.pttElement.fileSize || '0',
element.pttElement.fileUuid || '',
);
//以uuid作为文件名
} else if (element.arkElement) {
@@ -377,7 +382,7 @@ export class OB11Constructor {
}
for (const element of msg.elements) {
if (element.grayTipElement) {
if (element.grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(element.grayTipElement.jsonGrayTipElement.jsonStr);
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
@@ -398,7 +403,7 @@ export class OB11Constructor {
}
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
}
if (element.grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
//好友添加成功事件
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
return new OB11FriendAddNoticeEvent(core, parseInt(msg.peerUin));
@@ -417,16 +422,15 @@ export class OB11Constructor {
return;
}
//log("group msg", msg);
// Mlikiowa V2.0.20 Refactor Todo
// if (msg.senderUin && msg.senderUin !== '0') {
// const member = await getGroupMember(msg.peerUid, msg.senderUin);
// if (member && member.cardName !== msg.sendMemberName) {
// const newCardName = msg.sendMemberName || '';
// const event = new OB11GroupCardEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
// member.cardName = newCardName;
// return event;
// }
// }
if (msg.senderUin && msg.senderUin !== '0') {
const member = await NTQQGroupApi.getGroupMember(msg.peerUid, msg.senderUin);
if (member && member.cardName !== msg.sendMemberName) {
const newCardName = msg.sendMemberName || '';
const event = new OB11GroupCardEvent(core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
member.cardName = newCardName;
return event;
}
}
for (const element of msg.elements) {
const grayTipElement = element.grayTipElement;
@@ -530,18 +534,18 @@ export class OB11Constructor {
const senderUin = emojiLikeData.gtip.qq.jp;
const msgSeq = emojiLikeData.gtip.url.msgseq;
const emojiId = emojiLikeData.gtip.face.id;
const replyMsgList = (await NTQQMsgApi.getMsgsBySeqAndCount({
const peer = {
chatType: ChatType.group,
guildId: '',
peerUid: msg.peerUid,
}, msgSeq, 1, true, true)).msgList;
peerUid: msg.peerUid
}
const replyMsgList = (await NTQQMsgApi.getMsgExBySeq(peer, msgSeq)).msgList;
if (replyMsgList.length < 1) {
return;
}
const replyMsg = replyMsgList[0];
console.log('表情回应消息', msgSeq, ' 结算ID', replyMsg.msgId);
const replyMsg = replyMsgList.filter(e => e.msgSeq == msgSeq).sort((a, b) => parseInt(a.msgTime) - parseInt(b.msgTime))[0];
//console.log("表情回应消息长度检测", msgSeq, replyMsg.elements);
if (!replyMsg) throw new Error('找不到回应消息');
return new OB11GroupMsgEmojiLikeEvent(
core,
parseInt(msg.peerUid),
@@ -556,7 +560,7 @@ export class OB11Constructor {
logger.logError('解析表情回应消息失败', e.stack);
}
}
if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
logger.logDebug('收到新人被邀请进群消息', grayTipElement);
const xmlElement = grayTipElement.xmlElement;
if (xmlElement?.content) {
@@ -582,7 +586,7 @@ export class OB11Constructor {
}
}
//代码歧义 GrayTipElementSubType.MEMBER_NEW_TITLE
else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
else if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr);
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型
@@ -709,7 +713,7 @@ export class OB11Constructor {
nickname: member.nick,
card: member.cardName,
sex: OB11Constructor.sex(member.sex!),
age: 0,
age: member.age ?? 0,
area: '',
level: '0',
qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,

View File

@@ -18,10 +18,11 @@ import {
SendVideoElement,
viedo_type,
} from '@/core';
import * as fsnormal from 'node:fs';
import { promises as fs } from 'node:fs';
import ffmpeg from 'fluent-ffmpeg';
import { calculateFileMD5, isGIF } from '@/common/utils/file';
import { getVideoInfo } from '@/common/utils/video';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/utils/video';
import { encodeSilk } from '@/common/utils/audio';
import faceConfig from '@/core/external/face_config.json';
import * as pathLib from 'node:path';
@@ -140,11 +141,7 @@ export class SendMsgElementConstructor {
}
static async video(coreContext: NapCatCore, filePath: string, fileName: string = '', diyThumbPath: string = '', videotype: viedo_type = viedo_type.VIDEO_FORMAT_MP4): Promise<SendVideoElement> {
const NTQQGroupApi = coreContext.apis.GroupApi;
const NTQQUserApi = coreContext.apis.UserApi;
const NTQQFileApi = coreContext.apis.FileApi;
const NTQQMsgApi = coreContext.apis.MsgApi;
const NTQQFriendApi = coreContext.apis.FriendApi;
const logger = coreContext.context.logger;
const { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) {
@@ -179,7 +176,8 @@ export class SendMsgElementConstructor {
resolve(thumbPath);
}).catch(reject);
} else {
resolve(undefined);
fsnormal.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
resolve(thumbPath);
}
})
.screenshots({
@@ -224,6 +222,21 @@ export class SendMsgElementConstructor {
// sourceVideoCodecFormat: 2
},
};
// "fileElement": {
// "fileMd5": "",
// "fileName": "1.mp4",
// "filePath": "C:\\Users\\nanae\\OneDrive\\Desktop\\1.mp4",
// "fileSize": "1847007",
// "picHeight": 1280,
// "picWidth": 720,
// "picThumbPath": {},
// "file10MMd5": "",
// "fileSha": "",
// "fileSha3": "",
// "fileUuid": "",
// "fileSubId": "",
// "thumbFileSize": 750
// }
return element;
}

View File

@@ -8,6 +8,8 @@ import {
MsgListener,
NapCatCore,
RawMessage,
SendStatusType,
GroupMemberRole,
} from '@/core';
import { OB11Config, OB11ConfigLoader } from '@/onebot/helper/config';
import { OneBotApiContextType } from '@/onebot/types';
@@ -250,7 +252,7 @@ export class NapCatOneBot11Adapter {
.catch(e => this.context.logger.logError('处理消息失败', e));
for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
if (msg.sendStatus == 2 && !msgIdSend.get(msg.msgId)) {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && !msgIdSend.get(msg.msgId)) {
msgIdSend.put(msg.msgId, true);
// 完成后再post
OB11Constructor.message(this.core, this, msg)
@@ -273,7 +275,7 @@ export class NapCatOneBot11Adapter {
};
this.context.session.getMsgService().addKernelMsgListener(
new this.context.wrapper.NodeIKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)),
proxiedListenerOf(msgListener, this.context.logger) as any
);
}
@@ -290,8 +292,8 @@ export class NapCatOneBot11Adapter {
await this.networkManager.emitEvent(new OB11FriendRequestEvent(
this.core,
parseInt(requesterUin!),
req.friendUid + '|' + req.reqTime,
req.extWords,
req.friendUid + '|' + req.reqTime,
));
} catch (e) {
this.context.logger.logDebug('获取加好友者QQ号失败', e);
@@ -300,7 +302,7 @@ export class NapCatOneBot11Adapter {
};
this.context.session.getBuddyService().addKernelBuddyListener(
new this.context.wrapper.NodeIKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger)),
proxiedListenerOf(buddyListener, this.context.logger) as any
);
}
@@ -325,11 +327,6 @@ export class NapCatOneBot11Adapter {
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type;
this.context.logger.logDebug('收到群通知', notify);
// let member2: GroupMember;
// if (notify.user2.uid) {
// member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
// }
if ([
GroupNotifyTypes.ADMIN_SET,
GroupNotifyTypes.ADMIN_UNSET,
@@ -400,12 +397,12 @@ export class NapCatOneBot11Adapter {
} catch (e) {
this.context.logger.logError('获取加群人QQ号失败 Uid:', notify.user1.uid, e);
}
} else if (notify.type == GroupNotifyTypes.INVITE_ME) {
} else if (notify.type == GroupNotifyTypes.INVITE_ME && notify.status == 1) {
this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
const groupInviteEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),
parseInt(notify.user1.uid),
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid)),
'invite',
notify.postscript,
flag,
@@ -417,8 +414,31 @@ export class NapCatOneBot11Adapter {
}
};
groupListener.onMemberInfoChange = async (groupCode, changeType, members) => {
//this.context.logger.logDebug('收到群成员信息变动通知', groupCode, changeType);
if (changeType === 0) {
const existMembers = this.core.apis.GroupApi.groupMemberCache.get(groupCode);
if (!existMembers) return;
members.forEach((member) => {
const existMember = existMembers.get(member.uid);
if (!existMember?.isChangeRole) return;
this.context.logger.logDebug('变动管理员获取成功');
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
this.core,
parseInt(groupCode),
parseInt(member.uin),
member.role === GroupMemberRole.admin ? 'set' : 'unset',
);
this.networkManager.emitEvent(groupAdminNoticeEvent)
.catch(e => this.context.logger.logError('处理群管理员变动失败', e));
existMember.isChangeRole = false;
this.context.logger.logDebug('群管理员变动处理完毕');
});
}
};
this.context.session.getGroupService().addKernelGroupListener(
new this.context.wrapper.NodeIKernelGroupListener(proxiedListenerOf(groupListener, this.context.logger)),
proxiedListenerOf(groupListener, this.context.logger)
);
}

View File

@@ -44,14 +44,16 @@ export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
resJson = await res.json();
//logDebug('新消息事件HTTP上报返回快速操作: ', JSON.stringify(resJson));
} catch (e) {
this.logger.logDebug('新消息事件HTTP上报没有返回快速操作不需要处理');
this.logger.logDebug('[OneBot] [Http Client] 新消息事件HTTP上报没有返回快速操作不需要处理');
return;
}
try {
handleQuickOperation(this.coreContext, this.obContext, event as QuickActionEvent, resJson).then().catch(this.logger.logError);
} catch (e: any) {
this.logger.logError('新消息事件HTTP上报返回快速操作失败', e);
this.logger.logError('[OneBot] [Http Client] 新消息事件HTTP上报返回快速操作失败', e);
}
}).catch((e) => {
this.logger.logError('[OneBot] [Http Client] 新消息事件HTTP上报失败', e);
});
}

View File

@@ -71,6 +71,8 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
this.connection = new WebSocket(this.url, {
maxPayload: 1024 * 1024 * 1024,
handshakeTimeout: 2000,
perMessageDeflate: false,
headers: {
'X-Self-ID': this.coreContext.selfInfo.uin,
'Authorization': `Bearer ${this.token}`,
@@ -79,6 +81,12 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
},
});
this.connection.on('ping', () => {
this.connection?.pong();
});
this.connection.on('pong', () => {
//this.logger.logDebug('[OneBot] [WebSocket Client] 收到pong');
});
this.connection.on('open', () => {
try {
this.connectEvent(this.coreContext);
@@ -128,6 +136,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
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)
@@ -135,6 +144,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
const packet = Object.assign({}, retdata);
this.checkStateAndReply<any>(packet);
} catch (e) {
this.logger.logError('[OneBot] [WebSocket Client] 发生错误', e);
this.checkStateAndReply<any>(OB11Response.error('不支持的api ' + receiveData.action, 1404, echo));
}
}

View File

@@ -4,6 +4,7 @@ import http from 'http';
import { NapCatCore } from '@/core';
import { OB11Response } from '../action/OB11Response';
import { ActionMap } from '@/onebot/action';
import cors from 'cors';
export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
private app: Express | undefined;
@@ -43,13 +44,24 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
this.server?.close();
this.app = undefined;
}
private initializeServer() {
this.app = express();
this.server = http.createServer(this.app);
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: false }));
this.app.use(cors());
this.app.use(express.urlencoded({ extended: true, limit: '5000mb' }));
this.app.use((req, res, next) => {
// 兼容处理没有带content-type的请求
req.headers['content-type'] = 'application/json';
const originalJson = express.json({ limit: '5000mb' });
originalJson(req, res, (err) => {
if (err) {
return res.status(400).send('Invalid JSON');
}
next();
});
});
this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
this.app.use((req, res) => this.handleRequest(req, res));

View File

@@ -36,7 +36,11 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
this.logger = coreContext.context.logger;
this.heartbeatInterval = heartbeatInterval;
this.wsServer = new WebSocketServer({ port: port, host: ip, maxPayload: 1024 * 1024 * 1024, });
this.wsServer = new WebSocketServer({
port: port,
host: ip,
maxPayload: 1024 * 1024 * 1024,
});
const core = coreContext;
this.wsServer.on('connection', async (wsClient, wsReq) => {
if (!this.isOpen) {
@@ -50,6 +54,12 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
wsClient.on('message', (message) => {
this.handleMessage(wsClient, message).then().catch(this.logger.logError);
});
wsClient.on('ping', () => {
wsClient.pong();
});
wsClient.on('pong', () => {
//this.logger.logDebug('[OneBot] [WebSocket Server] Pong received');
});
wsClient.once('close', () => {
this.wsClientsMutex.runExclusive(async () => {
const index = this.wsClients.indexOf(wsClient);
@@ -145,6 +155,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
//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 || '');

View File

@@ -43,12 +43,12 @@ export async function NCoreInitShell() {
// from constructor
const engine = new wrapper.NodeIQQNTWrapperEngine();
const util = new wrapper.NodeQQNTWrapperUtil();
//const util = wrapper.NodeQQNTWrapperUtil.get();
const loginService = new wrapper.NodeIKernelLoginService();
const session = new wrapper.NodeIQQNTWrapperSession();
// from get dataPath
let dataPath = util.getNTUserDataInfoConfig();
let dataPath = wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig();
if (!dataPath) {
dataPath = path.resolve(os.homedir(), './.config/QQ');
fs.mkdirSync(dataPath, { recursive: true });
@@ -70,7 +70,7 @@ export async function NCoreInitShell() {
},
thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 },
},
new wrapper.NodeIGlobalAdapter(new GlobalAdapter()),
new GlobalAdapter() as any,
);
loginService.initConfig({
machineId: '',
@@ -140,8 +140,7 @@ export async function NCoreInitShell() {
logger.logError('[Core] [Login] Login Error , ErrInfo: ', args);
};
loginService.addKernelLoginListener(new wrapper.NodeIKernelLoginListener(
proxiedListenerOf(loginListener, logger)));
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
// 实现WebUi快速登录
loginService.getLoginList().then((res) => {
@@ -188,11 +187,10 @@ export async function NCoreInitShell() {
} else {
logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式');
if (historyLoginList.length > 0) {
logger.log(`可用于快速登录的 QQ\n${
historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n')
}`);
logger.log(`可用于快速登录的 QQ\n${historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n')
}`);
}
loginService.getQRCodePicture();
}
@@ -220,9 +218,9 @@ export async function NCoreInitShell() {
};
session.init(
sessionConfig,
new wrapper.NodeIDependsAdapter(new DependsAdapter()),
new wrapper.NodeIDispatcherAdapter(new DispatcherAdapter()),
new wrapper.NodeIKernelSessionListener(sessionListener),
new DependsAdapter() as any,
new DispatcherAdapter() as any,
sessionListener as any,
);
try {
session.startNT(0);

View File

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