Compare commits

...

44 Commits

Author SHA1 Message Date
手瓜一十雪
74ca2e2e16 Merge pull request #485 from clansty/revert/get_forward_msg
revert: 还原 ob11 风格 get_forward_msg
2024-11-03 09:48:07 +08:00
Clansty
8ab550f2f5 revert: 还原 ob11 风格 get_forward_msg 2024-11-03 09:44:35 +08:00
pk5ls20
018aca4db2 fix: type hint 2024-11-03 02:45:58 +08:00
Mlikiowa
d4327166c1 release: v3.4.6 2024-11-02 05:20:34 +00:00
手瓜一十雪
fa25d2e779 feat: arm64 29271 2024-11-02 13:19:13 +08:00
手瓜一十雪
3ce1c3f0ec feat: support 3.2.13-29271-x64 2024-11-02 10:58:36 +08:00
手瓜一十雪
96dff5141e feat: packet 29271 2024-11-02 10:46:15 +08:00
手瓜一十雪
78d85d9965 feat: 29271 2024-11-02 10:02:39 +08:00
手瓜一十雪
37ec455b02 Merge pull request #484 from NapNeko/feat/ai-voice
feat: ai voice
2024-11-02 08:15:05 +08:00
pk5ls20
6ab82739a6 feat: ai voice 2024-11-02 01:51:57 +08:00
手瓜一十雪
a36917e7c0 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-01 12:13:24 +08:00
手瓜一十雪
21f3428b36 feat: support 6.9.58-28971 2024-11-01 12:13:04 +08:00
Mlikiowa
f8a487db25 release: v3.4.5 2024-10-31 12:30:25 +00:00
手瓜一十雪
73a859be04 fix: report self 2024-10-31 20:30:02 +08:00
手瓜一十雪
63bcee01a1 fix: report self 2024-10-31 20:27:17 +08:00
Mlikiowa
85b4966ba8 release: v3.4.4 2024-10-31 11:32:42 +00:00
手瓜一十雪
36c2c567b7 fix: #444 2024-10-31 19:32:10 +08:00
Mlikiowa
7b1ac224f6 release: v3.4.3 2024-10-31 10:18:21 +00:00
手瓜一十雪
34d9f04f15 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-10-31 18:17:53 +08:00
手瓜一十雪
be5da7cc6f fix: 正向ws异常推送事件问题 2024-10-31 18:17:41 +08:00
Mlikiowa
8d32ccb5d4 release: v3.4.2 2024-10-31 10:03:19 +00:00
手瓜一十雪
6acceb884c fix: #473 2024-10-31 18:00:55 +08:00
Hao Guan
4c834fd640 chore: Major获取Appid添加提示 (#480) 2024-10-31 16:10:27 +08:00
Mlikiowa
301278c7a9 release: v3.4.1 2024-10-31 00:36:50 +00:00
凌莞~(=^▽^=)
42ee83c54f feat: GetStrangerInfo 加回以前的完整信息 (#479) 2024-10-31 07:25:13 +08:00
Mlikiowa
e631f69621 release: v3.4.0 2024-10-30 13:11:06 +00:00
Nepenthe
ce8760a39a 修复<get_record>接口 (#478) 2024-10-30 21:09:32 +08:00
Mlikiowa
ff952956de release: v3.3.27 2024-10-30 07:28:17 +00:00
手瓜一十雪
28f3ff4971 fix: reply msg 大坐牢 #452 #477 2024-10-30 15:27:26 +08:00
手瓜一十雪
19e728c3cb feat: 提升全平台兼容性 2024-10-30 13:50:47 +08:00
Mlikiowa
269773ed6b release: v3.3.26 2024-10-30 01:23:01 +00:00
Hao Guan
e0d32417e1 chore: AppID for macOS 6.9.58-28971 (#476) 2024-10-30 09:20:32 +08:00
pk5ls20
9fa6083bed refactor: kill any (#475)
* refactor: kill any stage 1

* refactor: kill any stage 2

* refactor: kill any stage 3
2024-10-30 09:10:30 +08:00
手瓜一十雪
4d2fccdfb4 style: lint 2024-10-29 18:48:20 +08:00
Mlikiowa
c1c4bdfe94 release: v3.3.25 2024-10-29 10:42:12 +00:00
手瓜一十雪
8a0e9e8b61 release: v3.3.25 2024-10-29 18:41:39 +08:00
手瓜一十雪
1190e14171 docs: 调整文档优先级 2024-10-29 14:25:36 +08:00
Mlikiowa
00292b177a release: v3.3.22 2024-10-29 02:56:37 +00:00
手瓜一十雪
88de57f984 Merge pull request #472 from pohgxz/main
完善<set_input_status>接口
2024-10-29 10:53:29 +08:00
手瓜一十雪
61ddf38892 fix: Error 2024-10-29 10:52:50 +08:00
Nepenthe
52b3540ec3 修改<get_profile_like>接口 2024-10-29 07:51:16 +08:00
Nepenthe
5f831958c3 完善<set_input_status>接口 2024-10-28 23:21:49 +08:00
手瓜一十雪
c3d4698af3 try fix: error 2024-10-28 21:34:13 +08:00
Mlikiowa
bd6e83217d release: v3.3.21 2024-10-28 04:05:30 +00:00
49 changed files with 703 additions and 205 deletions

View File

@@ -25,7 +25,6 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
**首次使用**请务必查看如下文档看使用教程 **首次使用**请务必查看如下文档看使用教程
### 文档地址 ### 文档地址
[Github.IO](https://napneko.github.io/)
[Cloudflare.Worker](https://doc.napneko.icu/) [Cloudflare.Worker](https://doc.napneko.icu/)
@@ -33,6 +32,7 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
[Cloudflare.Pages](https://napneko.pages.dev/) [Cloudflare.Pages](https://napneko.pages.dev/)
[Github.IO](https://napneko.github.io/)
## 回家旅途 ## 回家旅途
[QQ Group](https://qm.qq.com/q/VfjAq5HIMS) [QQ Group](https://qm.qq.com/q/VfjAq5HIMS)

View File

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

View File

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

View File

@@ -9,6 +9,15 @@ interface InternalMapKey {
checker: ((...args: any[]) => boolean) | undefined; checker: ((...args: any[]) => boolean) | undefined;
} }
type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
type FuncKeys<T> = Extract<
{
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
}[keyof T],
string
>;
export type ListenerClassBase = Record<string, string>; export type ListenerClassBase = Record<string, string>;
export class NTEventWrapper { export class NTEventWrapper {
@@ -43,10 +52,8 @@ export class NTEventWrapper {
createEventFunction< createEventFunction<
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
// eslint-disable-next-line T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
// @ts-ignore
T extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
>(eventName: `${Service}/${ServiceMethod}`): T | undefined { >(eventName: `${Service}/${ServiceMethod}`): T | undefined {
const eventNameArr = eventName.split('/'); const eventNameArr = eventName.split('/');
type eventType = { type eventType = {
@@ -98,10 +105,8 @@ export class NTEventWrapper {
async callNoListenerEvent< async callNoListenerEvent<
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
// eslint-disable-next-line EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
// @ts-ignore
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
>( >(
serviceAndMethod: `${Service}/${ServiceMethod}`, serviceAndMethod: `${Service}/${ServiceMethod}`,
...args: Parameters<EventType> ...args: Parameters<EventType>
@@ -111,10 +116,8 @@ export class NTEventWrapper {
async registerListen< async registerListen<
Listener extends keyof ListenerNamingMapping, Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>, ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
// eslint-disable-next-line ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>,
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod],
>( >(
listenerAndMethod: `${Listener}/${ListenerMethod}`, listenerAndMethod: `${Listener}/${ListenerMethod}`,
waitTimes = 1, waitTimes = 1,
@@ -164,15 +167,11 @@ export class NTEventWrapper {
async callNormalEventV2< async callNormalEventV2<
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
Listener extends keyof ListenerNamingMapping, Listener extends keyof ListenerNamingMapping,
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>, ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
// eslint-disable-next-line EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
// @ts-ignore ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
// eslint-disable-next-line
// @ts-ignore
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
>( >(
serviceAndMethod: `${Service}/${ServiceMethod}`, serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`, listenerAndMethod: `${Listener}/${ListenerMethod}`,

View File

@@ -52,7 +52,7 @@ export class FileNapCatOneBotUUID {
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data; const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
return { return {
peer: { peer: {
chatType: chatType as any, chatType: +chatType,
peerUid: peerUid, peerUid: peerUid,
}, },
modelId, modelId,
@@ -89,7 +89,7 @@ export class FileNapCatOneBotUUID {
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data; const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
return { return {
peer: { peer: {
chatType: chatType as any, chatType: +chatType,
peerUid: peerUid, peerUid: peerUid,
}, },
msgId, msgId,
@@ -245,3 +245,36 @@ export function stringifyWithBigInt(obj: any) {
typeof value === 'bigint' ? value.toString() : value typeof value === 'bigint' ? value.toString() : value
); );
} }
export function parseAppidFromMajor(nodeMajor: string): string | undefined {
const hexSequence = "A4 09 00 00 00 35";
const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ""), "hex");
const filePath = path.resolve(nodeMajor);
const fileContent = fs.readFileSync(filePath);
let searchPosition = 0;
while (true) {
const index = fileContent.indexOf(sequenceBytes, searchPosition);
if (index === -1) {
break;
}
const start = index + sequenceBytes.length - 1;
const end = fileContent.indexOf(0x00, start);
if (end === -1) {
break;
}
const content = fileContent.subarray(start, end);
if (!content.every(byte => byte === 0x00)) {
try {
return content.toString("utf-8");
} catch (error) {
break;
}
}
searchPosition = end + 1;
}
return undefined;
}

View File

@@ -23,12 +23,12 @@ export class LimitedHashTable<K, V> {
} }
while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) { while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) {
const oldestKey = this.keyToValue.keys().next().value; const oldestKey = this.keyToValue.keys().next().value;
// @ts-ignore if (oldestKey !== undefined) {
this.valueToKey.delete(this.keyToValue.get(oldestKey)!); this.valueToKey.delete(this.keyToValue.get(oldestKey) as V);
// @ts-ignore
this.keyToValue.delete(oldestKey); this.keyToValue.delete(oldestKey);
} }
} }
}
getValue(key: K): V | undefined { getValue(key: K): V | undefined {
return this.keyToValue.get(key); return this.keyToValue.get(key);

View File

@@ -1,8 +1,9 @@
import fs from 'node:fs'; import fs from 'node:fs';
import { systemPlatform } from '@/common/system'; import { systemPlatform } from '@/common/system';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath } from './helper'; import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
import AppidTable from '@/core/external/appid.json'; import AppidTable from '@/core/external/appid.json';
import { LogWrapper } from './log'; import { LogWrapper } from './log';
import { getMajorPath } from '@/core';
export class QQBasicInfoWrapper { export class QQBasicInfoWrapper {
QQMainPath: string | undefined; QQMainPath: string | undefined;
@@ -72,6 +73,7 @@ export class QQBasicInfoWrapper {
} }
getAppidV2(): { appid: string; qua: string } { getAppidV2(): { appid: string; qua: string } {
// 通过已有表 性能好
const appidTbale = AppidTable as unknown as QQAppidTableType; const appidTbale = AppidTable as unknown as QQAppidTableType;
const fullVersion = this.getFullQQVesion(); const fullVersion = this.getFullQQVesion();
if (fullVersion) { if (fullVersion) {
@@ -80,10 +82,25 @@ export class QQBasicInfoWrapper {
return data; return data;
} }
} }
// 通过Major拉取 性能差
// else try {
let majorAppid = this.getAppidV2ByMajor(fullVersion);
if (majorAppid) {
this.context.logger.log(`[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat`);
return { appid: majorAppid, qua: this.getQUAFallback() };
}
} catch (error) {
this.context.logger.log(`[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常`);
}
// 最终兜底为老版本
this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`); this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,); this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,);
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() }; return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
} }
getAppidV2ByMajor(QQVersion: string) {
let majorPath = getMajorPath(QQVersion);
let appid = parseAppidFromMajor(majorPath);
return appid;
}
} }

View File

@@ -1 +1 @@
export const napCatVersion = '3.3.20'; export const napCatVersion = '3.4.6';

View File

@@ -54,7 +54,9 @@ export class NTQQGroupApi {
}, pskey); }, pskey);
} }
async getGroupShutUpMemberList(groupCode: string) { async getGroupShutUpMemberList(groupCode: string) {
return this.context.session.getGroupService().getGroupShutUpMemberList(groupCode); let data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
return (await data)[1];
} }
async clearGroupNotifiesUnreadCount(uk: boolean) { async clearGroupNotifiesUnreadCount(uk: boolean) {
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk); return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
@@ -320,10 +322,10 @@ export class NTQQGroupApi {
infos: Map<string, GroupMember>; infos: Map<string, GroupMember>;
finish: boolean; finish: boolean;
hasNext: boolean | undefined; hasNext: boolean | undefined;
}>{ }> {
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1'); const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId) const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
.catch(() => {}); .catch(() => { });
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num); const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) { if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg); throw new Error('获取群成员列表出错,' + result.errMsg);
@@ -337,21 +339,55 @@ export class NTQQGroupApi {
} }
this.context.session.getGroupService().destroyMemberListScene(sceneId); this.context.session.getGroupService().destroyMemberListScene(sceneId);
return { return {
infos: resMode2?.infos || result.result.infos, infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish, finish: result.result.finish,
hasNext: resMode2?.hasNext, hasNext: resMode2?.hasNext,
}; };
} }
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
infos: Map<string, GroupMember>;
finish: boolean;
hasNext: boolean | undefined;
listenerMode: boolean;
}> {
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId)
.catch(() => { });
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
if (result.errCode !== 0) {
throw new Error('获取群成员列表出错,' + result.errMsg);
}
let resMode2;
if (result.result.finish && result.result.infos.size === 0) {
const ret = (await once)?.[0];
if (ret) {
resMode2 = ret;
}
}
this.context.session.getGroupService().destroyMemberListScene(sceneId);
//console.log('GetGroupMembersV3 len :', result.result.infos.size, resMode2?.infos.size, groupQQ);
return {
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
finish: result.result.finish,
hasNext: resMode2?.hasNext,
listenerMode: resMode2?.hasNext !== undefined ? true : false
};
}
async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembersV2(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
let res = await this.tryGetGroupMembersV2(true, groupQQ); //console.log('getGroupMembers -->', groupQQ);
if (res.hasNext || !res.finish || res.infos.size === 0) { let res = await this.GetGroupMembersV3(groupQQ, num);
res = await this.tryGetGroupMembersV2(false, groupQQ, num); let ret = res.infos;
if (res.infos.size === 0 && !res.listenerMode) {
res = await this.GetGroupMembersV3(groupQQ, num);
ret = res.infos;
} }
if ((res.infos.size === 0 || res.infos.size === 30) && res.finish) { if (res.infos.size === 0) {
res = await this.tryGetGroupMembersV2(true, groupQQ, num); ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
} }
return res.infos; //console.log("<---------------")
return ret;
} }
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {

View File

@@ -3,6 +3,9 @@ import { InstanceContext, NapCatCore } from '@/core';
import { GeneralCallResult } from '@/core/services/common'; import { GeneralCallResult } from '@/core/services/common';
export class NTQQMsgApi { export class NTQQMsgApi {
getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) {
return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime);
}
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType // 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
@@ -22,7 +25,9 @@ export class NTQQMsgApi {
async sendShowInputStatusReq(peer: Peer, eventType: number) { async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid); return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
} }
async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) {
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) { async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa //注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count); return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
@@ -106,9 +111,9 @@ export class NTQQMsgApi {
pageLimit: 1, pageLimit: 1,
}); });
} }
//@deprecated // 客户端还在用别慌
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) {
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z); return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder);
} }
async getMsgExBySeq(peer: Peer, msgSeq: string) { async getMsgExBySeq(peer: Peer, msgSeq: string) {
const DateNow = Math.floor(Date.now() / 1000); const DateNow = Math.floor(Date.now() / 1000);

View File

@@ -1,10 +1,11 @@
import * as crypto from 'crypto';
import * as os from 'os'; import * as os from 'os';
import { ChatType, InstanceContext, NapCatCore } from '..'; import { ChatType, InstanceContext, NapCatCore } from '..';
import offset from '@/core/external/offset.json'; import offset from '@/core/external/offset.json';
import { PacketClient, RecvPacketData } from '@/core/packet/client'; import { PacketClient, RecvPacketData } from '@/core/packet/client';
import { PacketSession } from "@/core/packet/session"; import { PacketSession } from "@/core/packet/session";
import { OidbPacket, PacketHexStr } from "@/core/packet/packer"; import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
import { NapProtoMsg } from '@/core/packet/proto/NapProto'; import { NapProtoEncodeStructType, NapProtoDecodeStructType, NapProtoMsg } from '@/core/packet/proto/NapProto';
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202'; import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase'; import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2'; import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
@@ -20,6 +21,10 @@ import {
} from "@/core/packet/message/element"; } from "@/core/packet/message/element";
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp"; import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo"; import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
import { AIVoiceChatType, AIVoiceItemList } from "@/core/packet/entities/aiChat";
import { OidbSvcTrpcTcp0X929B_0Resp, OidbSvcTrpcTcp0X929D_0Resp } from "@/core/packet/proto/oidb/Oidb.0x929";
import { IndexNode, MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
interface OffsetType { interface OffsetType {
@@ -188,10 +193,54 @@ export class NTQQPacketApi {
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`; return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`;
} }
async sendGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const data = this.packetSession?.packer.packGroupPttFileDownloadReq(groupUin, node);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(NTV2RichMediaResp).decode(body);
const info = resp.download.info;
return `https://${info.domain}${info.urlPath}${resp.download.rKeyParam}`;
}
async sendMiniAppShareInfoReq(param: MiniAppReqParams) { async sendMiniAppShareInfoReq(param: MiniAppReqParams) {
const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param); const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param);
const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true); const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true);
const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex')) const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex'));
return JSON.parse(body.content.jsonContent) as MiniAppRawData; return JSON.parse(body.content.jsonContent) as MiniAppRawData;
} }
async sendFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType) : Promise<AIVoiceItemList[] | null> {
const data = this.packetSession?.packer.packFetchAiVoiceListReq(groupUin, chatType);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929D_0Resp).decode(body);
if (!resp.content) return null;
return resp.content.map((item) => {
return {
category: item.category,
voices: item.voices
};
});
}
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
let reqTime = 0;
const reqMaxTime = 30;
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
while (true) {
if (reqTime >= reqMaxTime) {
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
}
reqTime++;
const data = this.packetSession?.packer.packAiVoiceChatReq(groupUin, voiceId, text, chatType, sessionId);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(Buffer.from(ret.hex_data, 'hex'));
if (body.errorCode) {
throw new Error(`sendAiVoiceChatReq retCode: ${body.errorCode} error: ${body.errorMsg}`);
}
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929B_0Resp).decode(body.body);
if (!resp.msgInfo) continue;
return resp.msgInfo;
}
}
} }

