Compare commits

...

20 Commits

Author SHA1 Message Date
手瓜一十雪
1ecd5b78e6 feat: 文件移除path字段增强部分能力 2025-02-18 16:55:43 +08:00
手瓜一十雪
fca2e3c51a style: remove debug 2025-02-18 16:52:30 +08:00
手瓜一十雪
95ea761b2d feat: get_private_file_url 2025-02-18 16:51:51 +08:00
手瓜一十雪
6b3bfa1ee9 fix #810 2025-02-18 13:24:37 +08:00
bietiaop
df3e302a9d fix: #802 2025-02-14 21:26:16 +08:00
pk5ls20
c88a68c9a8 fix: typo x2 2025-02-14 20:52:31 +08:00
Mlikiowa
92d01b9cdd release: v4.5.22 2025-02-14 10:36:03 +00:00
手瓜一十雪
fe04fa5986 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-14 17:41:40 +08:00
手瓜一十雪
c382f541b4 fix: 优化文件处理错误信息并简化下载逻辑 2025-02-14 17:41:25 +08:00
手瓜一十雪
f420527207 Update msg.ts 2025-02-14 17:41:03 +08:00
手瓜一十雪
e0c83ebf79 fix: #793 2025-02-14 17:15:19 +08:00
手瓜一十雪
c7fb18fc08 feat: 补全一些type 2025-02-14 15:39:06 +08:00
手瓜一十雪
2db8ab937d feat: GetUnidirectionalFriendList router 2025-02-14 15:06:36 +08:00
手瓜一十雪
819f5dd8e5 fix: #785 2025-02-14 14:50:00 +08:00
手瓜一十雪
d4a8ed735e fix: #789 2025-02-14 14:48:36 +08:00
手瓜一十雪
f07e3bb4d5 fix: type 2025-02-14 14:44:10 +08:00
手瓜一十雪
fa5ef0c221 fix: #797 2025-02-14 14:41:16 +08:00
手瓜一十雪
da7499ec0b Merge pull request #790 from NapNeko/dependabot/npm_and_yarn/esbuild-0.25.0
chore(deps-dev): bump esbuild from 0.24.0 to 0.25.0
2025-02-14 13:51:47 +08:00
Mlikiowa
d2f4327e44 release: v4.5.21 2025-02-12 18:57:14 +00:00
dependabot[bot]
29ae55f340 chore(deps-dev): bump esbuild from 0.24.0 to 0.25.0
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 08:16:17 +00:00
18 changed files with 188 additions and 58 deletions

View File

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

View File

