mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0b8bf739e9 | ||
![]() |
0222664db8 | ||
![]() |
a88792e452 | ||
![]() |
ad45400742 | ||
![]() |
53e5ba03be | ||
![]() |
b587d6b91d | ||
![]() |
5e750d4ee9 | ||
![]() |
50fb32f81c |
16
README.md
16
README.md
@@ -8,7 +8,7 @@
|
|||||||
## 欢迎回家
|
## 欢迎回家
|
||||||
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||||
|
|
||||||
## 碎碎叨叨
|
## 特性介绍
|
||||||
- [x] **安装简单**:就算是笨蛋也能使用
|
- [x] **安装简单**:就算是笨蛋也能使用
|
||||||
- [x] **性能友好**:就算是低内存也能使用
|
- [x] **性能友好**:就算是低内存也能使用
|
||||||
- [x] **接口丰富**:就算是没有也能使用
|
- [x] **接口丰富**:就算是没有也能使用
|
||||||
@@ -26,19 +26,19 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
|
|
||||||
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
||||||
|
|
||||||
|
[Github.IO](https://napneko.github.io/)
|
||||||
|
|
||||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||||
|
|
||||||
[Server.China](https://napneko.com/)
|
[Server.China](https://napneko.com/)
|
||||||
|
|
||||||
[Server.Other](https://napcat.cyou/)
|
[Server.Other](https://napcat.cyou/)
|
||||||
|
|
||||||
[Github.IO](https://napneko.github.io/)
|
|
||||||
## 回家旅途
|
## 回家旅途
|
||||||
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS)
|
||||||
|
|
||||||
## 感谢他们
|
## 感谢他们
|
||||||
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot)
|
|
||||||
|
|
||||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||||
|
|
||||||
感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi
|
感谢 Tencent Tdesign / Vue3 强力驱动 NapCat.WebUi
|
||||||
@@ -46,6 +46,14 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 延缓Native模块与NapCat对新版QQ适配
|
||||||
|
为未来持续与高效的使用Native模块 模块代码转为完全非Git仓库的本地保存源码 并进行相关重构
|
||||||
|
|
||||||
|
同时为了保证稳定 NapCat 本体通常会在3 Week+的周期进行新版本适配
|
||||||
|
|
||||||
|
因此此时推荐使用release指定版本
|
||||||
|
|
||||||
## 开源附加
|
## 开源附加
|
||||||
|
|
||||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**
|
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.1.3",
|
"version": "4.1.5",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.1.3",
|
"version": "4.1.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework",
|
"build:framework": "npm run build:webui && vite build --mode framework",
|
||||||
"build:shell": "npm run build:webui && vite build --mode shell",
|
"build:shell": "npm run build:webui && vite build --mode shell",
|
||||||
|
@@ -234,25 +234,33 @@ export class NTEventWrapper {
|
|||||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||||
this.createListenerFunction(ListenerMainName);
|
this.createListenerFunction(ListenerMainName);
|
||||||
|
|
||||||
this.createEventFunction(serviceAndMethod)!(...(args))
|
let eventResult = this.createEventFunction(serviceAndMethod)!(...(args));
|
||||||
.then((eventResult: any) => {
|
|
||||||
retEvent = eventResult;
|
const eventRetHandle = (eventData: any) => {
|
||||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
retEvent = eventData;
|
||||||
clearTimeout(timeoutRef);
|
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||||
reject(
|
clearTimeout(timeoutRef);
|
||||||
new Error(
|
reject(
|
||||||
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
new Error(
|
||||||
serviceAndMethod +
|
'EventChecker Failed: NTEvent serviceAndMethod:' +
|
||||||
' ListenerName:' +
|
serviceAndMethod +
|
||||||
listenerAndMethod +
|
' ListenerName:' +
|
||||||
' EventRet:\n' +
|
listenerAndMethod +
|
||||||
JSON.stringify(retEvent, null, 4) +
|
' EventRet:\n' +
|
||||||
'\n',
|
JSON.stringify(retEvent, null, 4) +
|
||||||
),
|
'\n',
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventResult instanceof Promise) {
|
||||||
|
eventResult.then((eventResult: any) => {
|
||||||
|
eventRetHandle(eventResult);
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
|
} else {
|
||||||
|
eventRetHandle(eventResult);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.1.3';
|
export const napCatVersion = '4.1.5';
|
||||||
|
@@ -8,6 +8,9 @@ import {
|
|||||||
WebHonorType,
|
WebHonorType,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { NapCatCore } from '..';
|
import { NapCatCore } from '..';
|
||||||
|
import { createReadStream, readFileSync, statSync } from 'node:fs';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
import { basename } from 'node:path';
|
||||||
|
|
||||||
export class NTQQWebApi {
|
export class NTQQWebApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
@@ -303,4 +306,110 @@ export class NTQQWebApi {
|
|||||||
}
|
}
|
||||||
return (hash & 0x7FFFFFFF).toString();
|
return (hash & 0x7FFFFFFF).toString();
|
||||||
}
|
}
|
||||||
|
async createQunAlbumSession(gc: string, sAlbumID: string, sAlbumName: string, path: string, skey: string, pskey: string, uin: string) {
|
||||||
|
const img = readFileSync(path);
|
||||||
|
const img_md5 = createHash('md5').update(img).digest('hex');
|
||||||
|
const img_size = img.length;
|
||||||
|
const img_name = basename(path);
|
||||||
|
const time = Math.floor(Date.now() / 1000);
|
||||||
|
const GTK = this.getBknFromSKey(pskey);
|
||||||
|
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
|
||||||
|
const body = {
|
||||||
|
control_req: [{
|
||||||
|
uin: uin,
|
||||||
|
token: {
|
||||||
|
type: 4,
|
||||||
|
data: pskey,
|
||||||
|
appid: 5
|
||||||
|
},
|
||||||
|
appid: "qun",
|
||||||
|
checksum: img_md5,
|
||||||
|
check_type: 0,
|
||||||
|
file_len: img_size,
|
||||||
|
env: {
|
||||||
|
refer: "qzone",
|
||||||
|
deviceInfo: "h5"
|
||||||
|
},
|
||||||
|
model: 0,
|
||||||
|
biz_req: {
|
||||||
|
sPicTitle: img_name,
|
||||||
|
sPicDesc: "",
|
||||||
|
sAlbumName: sAlbumName,
|
||||||
|
sAlbumID: sAlbumID,
|
||||||
|
iAlbumTypeID: 0,
|
||||||
|
iBitmap: 0,
|
||||||
|
iUploadType: 0,
|
||||||
|
iUpPicType: 0,
|
||||||
|
iBatchID: time,
|
||||||
|
sPicPath: "",
|
||||||
|
iPicWidth: 0,
|
||||||
|
iPicHight: 0,
|
||||||
|
iWaterType: 0,
|
||||||
|
iDistinctUse: 0,
|
||||||
|
iNeedFeeds: 1,
|
||||||
|
iUploadTime: time,
|
||||||
|
mapExt: {
|
||||||
|
appid: "qun",
|
||||||
|
userid: gc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
session: "",
|
||||||
|
asy_upload: 0,
|
||||||
|
cmd: "FileUpload"
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileBatchControl/${img_md5}?g_tk=${GTK}`;
|
||||||
|
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
|
||||||
|
"Cookie": cookie,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
});
|
||||||
|
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadQunAlbumSlice(path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) {
|
||||||
|
const img_size = statSync(path).size;
|
||||||
|
const img_name = basename(path);
|
||||||
|
let seq = 0;
|
||||||
|
let offset = 0;
|
||||||
|
const GTK = this.getBknFromSKey(pskey);
|
||||||
|
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
|
||||||
|
|
||||||
|
const stream = createReadStream(path, { highWaterMark: slice_size });
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
const end = Math.min(offset + chunk.length, img_size);
|
||||||
|
const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
|
||||||
|
const formData = await RequestUtil.createFormData(boundary, path);
|
||||||
|
|
||||||
|
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileUpload?seq=${seq}&retry=0&offset=${offset}&end=${end}&total=${img_size}&type=form&g_tk=${GTK}`;
|
||||||
|
const body = {
|
||||||
|
uin: uin,
|
||||||
|
appid: "qun",
|
||||||
|
session: session,
|
||||||
|
offset: offset,
|
||||||
|
data: formData,
|
||||||
|
checksum: "",
|
||||||
|
check_type: 0,
|
||||||
|
retry: 0,
|
||||||
|
seq: seq,
|
||||||
|
end: end,
|
||||||
|
cmd: "FileUpload",
|
||||||
|
slice_size: slice_size,
|
||||||
|
"biz_req.iUploadType": 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
|
||||||
|
"Cookie": cookie,
|
||||||
|
"Content-Type": `multipart/form-data; boundary=${boundary}`
|
||||||
|
});
|
||||||
|
|
||||||
|
offset += chunk.length;
|
||||||
|
seq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async uploadQunAlbum(path: string, albumId: string, group: string, skey: string, pskey: string, uin: string) {
|
||||||
|
const session = (await this.createQunAlbumSession(group, albumId, group, path, skey, pskey, uin) as { data: { session: string } }).data.session;
|
||||||
|
return await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 1024 * 1024);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -150,7 +150,7 @@ export interface NodeIQQNTWrapperSession {
|
|||||||
nodeIKernelSessionListener: NodeIKernelSessionListener,
|
nodeIKernelSessionListener: NodeIKernelSessionListener,
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
startNT(n: 0): void;
|
startNT(session: number): void;
|
||||||
|
|
||||||
startNT(): void;
|
startNT(): void;
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import BaseAction from '../BaseAction';
|
import { GetPacketStatusDepends } from '../packet/GetPacketStatus';
|
||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ const SchemaData = {
|
|||||||
|
|
||||||
type Payload = FromSchema<typeof SchemaData>;
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
export class SetGroupSign extends BaseAction<Payload, any> {
|
export class SetGroupSign extends GetPacketStatusDepends<Payload, any> {
|
||||||
actionName = ActionName.SetGroupSign;
|
actionName = ActionName.SetGroupSign;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ export class GetGroupRootFiles extends BaseAction<Payload, {
|
|||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
showOnlinedocFolder: 0,
|
showOnlinedocFolder: 0,
|
||||||
}).catch(() => []);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
files: ret.filter(item => item.fileInfo)
|
files: ret.filter(item => item.fileInfo)
|
||||||
|
Reference in New Issue
Block a user