View File

@@ -18,7 +18,7 @@ export class NTQQUserApi {
async getStatusByUid(uid: string) { async getStatusByUid(uid: string) {
return this.context.session.getProfileService().getStatus(uid); return this.context.session.getProfileService().getStatus(uid);
} }
async getProfileLike(uid: string) { async getProfileLike(uid: string, start: number, count: number) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({ return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [uid], friendUids: [uid],
basic: 1, basic: 1,
@@ -26,8 +26,8 @@ export class NTQQUserApi {
favorite: 0, favorite: 0,
userProfile: 1, userProfile: 1,
type: 2, type: 2,
start: 0, start: start,
limit: 20, limit: count,
}); });
} }
async fetchOtherProfileLike(uid: string) { async fetchOtherProfileLike(uid: string) {

View File

@@ -64,7 +64,7 @@ type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBu
type ElementBase< type ElementBase<
K extends keyof ElementFullBase, K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = {} S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
> = { > = {
[P in K]: [P in K]:
S[P] extends Array<infer U> S[P] extends Array<infer U>
@@ -822,6 +822,8 @@ export interface RawMessage {
elements: MessageElement[]; elements: MessageElement[];
sourceType: MsgSourceType; sourceType: MsgSourceType;
isOnlineMsg: boolean;
} }
export interface QueryMsgsParams { export interface QueryMsgsParams {
chatInfo: Peer; chatInfo: Peer;

View File

@@ -43,6 +43,50 @@ export enum GroupInviteType {
BYGROUPMEMBER, BYGROUPMEMBER,
BYDISCUSSMEMBER BYDISCUSSMEMBER
} }
export interface ShutUpGroupHonor {
[key: string]: number;
}
export interface ShutUpGroupMember {
uid: string;
qid: string;
uin: string;
nick: string;
remark: string;
cardType: number;
cardName: string;
role: number;
avatarPath: string;
shutUpTime: number;
isDelete: boolean;
isSpecialConcerned: boolean;
isSpecialShield: boolean;
isRobot: boolean;
groupHonor: ShutUpGroupHonor;
memberRealLevel: number;
memberLevel: number;
globalGroupLevel: number;
globalGroupPoint: number;
memberTitleId: number;
memberSpecialTitle: string;
specialTitleExpireTime: string;
userShowFlag: number;
userShowFlagNew: number;
richFlag: number;
mssVipType: number;
bigClubLevel: number;
bigClubFlag: number;
autoRemark: string;
creditLevel: number;
joinTime: number;
lastSpeakTime: number;
memberFlag: number;
memberFlagExt: number;
memberMobileFlag: number;
memberFlagExt2: number;
isSpecialShielded: boolean;
cardNameId: number;
}
export interface GroupNotify { export interface GroupNotify {
seq: string; // 通知序列号 seq: string; // 通知序列号

View File

@@ -51,12 +51,28 @@
"appid": 537249739, "appid": 537249739,
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B" "qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
}, },
"9.9.16-28971":{ "9.9.16-28971": {
"appid": 537249775, "appid": 537249775,
"qua": "V1_WIN_NQ_9.9.16_28971_GW_B" "qua": "V1_WIN_NQ_9.9.16_28971_GW_B"
}, },
"3.2.13-28971": { "3.2.13-28971": {
"appid": 537249848, "appid": 537249848,
"qua": "V1_LNX_NQ_3.2.13_28971_GW_B" "qua": "V1_LNX_NQ_3.2.13_28971_GW_B"
},
"6.9.58-28971": {
"appid": 537249826,
"qua": "V1_MAC_NQ_6.9.58_28971_GW_B"
},
"9.9.16-29271": {
"appid": 537249813,
"qua": "V1_WIN_NQ_9.9.16_29271_GW_B"
},
"3.2.13-29271": {
"appid": 537249913,
"qua": "V1_LNX_NQ_3.2.13_29271_GW_B"
},
"6.9.59-29271": {
"appid": 537249863,
"qua": "V1_MAC_NQ_6.9.59_29271_GW_B"
} }
} }

View File

@@ -1,4 +1,8 @@
{ {
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
},
"3.2.12-28418-x64": { "3.2.12-28418-x64": {
"recv": "A0723E0", "recv": "A0723E0",
"send": "A06EAE0" "send": "A06EAE0"
@@ -35,8 +39,20 @@
"send": "6E91318", "send": "6E91318",
"recv": "6E94B50" "recv": "6E94B50"
}, },
"6.9.56-28418-arm64": { "6.9.58-28971-arm64": {
"send": "4471360", "send": "449ACA0",
"recv": "4473BCC" "recv": "449D50C"
},
"9.9.16-29271-x64": {
"send": "3833510",
"recv": "3837944"
},
"3.2.13-29271-x64": {
"send": "A11E680",
"recv": "A121F80"
},
"3.2.13-29271-arm64": {
"send": "6ECA098",
"recv": "6ECD8D0"
} }
} }

View File

@@ -62,7 +62,26 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
process.dlopen(nativemodule, wrapperNodePath); process.dlopen(nativemodule, wrapperNodePath);
return nativemodule.exports; return nativemodule.exports;
} }
export function getMajorPath(QQVersion: string): string {
// major.node
let appPath;
if (os.platform() === 'darwin') {
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
} else if (os.platform() === 'linux') {
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
} else {
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
}
let majorPath = path.resolve(appPath, 'major.node');
if (!fs.existsSync(majorPath)) {
majorPath = path.join(appPath, `./resources/app/major.node`);
}
//老版本兼容 未来去掉
if (!fs.existsSync(majorPath)) {
majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`);
}
return majorPath;
}
export class NapCatCore { export class NapCatCore {
readonly context: InstanceContext; readonly context: InstanceContext;
readonly apis: StableNTApiWrapper; readonly apis: StableNTApiWrapper;
@@ -140,7 +159,7 @@ export class NapCatCore {
}; };
//await sleep(2500); //await sleep(2500);
this.context.session.getMsgService().addKernelMsgListener( this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any, proxiedListenerOf(msgListener, this.context.logger),
); );
const profileListener = new NodeIKernelProfileListener(); const profileListener = new NodeIKernelProfileListener();
@@ -236,7 +255,7 @@ export class NapCatCore {
} }
}; };
this.context.session.getGroupService().addKernelGroupListener( this.context.session.getGroupService().addKernelGroupListener(
proxiedListenerOf(groupListener, this.context.logger) as any, proxiedListenerOf(groupListener, this.context.logger),
); );
} }

View File

@@ -1,4 +1,4 @@
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/core/entities'; import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/entities';
export class NodeIKernelGroupListener { export class NodeIKernelGroupListener {
onGroupListInited(listEmpty: boolean): void { } onGroupListInited(listEmpty: boolean): void { }
@@ -80,6 +80,6 @@ export class NodeIKernelGroupListener {
onSearchMemberChange(...args: unknown[]) { onSearchMemberChange(...args: unknown[]) {
} }
onShutUpMemberListChanged(...args: unknown[]) { onShutUpMemberListChanged(groupCode: string, members: Array<ShutUpGroupMember>) {
} }
} }

View File

@@ -0,0 +1,16 @@
export enum AIVoiceChatType {
Unknown = 0,
Sound = 1,
Sing = 2
}
export interface AIVoiceItem {
voiceId: string;
voiceDisplayName: string;
voiceExampleUrl: string;
}
export interface AIVoiceItemList {
category: string;
voices: AIVoiceItem[];
}

View File

@@ -38,7 +38,7 @@ export abstract class MiniAppInfo {
}); });
MiniAppInfo.appMap.set("bili", this); MiniAppInfo.appMap.set("bili", this);
} }
} };
static WeiBo = new class extends MiniAppInfo { static WeiBo = new class extends MiniAppInfo {
constructor() { constructor() {
@@ -56,7 +56,7 @@ export abstract class MiniAppInfo {
}); });
MiniAppInfo.appMap.set("weibo", this); MiniAppInfo.appMap.set("weibo", this);
} }
} };
} }
export class MiniAppInfoHelper { export class MiniAppInfoHelper {

View File

@@ -1,13 +1,13 @@
import * as zlib from "node:zlib"; import * as zlib from "node:zlib";
import * as crypto from "node:crypto"; import * as crypto from "node:crypto";
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash"; import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
import { NapProtoMsg } from "@/core/packet/proto/NapProto"; import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase"; import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202"; import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2"; import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/packet/proto/oidb/Oidb.0XFE1_2"; import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
import { OidbSvcTrpcTcp0XED3_1 } from "@/core/packet/proto/oidb/Oidb.0xED3_1"; import { OidbSvcTrpcTcp0XED3_1 } from "@/core/packet/proto/oidb/Oidb.0xED3_1";
import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq"; import { IndexNode, NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action"; import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action"; import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
import { PacketMsgBuilder } from "@/core/packet/message/builder"; import { PacketMsgBuilder } from "@/core/packet/message/builder";
@@ -28,6 +28,8 @@ import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800
import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7"; import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7";
import { MiniAppReqParams } from "@/core/packet/entities/miniApp"; import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo"; import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
import {OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0} from "@/core/packet/proto/oidb/Oidb.0x929";
export type PacketHexStr = string & { readonly hexNya: unique symbol }; export type PacketHexStr = string & { readonly hexNya: unique symbol };
@@ -696,6 +698,37 @@ export class PacketPacker {
); );
} }
packGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
return this.packOidbPacket(0x126E, 200, new NapProtoMsg(NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 4,
command: 200
},
scene: {
requestType: 1,
businessType: 3,
sceneType: 2,
group: {
groupUin: groupUin
}
},
client: {
agentType: 2
}
},
download: {
node: node,
download: {
video: {
busiType: 0,
sceneType: 0,
}
}
}
}), true, false);
}
packGroupSignReq(uin: string, groupCode: string): OidbPacket { packGroupSignReq(uin: string, groupCode: string): OidbPacket {
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode( return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
{ {
@@ -742,6 +775,29 @@ export class PacketPacker {
} }
} }
) )
) );
}
packFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
return this.packOidbPacket(0x929D, 0,
new NapProtoMsg(OidbSvcTrpcTcp0X929D_0).encode({
groupUin: groupUin,
chatType: chatType
})
);
}
packAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType, sessionId: number): OidbPacket {
return this.packOidbPacket(0x929B, 0,
new NapProtoMsg(OidbSvcTrpcTcp0X929B_0).encode({
groupUin: groupUin,
voiceId: voiceId,
text: text,
chatType: chatType,
session: {
sessionId: sessionId
}
})
);
} }
} }

View File

@@ -61,35 +61,35 @@ export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageTyp
} }
} }
type ProtoFieldReturnType<T extends unknown, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R> type ProtoFieldReturnType<T, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
? ScalarTypeToTsType<S> ? ScalarTypeToTsType<S>
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>> : T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
? NonNullable<NapProtoStructType<ReturnType<S>, E>> ? NonNullable<NapProtoStructType<ReturnType<S>, E>>
: never; : never;
type RequiredFieldsBaseType<T extends unknown, E extends boolean> = { type RequiredFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]: [K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
T[K] extends { repeat: true } T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[] ? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E> : ProtoFieldReturnType<T[K], E>
} }
type OptionalFieldsBaseType<T extends unknown, E extends boolean> = { type OptionalFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?: [K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
T[K] extends { repeat: true } T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[] ? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E> : ProtoFieldReturnType<T[K], E>
} }
type RequiredFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>; type RequiredFieldsType<T, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
type OptionalFieldsType<T extends unknown, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>; type OptionalFieldsType<T, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
type NapProtoStructType<T extends unknown, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>; type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
export type NapProtoEncodeStructType<T extends unknown> = NapProtoStructType<T, true>; export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
export type NapProtoDecodeStructType<T extends unknown> = NapProtoStructType<T, false>; export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>(); const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();

View File

@@ -0,0 +1,42 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const OidbSvcTrpcTcp0X929D_0 = {
groupUin: ProtoField(1, ScalarType.UINT32),
chatType: ProtoField(2, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X929D_0Resp = {
content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true),
};
export const OidbSvcTrpcTcp0X929D_0RespContent = {
category: ProtoField(1, ScalarType.STRING),
voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true),
};
export const OidbSvcTrpcTcp0X929D_0RespContentVoice = {
voiceId: ProtoField(1, ScalarType.STRING),
voiceDisplayName: ProtoField(2, ScalarType.STRING),
voiceExampleUrl: ProtoField(3, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0X929B_0 = {
groupUin: ProtoField(1, ScalarType.UINT32),
voiceId: ProtoField(2, ScalarType.STRING),
text: ProtoField(3, ScalarType.STRING),
chatType: ProtoField(4, ScalarType.UINT32),
session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session),
};
export const OidbSvcTrpcTcp0X929B_0_Session = {
sessionId: ProtoField(1, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X929B_0Resp = {
statusCode: ProtoField(1, ScalarType.UINT32),
field2: ProtoField(2, ScalarType.UINT32, true),
field3: ProtoField(3, ScalarType.UINT32),
msgInfo: ProtoField(4, () => MsgInfo, true),
};

View File

@@ -4,6 +4,7 @@ import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcpBase = { export const OidbSvcTrpcTcpBase = {
command: ProtoField(1, ScalarType.UINT32), command: ProtoField(1, ScalarType.UINT32),
subCommand: ProtoField(2, ScalarType.UINT32), subCommand: ProtoField(2, ScalarType.UINT32),
errorCode: ProtoField(3, ScalarType.UINT32),
body: ProtoField(4, ScalarType.BYTES), body: ProtoField(4, ScalarType.BYTES),
errorMsg: ProtoField(5, ScalarType.STRING, true), errorMsg: ProtoField(5, ScalarType.STRING, true),
isReserved: ProtoField(12, ScalarType.UINT32) isReserved: ProtoField(12, ScalarType.UINT32)

View File

@@ -105,13 +105,13 @@ export interface NodeIKernelGroupService {
uid: string, uid: string,
index: number//0 index: number//0
}>, }>,
infos: unknown, infos: Map<string, GroupMember>,
finish: true, finish: true,
hasRobot: false hasRobot: false
} }
}>; }>;
setHeader(uid: string, path: string): unknown; setHeader(uid: string, path: string): Promise<GeneralCallResult>;
addKernelGroupListener(listener: NodeIKernelGroupListener): number; addKernelGroupListener(listener: NodeIKernelGroupListener): number;

View File

@@ -172,7 +172,7 @@ export interface NodeIKernelMsgService {
msgList: RawMessage[] msgList: RawMessage[]
}>; }>;
//@deprecated //@deprecated
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[] msgList: RawMessage[]
}>; }>;
@@ -186,27 +186,29 @@ export interface NodeIKernelMsgService {
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>; getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown; // 下面的msgid全部不真实
getSourceOfReplyMsg(peer: Peer, msgId: string, sourceSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown; //用法和聊天记录一样
getSourceOfReplyMsgV2(peer: Peer, rootMsgId: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown; getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown; getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string, replyMsgId: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: {
type: number, type: number,
subtype: Array<number> subtype: Array<number>
}): unknown; }): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{
type: number, type: number,
subtype: Array<number> subtype: Array<number>
}>): unknown; }>): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown; getMsgWithAbstractByFilterParam(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
queryMsgsWithFilter(...args: unknown[]): unknown; queryMsgsWithFilter(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
//queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>; //queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>;

View File

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

View File

@@ -0,0 +1,41 @@
import {ActionName} from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
const SchemaData = {
type: 'object',
properties: {
group_id: { type: ['number', 'string'] },
chat_type: { type: ['number', 'string'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
interface GetAiCharactersResponse {
type: string;
characters: {
character_id: string;
character_name: string;
preview_url: string;
}[];
}
export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharactersResponse[]> {
actionName = ActionName.GetAiCharacters;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawList = await this.core.apis.PacketApi.sendFetchAiVoiceListReq(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
return rawList?.map((item) => ({
type: item.category,
characters: item.voices.map((voice) => ({
character_id: voice.voiceId,
character_name: voice.voiceDisplayName,
preview_url: voice.voiceExampleUrl,
})),
})) ?? [];
}
}

View File

@@ -1,8 +1,8 @@
import {ActionName} from '../types'; import { ActionName } from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
import {MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams} from "@/core/packet/entities/miniApp"; import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
import {MiniAppInfo, MiniAppInfoHelper} from "@/core/packet/helper/miniAppHelper"; import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/helper/miniAppHelper";
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -11,21 +11,21 @@ const SchemaData = {
type: 'string', type: 'string',
enum: ['bili', 'weibo'] enum: ['bili', 'weibo']
}, },
title: {type: 'string'}, title: { type: 'string' },
desc: {type: 'string'}, desc: { type: 'string' },
picUrl: {type: 'string'}, picUrl: { type: 'string' },
jumpUrl: {type: 'string'}, jumpUrl: { type: 'string' },
iconUrl: {type: 'string'}, iconUrl: { type: 'string' },
sdkId: {type: 'string'}, sdkId: { type: 'string' },
appId: {type: 'string'}, appId: { type: 'string' },
scene: {type: ['number', 'string']}, scene: { type: ['number', 'string'] },
templateType: {type: ['number', 'string']}, templateType: { type: ['number', 'string'] },
businessType: {type: ['number', 'string']}, businessType: { type: ['number', 'string'] },
verType: {type: ['number', 'string']}, verType: { type: ['number', 'string'] },
shareType: {type: ['number', 'string']}, shareType: { type: ['number', 'string'] },
versionId: {type: 'string'}, versionId: { type: 'string' },
withShareTicket: {type: ['number', 'string']}, withShareTicket: { type: ['number', 'string'] },
rawArkData: {type: ['boolean', 'string']} rawArkData: { type: ['boolean', 'string'] }
}, },
oneOf: [ oneOf: [
{ {
@@ -75,11 +75,11 @@ export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
versionId: versionId, versionId: versionId,
withShareTicket: +withShareTicket withShareTicket: +withShareTicket
} }
) );
} }
const arkData = await this.core.apis.PacketApi.sendMiniAppShareInfoReq(reqParam); const arkData = await this.core.apis.PacketApi.sendMiniAppShareInfoReq(reqParam);
return { return {
data: Boolean(payload.rawArkData) ? arkData : MiniAppInfoHelper.RawToSend(arkData) data: payload.rawArkData ? arkData : MiniAppInfoHelper.RawToSend(arkData)
} };
} }
} }

View File

@@ -1,15 +1,22 @@
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { ActionName } from '../types'; import { ActionName, BaseCheckResult } from '../types';
export class GetProfileLike extends BaseAction<void, any> { interface Payload {
start: number,
count: number
}
export class GetProfileLike extends BaseAction<Payload, any> {
actionName = ActionName.GetProfileLike; actionName = ActionName.GetProfileLike;
async _handle(payload: void) { async _handle(payload: Payload) {
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid); const start = payload.start ? Number(payload.start) : 0;
const listdata: any[] = ret.info.userLikeInfos[0].favoriteInfo.userInfos; const count = payload.count ? Number(payload.count) : 10;
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
const listdata: any[] = ret.info.userLikeInfos[0].voteInfo.userInfos;
for (let i = 0; i < listdata.length; i++) { for (let i = 0; i < listdata.length; i++) {
listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || ''); listdata[i].uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(listdata[i].uid)) || '');
} }
return listdata; return ret.info.userLikeInfos[0].voteInfo;
} }
} }

View File

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

View File

@@ -38,11 +38,10 @@ export default class SetAvatar extends BaseAction<Payload, null> {
throw `头像${payload.file}设置失败,api无返回`; throw `头像${payload.file}设置失败,api无返回`;
} }
// log(`头像设置返回:${JSON.stringify(ret)}`) // log(`头像设置返回:${JSON.stringify(ret)}`)
// @ts-ignore if (ret.result as number == 1004022) {
if (ret['result'] == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式`; throw `头像${payload.file}设置失败,文件可能不是图片格式`;
} else if (ret['result'] != 0) { } else if (ret.result != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`; throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
} }
} else { } else {
fs.unlink(path, () => { }); fs.unlink(path, () => { });

View File

@@ -5,9 +5,11 @@ import { promises as fs } from 'fs';
import { decode } from 'silk-wasm'; import { decode } from 'silk-wasm';
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg'; const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
interface Payload extends GetFilePayload { const out_format = ['mp3' , 'amr' , 'wma' , 'm4a' , 'spx' , 'ogg' , 'wav' , 'flac'];
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac';
} type Payload = {
out_format : string
} & GetFilePayload
export default class GetRecord extends GetFileBase { export default class GetRecord extends GetFileBase {
actionName = ActionName.GetRecord; actionName = ActionName.GetRecord;
@@ -17,12 +19,19 @@ export default class GetRecord extends GetFileBase {
if (payload.out_format && typeof payload.out_format === 'string') { if (payload.out_format && typeof payload.out_format === 'string') {
const inputFile = res.file; const inputFile = res.file;
if (!inputFile) throw new Error('file not found'); if (!inputFile) throw new Error('file not found');
if (!out_format.includes(payload.out_format)) {
throw new Error('转换失败 out_format 字段可能格式不正确');
}
const pcmFile = `${inputFile}.pcm`; const pcmFile = `${inputFile}.pcm`;
const outputFile = `${inputFile}.${payload.out_format}`; const outputFile = `${inputFile}.${payload.out_format}`;
try { try {
await fs.access(inputFile); await fs.access(inputFile);
try {
await fs.access(outputFile);
} catch (error) {
await this.decodeFile(inputFile, pcmFile); await this.decodeFile(inputFile, pcmFile);
await this.convertFile(pcmFile, outputFile, payload.out_format); await this.convertFile(pcmFile, outputFile, payload.out_format);
}
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' }); const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
res.file = outputFile; res.file = outputFile;
res.url = outputFile; res.url = outputFile;
@@ -48,7 +57,8 @@ export default class GetRecord extends GetFileBase {
private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> { private convertFile(inputFile: string, outputFile: string, format: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const ffmpeg = spawn(FFMPEG_PATH, ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile]); const params = format === 'amr' ? ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, '-ar', '8000', '-b:a', '12.2k', outputFile] : ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFile, outputFile];
const ffmpeg = spawn(FFMPEG_PATH, params);
ffmpeg.on('close', (code) => { ffmpeg.on('close', (code) => {
if (code === 0) { if (code === 0) {

View File

@@ -73,14 +73,12 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
const singleMsg = data.msgList[0]; const singleMsg = data.msgList[0];
const resMsg = await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array');//强制array 以便处理 const resMsg = await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array');//强制array 以便处理
if (!resMsg) { if (!(resMsg?.message?.[0] as OB11MessageForward)?.data?.content) {
throw new Error('找不到相关的聊天记录'); throw new Error('找不到相关的聊天记录');
} }
//if (this.obContext.configLoader.configData.messagePostFormat === 'array') { return {
//提取 messages: (resMsg?.message?.[0] as OB11MessageForward)?.data?.content
const realmsg = ((await this.parseForward([resMsg]))[0].data.message as OB11MessageNode[])[0].data.message; };
//里面都是offline消息 id都是0 没得说话
return { message: realmsg };
//} //}
// return { message: resMsg }; // return { message: resMsg };

View File

@@ -25,6 +25,11 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
if (!uid) uid = extendData.detail.uid; if (!uid) uid = extendData.detail.uid;
const info = (await this.core.apis.UserApi.getUserDetailInfo(uid)); const info = (await this.core.apis.UserApi.getUserDetailInfo(uid));
return { return {
...extendData.detail.simpleInfo.coreInfo,
...extendData.detail.commonExt ?? {},
...extendData.detail.simpleInfo.baseInfo,
...extendData.detail.simpleInfo.relationFlags ?? {},
...extendData.detail.simpleInfo.status ?? {},
user_id: parseInt(extendData.detail.uin) ?? 0, user_id: parseInt(extendData.detail.uin) ?? 0,
uid: info.uid ?? uid, uid: info.uid ?? uid,
nickname: extendData.detail.simpleInfo.coreInfo.nick, nickname: extendData.detail.simpleInfo.coreInfo.nick,
@@ -33,7 +38,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel), qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel),
sex: OB11Entities.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown, sex: OB11Entities.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown,
long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick, long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick,
reg_time: extendData.detail.commonExt.regTime ?? info.regTime, reg_time: extendData.detail.commonExt?.regTime ?? info.regTime,
is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag, is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag,
is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag, is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag,
vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel, vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel,

View File

@@ -31,15 +31,15 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
} }
if (path) { if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断 await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path) as any; const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path);
fs.unlink(path, () => { }); fs.unlink(path, () => { });
if (!ret) { if (!ret) {
throw `头像${payload.file}设置失败,api无返回`; throw `头像${payload.file}设置失败,api无返回`;
} }
if (ret['result'] == 1004022) { if (ret.result as number == 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`; throw `头像${payload.file}设置失败,文件可能不是图片格式或权限不足`;
} else if (ret['result'] != 0) { } else if (ret.result != 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`; throw `头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`;
} }
return ret; return ret;
} else { } else {

View File

@@ -0,0 +1,28 @@
import {ActionName} from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
import {IndexNode} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
const SchemaData = {
type: 'object',
properties: {
character: { type: ['string'] },
group_id: { type: ['number', 'string'] },
text: { type: 'string' },
},
required: ['character', 'group_id', 'text'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetAiRecord extends GetPacketStatusDepends<Payload, string> {
actionName = ActionName.GetAiRecord;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
return await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
}
}

View File

@@ -13,7 +13,7 @@ const SchemaData = {
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
export class GetGroupShutList extends BaseAction<Payload, OB11Group> { export class GetGroupShutList extends BaseAction<Payload, any> {
actionName = ActionName.GetGroupShutList; actionName = ActionName.GetGroupShutList;
payloadSchema = SchemaData; payloadSchema = SchemaData;

View File

@@ -0,0 +1,40 @@
import {ActionName} from '../types';
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
import {uri2local} from "@/common/file";
import {ChatType, Peer} from "@/core";
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
import {IndexNode} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
const SchemaData = {
type: 'object',
properties: {
character: { type: ['string'] },
group_id: { type: ['number', 'string'] },
text: { type: 'string' },
},
required: ['character', 'group_id', 'text'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
message_id: string
}> {
actionName = ActionName.SendGroupAiRecord;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
const url = await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
const { path, fileName, errMsg, success} = (await uri2local(this.core.NapCatTempPath, url));
if (!success) {
throw new Error(errMsg);
}
const peer = {chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString()} as Peer;
const element = await this.core.apis.FileApi.createValidSendPttElement(path);
const sendRes = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [element], [path]);
return {message_id: sendRes.msgId};
}
}

View File

@@ -99,6 +99,9 @@ import { GoCQHTTPGetModelShow } from './go-cqhttp/GoCQHTTPGetModelShow';
import { GoCQHTTPSetModelShow } from './go-cqhttp/GoCQHTTPSetModelShow'; import { GoCQHTTPSetModelShow } from './go-cqhttp/GoCQHTTPSetModelShow';
import { GoCQHTTPDeleteFriend } from './go-cqhttp/GoCQHTTPDeleteFriend'; import { GoCQHTTPDeleteFriend } from './go-cqhttp/GoCQHTTPDeleteFriend';
import { GetMiniAppArk } from "@/onebot/action/extends/GetMiniAppArk"; import { GetMiniAppArk } from "@/onebot/action/extends/GetMiniAppArk";
import { GetAiRecord } from "@/onebot/action/group/GetAiRecord";
import { SendGroupAiRecord } from "@/onebot/action/group/SendGroupAiRecord";
import { GetAiCharacters } from "@/onebot/action/extends/GetAiCharacters";
export type ActionMap = Map<string, BaseAction<any, any>>; export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -212,6 +215,9 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetGroupShutList(obContext, core), new GetGroupShutList(obContext, core),
new GetGroupFileUrl(obContext, core), new GetGroupFileUrl(obContext, core),
new GetMiniAppArk(obContext, core), new GetMiniAppArk(obContext, core),
new GetAiRecord(obContext, core),
new SendGroupAiRecord(obContext, core),
new GetAiCharacters(obContext, core),
]; ];
const actionMap = new Map(); const actionMap = new Map();
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

@@ -236,12 +236,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: RawMessage | null, message: RawMessage | null,
res_id?: string res_id?: string
}> { }> {
let returnMsg: RawMessage | undefined, res_id: string | undefined;
const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt); const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt);
res_id = uploadReturnData?.res_id; const res_id = uploadReturnData?.res_id;
const finallySendElements = uploadReturnData?.finallySendElements; const finallySendElements = uploadReturnData?.finallySendElements;
if (!finallySendElements) throw Error('转发消息失败,生成节点为空'); if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined); const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], [], true).catch(_ => undefined);
return { message: returnMsg ?? null, res_id }; return { message: returnMsg ?? null, res_id };
} }
@@ -276,7 +275,6 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分'); logger.logError.bind(this.core.context.logger)('子消息中包含非node消息 跳过不合法部分');
continue; continue;
} }
// @ts-ignore
const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node)); const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
if (nodeMsg) { if (nodeMsg) {
nodeMsgIds.push(nodeMsg.message!.msgId); nodeMsgIds.push(nodeMsg.message!.msgId);

View File

@@ -138,4 +138,7 @@ export enum ActionName {
SetGroupSign = "set_group_sign", SetGroupSign = "set_group_sign",
GetMiniAppArk = "get_mini_app_ark", GetMiniAppArk = "get_mini_app_ark",
// UploadForwardMsg = "upload_forward_msg", // UploadForwardMsg = "upload_forward_msg",
GetAiRecord = "get_ai_record",
GetAiCharacters = "get_ai_characters",
SendGroupAiRecord = "send_group_ai_record",
} }

View File

@@ -24,7 +24,6 @@ import {
OB11MessageData, OB11MessageData,
OB11MessageDataType, OB11MessageDataType,
OB11MessageFileBase, OB11MessageFileBase,
OB11MessageForward,
} from '@/onebot'; } from '@/onebot';
import { OB11Entities } from '@/onebot/entities'; import { OB11Entities } from '@/onebot/entities';
import { EventType } from '@/onebot/event/OB11BaseEvent'; import { EventType } from '@/onebot/event/OB11BaseEvent';
@@ -218,20 +217,34 @@ export class OneBotMsgApi {
if (records.peerUin === '284840486' || records.peerUin === '1094950020') { if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
return createReplyData(records.msgId); return createReplyData(records.msgId);
} }
let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr])).msgList; let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom); let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) { if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
// 我猜测可能是时间参数未对上 导致找不到引用消息 或者msgList 存在问题 this.core.context.logger.logError.bind(this.core.context.logger)(
this.core.context.logger.logWarn.bind(this.core.context.logger)( '筛选结果,筛选消息失败,将使用Fallback-1 Seq: ',
'初次筛选消息失败,获取不到引用的消息 Seq:', element.replayMsgSeq,
',消息长度:',
replyMsgList.length
);
replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, element.replayMsgSeq, 1, true, true)).msgList;
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
}
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logWarn.bind(this.core.context.logger)(
'筛选消息失败,将使用Fallback-2 Seq:',
element.replayMsgSeq, element.replayMsgSeq,
',消息长度:', ',消息长度:',
replyMsgList.length replyMsgList.length
); );
// 再次筛选
replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList; replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList;
replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom); replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
} }
// 丢弃该消息段
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) { if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
this.core.context.logger.logError.bind(this.core.context.logger)( this.core.context.logger.logError.bind(this.core.context.logger)(
'最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ', '最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ',
@@ -327,11 +340,11 @@ export class OneBotMsgApi {
}, },
multiForwardMsgElement: async (_, msg) => { multiForwardMsgElement: async (_, msg) => {
const message_data: OB11MessageForward = { // const message_data: OB11MessageForward = {
data: {} as any, // data: {} as any,
type: OB11MessageDataType.forward, // type: OB11MessageDataType.forward,
}; // };
message_data.data.id = msg.msgId; // message_data.data.id = msg.msgId;
const parentMsgPeer = msg.parentMsgPeer ?? { const parentMsgPeer = msg.parentMsgPeer ?? {
chatType: msg.chatType, chatType: msg.chatType,
guildId: '', guildId: '',
@@ -743,9 +756,12 @@ export class OneBotMsgApi {
async (element) => { async (element) => {
for (const key in element) { for (const key in element) {
if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) { if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) {
const parsedElement = await this.rawToOb11Converters[key]?.( const converters = this.rawToOb11Converters[key] as (
// eslint-disable-next-line element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
// @ts-ignore msg: RawMessage,
elementWrapper: MessageElement,
) => PromiseLike<OB11MessageData | null>;
const parsedElement = await converters?.(
element[key], element[key],
msg, msg,
element, element,
@@ -794,9 +810,11 @@ export class OneBotMsgApi {
if (ignoreTypes.includes(sendMsg.type)) { if (ignoreTypes.includes(sendMsg.type)) {
continue; continue;
} }
const callResult = this.ob11ToRawConverters[sendMsg.type]( const converter = this.ob11ToRawConverters[sendMsg.type] as (
// eslint-disable-next-line sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
// @ts-ignore context: MessageContext,
) => Promise<SendMessageElement | undefined>;
const callResult = converter(
sendMsg, sendMsg,
{ peer, deleteAfterSentFiles }, { peer, deleteAfterSentFiles },
)?.catch(undefined); )?.catch(undefined);

View File

@@ -68,13 +68,13 @@ export function encodeCQCode(data: OB11MessageData) {
let result = '[CQ:' + data.type; let result = '[CQ:' + data.type;
for (const name in data.data) { for (const name in data.data) {
const value = (data.data as any)[name]; const value = (data.data as Record<string, unknown>)[name];
if (value === undefined) { if (value === undefined) {
continue; continue;
} }
try { try {
const text = value.toString(); const text = value?.toString();
result += `,${name}=${CQCodeEscape(text)}`; if (text) result += `,${name}=${CQCodeEscape(text)}`;
} catch (error) { } catch (error) {
// If it can't be converted, skip this name-value pair // If it can't be converted, skip this name-value pair
} }

View File

@@ -311,15 +311,19 @@ export class NapCatOneBot11Adapter {
} }
}; };
const msgIdSend = new LRUCache<string, boolean>(100); const msgIdSend = new LRUCache<string, number>(100);
const recallMsgs = new LRUCache<string, boolean>(100); const recallMsgs = new LRUCache<string, boolean>(100);
msgListener.onAddSendMsg = async msg => {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
msgIdSend.put(msg.msgId, 0);
}
};
msgListener.onMsgInfoListUpdate = async msgList => { msgListener.onMsgInfoListUpdate = async msgList => {
this.emitRecallMsg(msgList, recallMsgs) this.emitRecallMsg(msgList, recallMsgs)
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e)); .catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) { for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && !msgIdSend.get(msg.msgId)) { if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && msgIdSend.get(msg.msgId) == 0) {
msgIdSend.put(msg.msgId, true); msgIdSend.put(msg.msgId, 1);
// 完成后再post // 完成后再post
this.apis.MsgApi.parseMessage(msg) this.apis.MsgApi.parseMessage(msg)
.then((ob11Msg) => { .then((ob11Msg) => {
@@ -341,7 +345,7 @@ export class NapCatOneBot11Adapter {
}; };
this.context.session.getMsgService().addKernelMsgListener( this.context.session.getMsgService().addKernelMsgListener(
proxiedListenerOf(msgListener, this.context.logger) as any, proxiedListenerOf(msgListener, this.context.logger),
); );
} }
@@ -370,7 +374,7 @@ export class NapCatOneBot11Adapter {
}; };
this.context.session.getBuddyService().addKernelBuddyListener( this.context.session.getBuddyService().addKernelBuddyListener(
proxiedListenerOf(buddyListener, this.context.logger) as any, proxiedListenerOf(buddyListener, this.context.logger),
); );
} }
@@ -523,7 +527,7 @@ export class NapCatOneBot11Adapter {
); );
} }
private async emitMsg(message: RawMessage) { private async emitMsg(message: RawMessage, parseEvent: boolean = true) {
const { debug, reportSelfMessage, messagePostFormat } = this.configLoader.configData; const { debug, reportSelfMessage, messagePostFormat } = this.configLoader.configData;
this.context.logger.logDebug('收到新消息 RawMessage', message); this.context.logger.logDebug('收到新消息 RawMessage', message);
this.apis.MsgApi.parseMessage(message, messagePostFormat).then((ob11Msg) => { this.apis.MsgApi.parseMessage(message, messagePostFormat).then((ob11Msg) => {

View File

@@ -64,9 +64,9 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
}); });
this.app.use((req, res, next) => this.authorize(this.token, req, res, next)); this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
// @ts-ignore this.app.use(async (req, res, _) => {
this.app.use((req, res) => this.handleRequest(req, res)); await this.handleRequest(req, res);
});
this.server.listen(this.port, () => { this.server.listen(this.port, () => {
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`); this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`);
}); });

View File

@@ -143,7 +143,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
private registerHeartBeat() { private registerHeartBeat() {
this.heartbeatIntervalId = setInterval(() => { this.heartbeatIntervalId = setInterval(() => {
this.wsClientsMutex.runExclusive(async () => { this.wsClientsMutex.runExclusive(async () => {
this.wsClients.forEach((wsClient) => { this.wsClientWithEvent.forEach((wsClient) => {
if (wsClient.readyState === WebSocket.OPEN) { if (wsClient.readyState === WebSocket.OPEN) {
wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatInterval, this.core.selfInfo.online, true))); wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatInterval, this.core.selfInfo.online, true)));
} }

View File

@@ -165,7 +165,7 @@ export async function NCoreInitShell() {
logger.logError.bind(logger)('[Core] [Login] Login Error , ErrInfo: ', args); logger.logError.bind(logger)('[Core] [Login] Login Error , ErrInfo: ', args);
}; };
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any); loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
const isConnect = loginService.connect(); const isConnect = loginService.connect();
if (!isConnect) { if (!isConnect) {
logger.logError.bind(logger)('核心登录服务连接失败!'); logger.logError.bind(logger)('核心登录服务连接失败!');

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem( SettingItem(
'<span id="napcat-update-title">Napcat</span>', '<span id="napcat-update-title">Napcat</span>',
void 0, void 0,
SettingButton("V3.3.20", "napcat-update-button", "secondary") SettingButton("V3.4.6", "napcat-update-button", "secondary")
) )
]), ]),
SettingList([ SettingList([