mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 00:00:26 +00:00
Compare commits
10 Commits
v4.8.115
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e431471c4c | ||
|
|
e6c694c2b7 | ||
|
|
65bd37fdb5 | ||
|
|
f2c62db76e | ||
|
|
b1b051c4ce | ||
|
|
a754b2ecc7 | ||
|
|
e0eb625b75 | ||
|
|
937be7678e | ||
|
|
9b88946209 | ||
|
|
74de3d9100 |
12
README.md
12
README.md
@@ -13,6 +13,15 @@ _Modern protocol-side framework implemented based on NTQQ._
|
||||
|
||||
---
|
||||
|
||||
## New Feature
|
||||
在 v4.8.115+ 版本开始
|
||||
|
||||
1. NapCatQQ 支持 [Stream Api](https://napneko.github.io/develop/file)
|
||||
2. NapCatQQ 推荐 message_id/user_id/group_id 均使用字符串类型
|
||||
|
||||
- [1] 解决 Docker/跨设备/大文件 的多媒体上下传问题
|
||||
- [2] 采用字符串可以解决扩展到int64的问题,同时也可以解决部分语言(如JavaScript)对大整数支持不佳的问题,增加极少成本。
|
||||
|
||||
## Welcome
|
||||
+ NapCatQQ is a modern implementation of the Bot protocol based on NTQQ.
|
||||
- NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
@@ -48,6 +57,9 @@ _Modern protocol-side framework implemented based on NTQQ._
|
||||
| Telegram | [](https://t.me/napcatqq) |
|
||||
|:-:|:-:|
|
||||
|
||||
| DeepWiki | [](https://deepwiki.com/NapNeko/NapCatQQ) |
|
||||
|:-:|:-:|
|
||||
|
||||
> 请不要在其余社区提及本项目(包括其余协议端/相关应用端项目)引发争论,如有建议到达官方交流群讨论或PR。
|
||||
|
||||
## Thanks
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.8.114",
|
||||
"version": "4.8.116",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.8.114",
|
||||
"version": "4.8.116",
|
||||
"scripts": {
|
||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.8.114';
|
||||
export const napCatVersion = '4.8.116';
|
||||
|
||||
@@ -22,8 +22,9 @@ export class MoveGroupFile extends GetPacketStatusDepends<Payload, MoveGroupFile
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.target_parent_directory);
|
||||
const fileUUID = contextMsgFile?.fileUUID || contextMsgFile?.fileId;
|
||||
if (fileUUID) {
|
||||
await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, fileUUID, payload.current_parent_directory, payload.target_parent_directory);
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
|
||||
@@ -22,8 +22,9 @@ export class RenameGroupFile extends GetPacketStatusDepends<Payload, RenameGroup
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.new_name);
|
||||
const fileUUID = contextMsgFile?.fileUUID || contextMsgFile?.fileId;
|
||||
if (fileUUID) {
|
||||
await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, fileUUID, payload.current_parent_directory, payload.new_name);
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
|
||||
@@ -20,8 +20,9 @@ export class TransGroupFile extends GetPacketStatusDepends<Payload, TransGroupFi
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), contextMsgFile.fileUUID);
|
||||
const fileUUID = contextMsgFile?.fileUUID || contextMsgFile?.fileId;
|
||||
if (fileUUID) {
|
||||
const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), fileUUID);
|
||||
if (result.transGroupFileResult.result.retCode === 0) {
|
||||
return {
|
||||
ok: true
|
||||
|
||||
@@ -20,9 +20,10 @@ export class GetGroupFileUrl extends GetPacketStatusDepends<Payload, GetGroupFil
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
const fileUUID = contextMsgFile?.fileUUID || contextMsgFile?.fileId;
|
||||
if (fileUUID) {
|
||||
return {
|
||||
url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, contextMsgFile.fileUUID)
|
||||
url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, fileUUID)
|
||||
};
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
|
||||
@@ -16,7 +16,7 @@ export class DeleteGroupFile extends OneBotAction<Payload, Awaited<ReturnType<NT
|
||||
override actionName = ActionName.GOCQHTTP_DeleteGroupFile;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
const data = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (!data || !data.fileId) throw new Error('Invalid file_id');
|
||||
return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import fs from 'fs';
|
||||
import { join as joinPath } from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { createHash } from 'crypto';
|
||||
import { unlink } from 'node:fs';
|
||||
|
||||
// 简化配置
|
||||
const CONFIG = {
|
||||
@@ -25,7 +26,8 @@ const SchemaData = Type.Object({
|
||||
is_complete: Type.Optional(Type.Boolean()),
|
||||
filename: Type.Optional(Type.String()),
|
||||
reset: Type.Optional(Type.Boolean()),
|
||||
verify_only: Type.Optional(Type.Boolean())
|
||||
verify_only: Type.Optional(Type.Boolean()),
|
||||
file_retention: Type.Number({ default: 5 * 60 * 1000 }) // 默认5分钟 回收 不设置或0为不回收
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -47,6 +49,7 @@ interface StreamState {
|
||||
memoryChunks?: Map<number, Buffer>;
|
||||
tempDir?: string;
|
||||
finalPath?: string;
|
||||
fileRetention?: number;
|
||||
|
||||
// 管理
|
||||
createdAt: number;
|
||||
@@ -131,9 +134,9 @@ export class UploadFileStream extends OneBotAction<Payload, StreamPacket<StreamR
|
||||
expectedSha256: expected_sha256,
|
||||
useMemory,
|
||||
createdAt: Date.now(),
|
||||
timeoutId: this.setupTimeout(stream_id)
|
||||
timeoutId: this.setupTimeout(stream_id),
|
||||
fileRetention: payload.file_retention
|
||||
};
|
||||
|
||||
try {
|
||||
if (useMemory) {
|
||||
stream.memoryChunks = new Map();
|
||||
@@ -244,7 +247,13 @@ export class UploadFileStream extends OneBotAction<Payload, StreamPacket<StreamR
|
||||
|
||||
// 清理资源但保留文件
|
||||
this.cleanupStream(stream.id, false);
|
||||
|
||||
if (stream.fileRetention && stream.fileRetention > 0) {
|
||||
setTimeout(() => {
|
||||
unlink(finalPath, err => {
|
||||
if (err) this.core.context.logger.logError(`Failed to delete retained file ${finalPath}:`, err);
|
||||
});
|
||||
}, stream.fileRetention);
|
||||
}
|
||||
return {
|
||||
type: StreamStatus.Response,
|
||||
stream_id: stream.id,
|
||||
|
||||
@@ -29,7 +29,7 @@ class OneBotUploadTester:
|
||||
headers["Authorization"] = f"Bearer {self.access_token}"
|
||||
|
||||
print(f"连接到 {self.ws_url}")
|
||||
self.websocket = await websockets.connect(self.ws_url, extra_headers=headers)
|
||||
self.websocket = await websockets.connect(self.ws_url, additional_headers=headers)
|
||||
print("WebSocket 连接成功")
|
||||
|
||||
async def disconnect(self):
|
||||
@@ -38,7 +38,7 @@ class OneBotUploadTester:
|
||||
await self.websocket.close()
|
||||
print("WebSocket 连接已断开")
|
||||
|
||||
def calculate_file_chunks(self, file_path: str, chunk_size: int = 64 * 1024) -> tuple[List[bytes], str, int]:
|
||||
def calculate_file_chunks(self, file_path: str, chunk_size: int = 64) -> tuple[List[bytes], str, int]:
|
||||
"""
|
||||
计算文件分片和 SHA256
|
||||
|
||||
@@ -97,7 +97,7 @@ class OneBotUploadTester:
|
||||
print(f"收到其他消息: {data}")
|
||||
continue
|
||||
|
||||
async def upload_file_stream_batch(self, file_path: str, chunk_size: int = 64 * 1024) -> str:
|
||||
async def upload_file_stream_batch(self, file_path: str, chunk_size: int = 64 ) -> str:
|
||||
"""
|
||||
一次性批量上传文件流
|
||||
|
||||
@@ -134,7 +134,8 @@ class OneBotUploadTester:
|
||||
"total_chunks": total_chunks,
|
||||
"file_size": total_size,
|
||||
"expected_sha256": sha256_hash,
|
||||
"filename": file_path.name
|
||||
"filename": file_path.name,
|
||||
"file_retention": 30 * 1000
|
||||
}
|
||||
|
||||
# 发送分片
|
||||
@@ -171,7 +172,7 @@ class OneBotUploadTester:
|
||||
else:
|
||||
raise Exception(f"文件状态异常: {result}")
|
||||
|
||||
async def test_upload(self, file_path: str, chunk_size: int = 64 * 1024):
|
||||
async def test_upload(self, file_path: str, chunk_size: int = 64 ):
|
||||
"""测试文件上传"""
|
||||
try:
|
||||
await self.connect()
|
||||
|
||||
Reference in New Issue
Block a user