mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
06eba28b4c | ||
![]() |
bbfeac46dd | ||
![]() |
2fe4da094a | ||
![]() |
b454d8c0f9 | ||
![]() |
1f9b5453cc | ||
![]() |
3261791e99 | ||
![]() |
3bb12e3f45 | ||
![]() |
1dc2f7e5a2 | ||
![]() |
2531b08538 | ||
![]() |
9fcfb5493c | ||
![]() |
4576354c51 | ||
![]() |
1dcf2ef0c6 | ||
![]() |
3642c65e8c | ||
![]() |
40e105994a | ||
![]() |
f2ee973882 |
@@ -34,7 +34,7 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
|
||||
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/NWP25OeV0c)
|
||||
[QQ Group](https://qm.qq.com/q/I6LU87a0Yq)
|
||||
|
||||
## 感谢他们
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.2.0",
|
||||
"version": "4.2.5",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.2.0",
|
||||
"version": "4.2.5",
|
||||
"scripts": {
|
||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||
"build:shell": "npm run build:webui && vite build --mode shell || exit 1",
|
||||
|
@@ -190,16 +190,27 @@ export async function checkUriType(Uri: string) {
|
||||
}, Uri);
|
||||
if (LocalFileRet) return LocalFileRet;
|
||||
const OtherFileRet = await solveProblem((uri: string) => {
|
||||
//再判断是否是Http
|
||||
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
||||
// 再判断是否是Http
|
||||
if (uri.startsWith('http:') || uri.startsWith('https:')) {
|
||||
return { Uri: uri, Type: FileUriType.Remote };
|
||||
}
|
||||
//再判断是否是Base64
|
||||
if (uri.startsWith('base64://')) {
|
||||
// 再判断是否是Base64
|
||||
if (uri.startsWith('base64:')) {
|
||||
return { Uri: uri, Type: FileUriType.Base64 };
|
||||
}
|
||||
if (uri.startsWith('file://')) {
|
||||
let filePath: string = uri.slice(7);
|
||||
// 默认file://
|
||||
if (uri.startsWith('file:')) {
|
||||
// 兼容file:///
|
||||
// file:///C:/1.jpg
|
||||
if (uri.startsWith('file:///') && process.platform === 'win32') {
|
||||
const filePath: string = uri.slice(8);
|
||||
return { Uri: filePath, Type: FileUriType.Local };
|
||||
}
|
||||
// 处理默认规范
|
||||
// file://C:\1.jpg
|
||||
// file:///test/1.jpg
|
||||
const filePath: string = uri.slice(7);
|
||||
|
||||
return { Uri: filePath, Type: FileUriType.Local };
|
||||
}
|
||||
if (uri.startsWith('data:')) {
|
||||
@@ -235,7 +246,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
||||
fs.copyFileSync(HandledUri, filePath);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
|
||||
|
||||
//接下来都要有文件名
|
||||
if (UriType == FileUriType.Remote) {
|
||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.2.0';
|
||||
export const napCatVersion = '4.2.5';
|
||||
|
@@ -41,7 +41,7 @@ export class NTQQFileApi {
|
||||
this.rkeyManager = new RkeyManager([
|
||||
'https://rkey.napneko.icu/rkeys'
|
||||
],
|
||||
this.context.logger
|
||||
this.context.logger
|
||||
);
|
||||
}
|
||||
|
||||
@@ -307,18 +307,18 @@ export class NTQQFileApi {
|
||||
element.elementType === ElementType.FILE
|
||||
) {
|
||||
switch (element.elementType) {
|
||||
case ElementType.PIC:
|
||||
case ElementType.PIC:
|
||||
element.picElement!.sourcePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.VIDEO:
|
||||
break;
|
||||
case ElementType.VIDEO:
|
||||
element.videoElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.PTT:
|
||||
break;
|
||||
case ElementType.PTT:
|
||||
element.pttElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
case ElementType.FILE:
|
||||
break;
|
||||
case ElementType.FILE:
|
||||
element.fileElement!.filePath = elementResults[elementIndex];
|
||||
break;
|
||||
break;
|
||||
}
|
||||
elementIndex++;
|
||||
}
|
||||
|
@@ -3,12 +3,12 @@ import { PicType } from '../types';
|
||||
export async function getFileTypeForSendType(picPath: string): Promise<PicType> {
|
||||
const fileTypeResult = (await fileType.fileTypeFromFile(picPath))?.ext ?? 'jpg';
|
||||
const picTypeMap: { [key: string]: PicType } = {
|
||||
'webp': PicType.NEWPIC_WEBP,
|
||||
//'webp': PicType.NEWPIC_WEBP,
|
||||
'gif': PicType.NEWPIC_GIF,
|
||||
'png': PicType.NEWPIC_APNG,
|
||||
'jpg': PicType.NEWPIC_JPEG,
|
||||
'jpeg': PicType.NEWPIC_JPEG,
|
||||
'bmp': PicType.NEWPIC_BMP,
|
||||
// 'png': PicType.NEWPIC_APNG,
|
||||
// 'jpg': PicType.NEWPIC_JPEG,
|
||||
// 'jpeg': PicType.NEWPIC_JPEG,
|
||||
// 'bmp': PicType.NEWPIC_BMP,
|
||||
};
|
||||
return picTypeMap[fileTypeResult] ?? PicType.NEWPIC_JPEG;
|
||||
}
|
@@ -62,7 +62,7 @@ export const GroupChange = {
|
||||
operatorUid: ProtoField(5, ScalarType.STRING, true),
|
||||
increaseType: ProtoField(6, ScalarType.UINT32),
|
||||
field7: ProtoField(7, ScalarType.BYTES, true),
|
||||
}
|
||||
};
|
||||
|
||||
export const PushMsgBody = {
|
||||
responseHead: ProtoField(1, () => ResponseHead),
|
||||
|
@@ -66,31 +66,31 @@ export class OneBotGroupApi {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async parseGroupIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
this.core.context.logger.logDebug('收到新人被邀请进群消息', grayTipElement);
|
||||
const xmlElement = grayTipElement.xmlElement;
|
||||
if (xmlElement?.content) {
|
||||
const regex = /jp="(\d+)"/g;
|
||||
// async parseGroupIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// this.core.context.logger.logDebug('收到新人被邀请进群消息', grayTipElement);
|
||||
// const xmlElement = grayTipElement.xmlElement;
|
||||
// if (xmlElement?.content) {
|
||||
// const regex = /jp="(\d+)"/g;
|
||||
|
||||
const matches = [];
|
||||
let match = null;
|
||||
// const matches = [];
|
||||
// let match = null;
|
||||
|
||||
while ((match = regex.exec(xmlElement.content)) !== null) {
|
||||
matches.push(match[1]);
|
||||
}
|
||||
if (matches.length === 2) {
|
||||
const [inviter, invitee] = matches;
|
||||
return new OB11GroupIncreaseEvent(
|
||||
this.core,
|
||||
parseInt(GroupCode),
|
||||
parseInt(invitee),
|
||||
parseInt(inviter),
|
||||
'invite',
|
||||
);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// while ((match = regex.exec(xmlElement.content)) !== null) {
|
||||
// matches.push(match[1]);
|
||||
// }
|
||||
// if (matches.length === 2) {
|
||||
// const [inviter, invitee] = matches;
|
||||
// return new OB11GroupIncreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(GroupCode),
|
||||
// parseInt(invitee),
|
||||
// parseInt(inviter),
|
||||
// 'invite',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
// async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// const groupElement = grayTipElement?.groupElement;
|
||||
@@ -304,9 +304,8 @@ export class OneBotGroupApi {
|
||||
// 筛选出表情回应 事件
|
||||
if (grayTipElement.xmlElement?.templId === '10382') {
|
||||
return await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, grayTipElement);
|
||||
|
||||
} else {
|
||||
return await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, grayTipElement);
|
||||
//return await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, grayTipElement);
|
||||
}
|
||||
} else if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
|
||||
// 解析json事件
|
||||
@@ -315,7 +314,7 @@ export class OneBotGroupApi {
|
||||
} else if (grayTipElement.jsonGrayTipElement.busiId == JsonGrayBusiId.AIO_GROUP_ESSENCE_MSG_TIP) {
|
||||
return await this.parseEssenceMsg(msg, grayTipElement.jsonGrayTipElement.jsonStr);
|
||||
} else {
|
||||
return await this.parseOtherJsonEvent(msg, grayTipElement.jsonGrayTipElement.jsonStr, this.core.context)
|
||||
return await this.parseOtherJsonEvent(msg, grayTipElement.jsonGrayTipElement.jsonStr, this.core.context);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
@@ -673,7 +673,7 @@ export class OneBotMsgApi {
|
||||
if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) {
|
||||
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||
const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(grayTipElement);
|
||||
if (PokeEvent) { return PokeEvent };
|
||||
if (PokeEvent) { return PokeEvent; };
|
||||
} else if (grayTipElement.jsonGrayTipElement.busiId == 19324 && msg.peerUid !== '') {
|
||||
return new OB11FriendAddNoticeEvent(this.core, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid)));
|
||||
}
|
||||
@@ -841,14 +841,14 @@ export class OneBotMsgApi {
|
||||
}
|
||||
|
||||
private async convertArrayToStringMessage(originMsg: OB11Message): Promise<OB11Message> {
|
||||
let msg = structuredClone(originMsg);
|
||||
const msg = structuredClone(originMsg);
|
||||
msg.message_format = 'string';
|
||||
msg.message = msg.raw_message;
|
||||
return msg;
|
||||
}
|
||||
|
||||
async importArrayTostringMsg(originMsg: OB11Message) {
|
||||
let msg = structuredClone(originMsg);
|
||||
const msg = structuredClone(originMsg);
|
||||
msg.message_format = 'string';
|
||||
msg.message = msg.raw_message;
|
||||
return msg;
|
||||
@@ -924,7 +924,7 @@ export class OneBotMsgApi {
|
||||
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', e));
|
||||
}
|
||||
} catch (error) {
|
||||
this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', (error as Error).message)
|
||||
this.core.context.logger.logError.bind(this.core.context.logger)('发送消息删除文件失败', (error as Error).message);
|
||||
}
|
||||
});
|
||||
}, 60000);
|
||||
@@ -960,9 +960,9 @@ export class OneBotMsgApi {
|
||||
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
||||
switch (type) {
|
||||
case 130:
|
||||
return 'kick';
|
||||
case 131:
|
||||
return 'leave';
|
||||
case 131:
|
||||
return 'kick';
|
||||
case 3:
|
||||
return 'kick_me';
|
||||
default:
|
||||
@@ -972,16 +972,7 @@ export class OneBotMsgApi {
|
||||
|
||||
async parseSysMessage(msg: number[]) {
|
||||
// Todo Refactor
|
||||
// const sysMsg = decodeSysMessage(Uint8Array.from(msg));
|
||||
// if (sysMsg.msgSpec.length === 0) {
|
||||
// return;
|
||||
// }
|
||||
// const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
|
||||
// if (msgType === 528 && subType === 39 && subSubType === 39) {
|
||||
// if (!sysMsg.bodyWrapper) return;
|
||||
// return await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody);
|
||||
// }
|
||||
let SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
|
||||
const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
|
||||
if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) {
|
||||
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||
console.log(JSON.stringify(groupChange));
|
||||
@@ -994,15 +985,17 @@ export class OneBotMsgApi {
|
||||
);
|
||||
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
|
||||
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||
// console.log(JSON.stringify(groupChange),JSON.stringify(SysMessage));
|
||||
return new OB11GroupDecreaseEvent(
|
||||
this.core,
|
||||
groupChange.groupUin,
|
||||
+this.core.selfInfo.uin,
|
||||
groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0,
|
||||
groupChange.operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.operatorUid) : 0,
|
||||
this.groupChangDecreseType2String(groupChange.decreaseType),
|
||||
);
|
||||
} else if (SysMessage.contentHead.type == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) {
|
||||
return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent);
|
||||
}
|
||||
|
||||
/*
|
||||
if (msgType === 732 && subType === 16 && subSubType === 16) {
|
||||
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
|
||||
|
@@ -13,7 +13,7 @@ export class OneBotUserApi {
|
||||
}
|
||||
|
||||
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
|
||||
const likeTip = decodeProfileLikeTip(Uint8Array.from(wrappedBody));
|
||||
const likeTip = decodeProfileLikeTip(wrappedBody);
|
||||
if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return;
|
||||
this.core.context.logger.logDebug("收到点赞通知消息");
|
||||
const likeMsg = likeTip.content.msg;
|
||||
|
@@ -213,7 +213,7 @@ export class NapCatOneBot11Adapter {
|
||||
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
|
||||
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
|
||||
}
|
||||
} else if(adapterConfig.enable) {
|
||||
} else if (adapterConfig.enable) {
|
||||
const newAdapter = new adapterClass(adapterConfig.name, adapterConfig, this.core, this.actions);
|
||||
await this.networkManager.registerAdapterAndOpen(newAdapter);
|
||||
}
|
||||
@@ -266,23 +266,30 @@ export class NapCatOneBot11Adapter {
|
||||
};
|
||||
|
||||
msgListener.onAddSendMsg = async (msg) => {
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
||||
await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgInfoListUpdate', (msgList: RawMessage[]) => {
|
||||
const report = msgList.find((e) =>
|
||||
e.senderUin == this.core.selfInfo.uin && e.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && e.msgId === msg.msgId
|
||||
);
|
||||
return !!report;
|
||||
}, 1, 300);
|
||||
msg.id = MessageUnique.createUniqueMsgId(
|
||||
{
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '',
|
||||
},
|
||||
msg.msgId
|
||||
);
|
||||
//此时上报的seq不是对的 不过对onebot业务无影响
|
||||
this.emitMsg(msg);
|
||||
try {
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
||||
const [updatemsgs] = await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgInfoListUpdate', (msgList: RawMessage[]) => {
|
||||
const report = msgList.find((e) =>
|
||||
e.senderUin == this.core.selfInfo.uin && e.sendStatus !== SendStatusType.KSEND_STATUS_SENDING && e.msgId === msg.msgId
|
||||
);
|
||||
return !!report;
|
||||
}, 1, 10 * 60 * 1000);
|
||||
// 10分钟 超时
|
||||
const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId);
|
||||
if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS || updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ) {
|
||||
updatemsg.id = MessageUnique.createUniqueMsgId(
|
||||
{
|
||||
chatType: updatemsg.chatType,
|
||||
peerUid: updatemsg.peerUid,
|
||||
guildId: '',
|
||||
},
|
||||
updatemsg.msgId
|
||||
);
|
||||
this.emitMsg(updatemsg);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.context.logger.logError('处理发送消息失败', error);
|
||||
}
|
||||
};
|
||||
msgListener.onMsgRecall = async (chatType: ChatType, uid: string, msgSeq: string) => {
|
||||
@@ -291,10 +298,10 @@ export class NapCatOneBot11Adapter {
|
||||
peerUid: uid,
|
||||
guildId: ''
|
||||
};
|
||||
let msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
|
||||
let element = msg?.elements[0];
|
||||
const msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
|
||||
const element = msg?.elements[0];
|
||||
if (msg && element) {
|
||||
let recallEvent = await this.emitRecallMsg(msg, element);
|
||||
const recallEvent = await this.emitRecallMsg(msg, element);
|
||||
try {
|
||||
if (recallEvent) {
|
||||
await this.networkManager.emitEvent(recallEvent);
|
||||
@@ -303,7 +310,7 @@ export class NapCatOneBot11Adapter {
|
||||
this.context.logger.logError('处理消息撤回失败', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
msgListener.onKickedOffLine = async (kick) => {
|
||||
const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc);
|
||||
this.networkManager
|
||||
@@ -657,7 +664,7 @@ export class NapCatOneBot11Adapter {
|
||||
|
||||
private async emitRecallMsg(message: RawMessage, element: MessageElement) {
|
||||
const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' };
|
||||
let oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) ?? MessageUnique.createUniqueMsgId(peer, message.msgId);
|
||||
const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) ?? MessageUnique.createUniqueMsgId(peer, message.msgId);
|
||||
if (message.chatType == ChatType.KCHATTYPEC2C) {
|
||||
return await this.emitFriendRecallMsg(message, oriMessageId, element);
|
||||
} else if (message.chatType == ChatType.KCHATTYPEGROUP) {
|
||||
@@ -677,7 +684,7 @@ export class NapCatOneBot11Adapter {
|
||||
private async emitGroupRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) {
|
||||
const operatorUid = element.grayTipElement?.revokeElement.operatorUid;
|
||||
if (!operatorUid) return undefined;
|
||||
let operatorId = message.senderUin ?? await this.core.apis.UserApi.getUinByUidV2(operatorUid);
|
||||
const operatorId = message.senderUin ?? await this.core.apis.UserApi.getUinByUidV2(operatorUid);
|
||||
return new OB11GroupRecallNoticeEvent(
|
||||
this.core,
|
||||
+message.peerUin,
|
||||
|
@@ -102,7 +102,7 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
|
||||
if (req.path === '' || req.path === '/') {
|
||||
const hello = OB11Response.ok({});
|
||||
hello.message = 'NapCat4 Ss Running';
|
||||
return res.json(hello)
|
||||
return res.json(hello);
|
||||
}
|
||||
const actionName = req.path.split('/')[1];
|
||||
const action = this.actions.get(actionName);
|
||||
|
Reference in New Issue
Block a user