@@ -1,19 +1,21 @@
import { PlayMode } from '@/const/enum'
import WebUIManager from '@/controllers/webui_manager'
import type {
FinalMusic,
Music163ListResponse,
Music163URLResponse
} from '@/types/music'
import WebUIManager from '@/controllers/webui_manager'
/**
* 获取网易云音乐歌单
* @param id 歌单id
* @returns 歌单信息
*/
export const get163MusicList = async (id: string) => {
let res = await WebUIManager.proxy<Music163ListResponse>('https://wavesgame.top/playlist/track/all?id=' + id);
let res = await WebUIManager.proxy<Music163ListResponse>(
'https://wavesgame.top/playlist/track/all?id=' + id
)
// const res = await request.get<Music163ListResponse>(
// `https://wavesgame.top/playlist/track/all?id=${id}`
// )
@@ -71,7 +73,7 @@ export const get163MusicListSongs = async (id: string) => {
if (songURL) {
finalMusic.push({
id: song.id,
url: songURL,
url: songURL.replace(/http:\/\//, '//').replace(/https:\/\//, '//'),
title: song.name,
artist: song.ar.map((p) => p.name).join('/'),
cover: song.al.picUrl

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.5.20",
"version": "4.5.22",
"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",
@@ -43,7 +43,7 @@
"async-mutex": "^0.5.0",
"commander": "^13.0.0",
"cors": "^2.8.5",
"esbuild": "0.24.0",
"esbuild": "0.25.0",
"eslint": "^9.14.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",

View File

@@ -1 +1 @@
export const napCatVersion = '4.5.20';
export const napCatVersion = '4.5.22';

View File

@@ -1,5 +1,5 @@
import { ChatType, GetFileListParam, Peer, RawMessage, SendMessageElement, SendStatusType } from '@/core/types';
import { GroupFileInfoUpdateItem, InstanceContext, NapCatCore } from '@/core';
import { GroupFileInfoUpdateItem, InstanceContext, NapCatCore, NodeIKernelMsgService } from '@/core';
import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi {
@@ -12,6 +12,11 @@ export class NTQQMsgApi {
this.context = context;
this.core = core;
}
async clickInlineKeyboardButton(...params: Parameters<NodeIKernelMsgService['clickInlineKeyboardButton']>) {
return this.context.session.getMsgService().clickInlineKeyboardButton(...params);
}
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
// https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType 可以用过特殊方式拉取
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);

View File

@@ -175,7 +175,7 @@
"send": "713A318",
"recv": "713DB50"
},
"6.9.63.30851-x64": {
"6.9.63-30851-x64": {
"send": "46C8040",
"recv": "46CA8AC"
},

View File

@@ -1,22 +1,22 @@
import * as crypto from 'crypto';
import {PacketContext} from '@/core/packet/context/packetContext';
import { PacketContext } from '@/core/packet/context/packetContext';
import * as trans from '@/core/packet/transformer';
import {PacketMsg} from '@/core/packet/message/message';
import { PacketMsg } from '@/core/packet/message/message';
import {
PacketMsgFileElement,
PacketMsgPicElement,
PacketMsgPttElement,
PacketMsgVideoElement
} from '@/core/packet/message/element';
import {ChatType, MsgSourceType, NTMsgType, RawMessage} from '@/core';
import {MiniAppRawData, MiniAppReqParams} from '@/core/packet/entities/miniApp';
import {AIVoiceChatType} from '@/core/packet/entities/aiChat';
import {NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg} from '@napneko/nap-proto-core';
import {IndexNode, LongMsgResult, MsgInfo} from '@/core/packet/transformer/proto';
import {OidbPacket} from '@/core/packet/transformer/base';
import {ImageOcrResult} from '@/core/packet/entities/ocrResult';
import {gunzipSync} from 'zlib';
import {PacketMsgConverter} from '@/core/packet/message/converter';
import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/core';
import { MiniAppRawData, MiniAppReqParams } from '@/core/packet/entities/miniApp';
import { AIVoiceChatType } from '@/core/packet/entities/aiChat';
import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
import { IndexNode, LongMsgResult, MsgInfo } from '@/core/packet/transformer/proto';
import { OidbPacket } from '@/core/packet/transformer/base';
import { ImageOcrResult } from '@/core/packet/entities/ocrResult';
import { gunzipSync } from 'zlib';
import { PacketMsgConverter } from '@/core/packet/message/converter';
export class PacketOperationContext {
private readonly context: PacketContext;
@@ -59,10 +59,10 @@ export class PacketOperationContext {
const res = trans.GetStrangerInfo.parse(resp);
const extBigInt = BigInt(res.data.status.value);
if (extBigInt <= 10n) {
return {status: Number(extBigInt) * 10, ext_status: 0};
return { status: Number(extBigInt) * 10, ext_status: 0 };
}
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn));
return {status: 10, ext_status: status};
return { status: 10, ext_status: status };
} catch {
return undefined;
}
@@ -79,13 +79,13 @@ export class PacketOperationContext {
const reqList = msg.flatMap(m =>
m.msg.map(e => {
if (e instanceof PacketMsgPicElement) {
return this.context.highway.uploadImage({chatType, peerUid}, e);
return this.context.highway.uploadImage({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgVideoElement) {
return this.context.highway.uploadVideo({chatType, peerUid}, e);
return this.context.highway.uploadVideo({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgPttElement) {
return this.context.highway.uploadPtt({chatType, peerUid}, e);
return this.context.highway.uploadPtt({ chatType, peerUid }, e);
} else if (e instanceof PacketMsgFileElement) {
return this.context.highway.uploadFile({chatType, peerUid}, e);
return this.context.highway.uploadFile({ chatType, peerUid }, e);
}
return null;
}).filter(Boolean)
@@ -160,6 +160,12 @@ export class PacketOperationContext {
const res = trans.DownloadGroupFile.parse(resp);
return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`;
}
async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string) {
const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5);
const resp = await this.context.client.sendOidbPacket(req, true);
const res = trans.DownloadPrivateFile.parse(resp);
return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`;
}
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const req = trans.DownloadGroupPtt.build(groupUin, node);

View File

@@ -1,4 +1,5 @@
import { DownloadBaseEmojiByIdReq, DownloadBaseEmojiByUrlReq, GetBaseEmojiPathReq, PullSysEmojisReq } from '../types';
import { GeneralCallResult } from './common';
export interface NodeIKernelBaseEmojiService {
removeKernelBaseEmojiListener(listenerId: number): void;
@@ -7,7 +8,26 @@ export interface NodeIKernelBaseEmojiService {
isBaseEmojiPathExist(args: Array<string>): unknown;
fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): unknown;
fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): Promise<GeneralCallResult & {
rsp: {
otherPanelResult: {
SysEmojiGroupList: Array<unknown>,
downloadInfo: Array<unknown>
},
normalPanelResult: {
SysEmojiGroupList: Array<unknown>,
downloadInfo: Array<unknown>
},
superPanelResult: {
SysEmojiGroupList: Array<unknown>,
downloadInfo: Array<unknown>
},
redHeartPanelResult: {
SysEmojiGroupList: Array<unknown>,
downloadInfo: Array<unknown>
}
}
}>;
getBaseEmojiPathByIds(getBaseEmojiPathReqs: Array<GetBaseEmojiPathReq>): unknown;

View File

@@ -253,7 +253,7 @@ export interface NodeIKernelGroupService {
getGroupShutUpMemberList(groupCode: string): Promise<GeneralCallResult>;
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>;
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<GeneralCallResult>;
getGroupRecommendContactArkJson(groupCode: string): Promise<GeneralCallResult & { arkJson: string }>;

View File

@@ -464,11 +464,20 @@ export interface NodeIKernelMsgService {
setMsgEmojiLikesForRole(...args: unknown[]): unknown;
clickInlineKeyboardButton(...args: unknown[]): unknown;
clickInlineKeyboardButton(params: {
guildId?: string,
peerId: string,
botAppid: string,
msgSeq: string,
buttonId: string,
callback_data: string,
dmFlag: number,
chatType: number // 1私聊 2群
}): Promise<GeneralCallResult & { status: number, promptText: string, promptType: number, promptIcon: number }>;
setCurOnScreenMsg(...args: unknown[]): unknown;
setCurOnScreenMsgForMsgEvent(...args: unknown[]): unknown;
setCurOnScreenMsgForMsgEvent(peer: Peer, msgRegList: Map<string, Uint8Array>): void;
getMiscData(key: string): unknown;

View File

@@ -1,4 +1,5 @@
import { NodeIKernelRobotListener } from '@/core/listeners';
import { GeneralCallResult, Peer } from '..';
export interface NodeIKernelRobotService {
fetchGroupRobotStoreDiscovery(arg: unknown): unknown;
@@ -31,5 +32,17 @@ export interface NodeIKernelRobotService {
getRobotUinRange(data: unknown): Promise<{ response: { robotUinRanges: Array<unknown> } }>;
getRobotFunctions(peer: Peer, params: {
uins: Array<string>,
num: 0,
client_info: { platform: 4, version: '', build_num: 9999 },
tinyids: [],
page: 0,
full_fetch: false,
scene: 4,
filter: 1,
bkn: ''
}): Promise<GeneralCallResult & { response: { bot_features: Array<unknown>, next_page: number } }>;
isNull(): boolean;
}

View File

@@ -0,0 +1,10 @@
import { ActionName } from '@/onebot/action/router';
import { OneBotAction } from '../OneBotAction';
export class BotExit extends OneBotAction<void, void> {
override actionName = ActionName.Exit;
async _handle() {
process.exit(0);
}
}

View File

@@ -0,0 +1,30 @@
import { ActionName } from '@/onebot/action/router';
import { OneBotAction } from '../OneBotAction';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]),
bot_appid: Type.String(),
button_id: Type.String({ default: '' }),
callback_data: Type.String({ default: '' }),
msg_seq: Type.String({ default: '10086' }),
});
type Payload = Static<typeof SchemaData>;
export class ClickInlineKeyboardButton extends OneBotAction<Payload, unknown> {
override actionName = ActionName.ClickInlineKeyboardButton;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
return await this.core.apis.MsgApi.clickInlineKeyboardButton({
buttonId: payload.button_id,
peerId: payload.group_id.toString(),
botAppid: payload.bot_appid,
msgSeq: payload.msg_seq,
callback_data: payload.callback_data,
dmFlag: 0,
chatType: 2
})
}
}

View File

@@ -0,0 +1,36 @@
import { ActionName } from '@/onebot/action/router';
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
file_id: Type.String(),
});
type Payload = Static<typeof SchemaData>;
interface GetPrivateFileUrlResponse {
url?: string;
}
export class GetPrivateFileUrl extends GetPacketStatusDepends<Payload, GetPrivateFileUrlResponse> {
override actionName = ActionName.NapCat_GetPrivateFileUrl;
override payloadSchema = SchemaData;
async _handle(payload: Payload) {
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id);
if (contextMsgFile?.fileUUID && contextMsgFile.msgId) {
let msg = await this.core.apis.MsgApi.getMsgsByMsgId(contextMsgFile.peer, [contextMsgFile.msgId]);
let self_id = this.core.selfInfo.uid;
let file_hash = msg.msgList[0]?.elements.map(ele => ele.fileElement?.file10MMd5)[0];
if (file_hash) {
return {
url: await this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(self_id, contextMsgFile.fileUUID, file_hash)
};
}
}
throw new Error('real fileUUID not found!');
}
}

View File

@@ -13,12 +13,13 @@ type Payload = Static<typeof SchemaData>;
export default class SetGroupBan extends OneBotAction<Payload, null> {
override actionName = ActionName.SetGroupBan;
override payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('uid error');
await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
// 例如无管理员权限时 result为 120101005 errMsg为 'ERR_NOT_GROUP_ADMIN'
let ret = await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
[{ uid: uid, timeStamp: +payload.duration }]);
if (ret.result !== 0) throw new Error(ret.errMsg);
return null;
}
}

View File

@@ -104,6 +104,9 @@ import { GetClientkey } from './extends/GetClientkey';
import { SendPacket } from './extends/SendPacket';
import { SendPoke } from '@/onebot/action/packet/SendPoke';
import { SetDiyOnlineStatus } from './extends/SetDiyOnlineStatus';
import { BotExit } from './extends/BotExit';
import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton';
import { GetPrivateFileUrl } from './file/GetPrivateFileUrl';
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
@@ -221,6 +224,9 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new SendPacket(obContext, core),
new SendPoke(obContext, core),
new GetGroupSystemMsg(obContext, core),
new BotExit(obContext, core),
new ClickInlineKeyboardButton(obContext, core),
new GetPrivateFileUrl(obContext,core)
];
type HandlerUnion = typeof actionHandlers[number];

View File

@@ -10,6 +10,9 @@ export interface InvalidCheckResult {
}
export const ActionName = {
NapCat_GetPrivateFileUrl: 'get_private_file_url',
ClickInlineKeyboardButton: 'click_inline_keyboard_button',
GetUnidirectionalFriendList: 'get_unidirectional_friend_list',
// onebot 11
SendPrivateMsg: 'send_private_msg',
SendGroupMsg: 'send_group_msg',
@@ -49,7 +52,7 @@ export const ActionName = {
GetVersionInfo: 'get_version_info',
// Reboot : 'set_restart',
// CleanCache : 'clean_cache',
Exit: 'bot_exit',
// go-cqhttp
SetQQProfile: 'set_qq_profile',
// QidianGetAccountInfo : 'qidian_get_account_info',
@@ -141,6 +144,6 @@ export const ActionName = {
SendGroupAiRecord: 'send_group_ai_record',
GetClientkey: 'get_clientkey',
SendPoke: 'send_poke',
} as const;

View File

@@ -132,7 +132,6 @@ export class OneBotMsgApi {
file: element.fileName,
sub_type: element.picSubType,
url: await this.core.apis.FileApi.getImageUrl(element),
path: element.filePath,
file_size: element.fileSize,
},
};
@@ -148,13 +147,13 @@ export class OneBotMsgApi {
peerUid: msg.peerUid,
guildId: '',
};
const file = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid);
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
return {
type: OB11MessageDataType.file,
data: {
file: file,
path: element.filePath,
file_id: file,
file: element.fileName,
file_id: element.fileUuid,
file_size: element.fileSize,
},
};
@@ -216,7 +215,6 @@ export class OneBotMsgApi {
data: {
summary: _.faceName, // 商城表情名称
file: filename,
path: url,
url: url,
key: _.key,
emoji_id: _.emojiId,
@@ -339,7 +337,6 @@ export class OneBotMsgApi {
type: OB11MessageDataType.video,
data: {
file: fileCode,
path: videoDownUrl,
url: videoDownUrl,
file_size: element.fileSize,
},
@@ -357,7 +354,6 @@ export class OneBotMsgApi {
type: OB11MessageDataType.voice,
data: {
file: fileCode,
path: element.filePath,
file_size: element.fileSize,
},
};
@@ -996,24 +992,17 @@ export class OneBotMsgApi {
this.core.context.logger.logError('文件消息缺少参数', inputdata);
throw new Error('文件消息缺少参数');
}
const downloadFile = async (uri: string) => {
const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, uri);
if (!success) {
this.core.context.logger.logError('文件下载失败', errMsg);
throw new Error('文件下载失败: ' + errMsg);
}
return { path, fileName };
};
realUri = await this.handleObfuckName(realUri) ?? realUri;
try {
const { path, fileName } = await downloadFile(realUri);
deleteAfterSentFiles.push(path);
return { path, fileName: inputdata.name ?? fileName };
} catch {
realUri = await this.handleObfuckName(realUri);
const { path, fileName } = await downloadFile(realUri);
const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, realUri);
if (!success) {
this.core.context.logger.logError('文件处理失败', errMsg);
throw new Error('文件处理失败: ' + errMsg);
}
deleteAfterSentFiles.push(path);
return { path, fileName: inputdata.name ?? fileName };
} catch (e: unknown) {
throw new Error((e as Error).message);
}
}
@@ -1038,7 +1027,7 @@ export class OneBotMsgApi {
}
return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
}
throw new Error('文件名解析失败');
return undefined;
}
groupChangDecreseType2String(type: number): GroupDecreaseSubType {