Compare commits

..

24 Commits

Author SHA1 Message Date
Alen
698624b4dc release: v2.0.32 2024-08-19 10:09:53 +08:00
Alen
5c1df82076 Merge pull request #278 from cnxysoft/upmain
fix: 多处修改
2024-08-19 10:07:16 +08:00
Alen
5d649b3687 perf: 优化API效率
优化get_group_member_info效率
2024-08-19 00:55:32 +08:00
Alen
a6a3d71155 Revert "perf: API优化"
This reverts commit 1cc9d501ab.
2024-08-18 18:49:46 +08:00
Alen
1cc9d501ab perf: API优化
优化get_group_member_info在查询非群员时的效率
2024-08-18 14:33:35 +08:00
Alen
7a98025df8 fix: 引用消息失败
修复 (疑似)旧设备引用消息验证失败
2024-08-18 01:40:14 +08:00
Alen
44d6ed5e80 release: v2.0.31 2024-08-17 23:15:52 +08:00
手瓜一十雪
b5f2226bef Merge pull request #270 from cnxysoft/upmain
fix: Uid转Uin
2024-08-17 22:53:42 +08:00
Alen
ddbffe55d2 Merge branch 'main' into upmain 2024-08-17 22:19:49 +08:00
手瓜一十雪
9676b1d0e9 fix 2024-08-17 18:13:43 +08:00
手瓜一十雪
8142d3bfeb fix: 打错啦 2024-08-17 18:12:43 +08:00
手瓜一十雪
755ad27a0a Merge pull request #269 from gfhdhytghd/patch-1
修改许可证以禁止宣传
2024-08-17 15:45:20 +08:00
手瓜一十雪
5afa2dcdf1 chore: 复活赛打赢啦 2024-08-17 15:37:19 +08:00
lin
7eb80646ba Update LICENSE
修改License,从法律层面禁止在公共社交媒体宣传
2024-08-17 14:11:57 +08:00
Alen
6fd24e57d3 Merge branch 'main' into upmain 2024-08-17 13:24:36 +08:00
手瓜一十雪
22c90adb47 chore: docs 2024-08-17 12:27:53 +08:00
Alen
df0c6fafbe fix: Uid转Uin
修复设置管理、禁言等数个API失效
对查询企业陌生人信息进行容错
2024-08-17 00:17:51 +08:00
手瓜一十雪
dc30321b04 Update README.md 2024-08-17 00:16:23 +08:00
手瓜一十雪
63dd98d2df release: 2.0.30 2024-08-16 21:44:34 +08:00
手瓜一十雪
caaa6ed506 feat: support SetInputStatus 2024-08-16 20:35:05 +08:00
手瓜一十雪
caf23792cb Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-08-16 13:01:38 +08:00
手瓜一十雪
e430db20aa release: 2.0.29 2024-08-16 13:01:30 +08:00
手瓜一十雪
6fc5da9b67 Merge pull request #267 from Fripine/fix/OB11GroupRequestEvent
fix: wrong user_id in GroupRequestEvent
2024-08-16 13:00:32 +08:00
Fripine
f428e57724 fix: GroupRequestEvent 2024-08-16 12:57:42 +08:00
23 changed files with 139 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC Without Social media promotion LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
@@ -111,6 +111,10 @@ above, provided that you also meet all of these conditions:
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
dYou may use this software in accordance with the above terms,
but you are not allowed to promote this project or your projects
based on this project on any public social media.
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in

View File

@@ -3,6 +3,8 @@
</div>
---
## To Be Continued
当前版本请使用低于27187 (不包含) 高于26702 (包含) 的QQ版本运行
## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.0.28';
export const napcat_version = '2.0.32';
export class NapCatPathWrapper {
binaryPath: string;

File diff suppressed because one or more lines are too long

View File

@@ -253,22 +253,45 @@ export class NTQQGroupApi {
}
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
//type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
type EventType = NodeIKernelGroupService['getMemberInfo'];
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced);
const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
const Listener = this.core.eventWrapper.RegisterListen<(params: any) => void>
(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
1,
5000,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
return groupCode == GroupCode && members.has(uid);
forced ? 5000 : 500,
(params) => {
return params === GroupCode;
},
GroupCode, [uid], forced,
);
return _members.get(uid);
const EventFunc = this.core.eventWrapper.createEventFunction<EventType>('NodeIKernelGroupService/getMemberInfo');
const retData = await EventFunc!(GroupCode, [uid], forced);
if (retData.result !== 0) {
throw new Error(`获取群成员信息失败: ${retData.errMsg}`);
}
const result = await Listener as unknown;
let member: GroupMember | undefined;
if (Array.isArray(result) && result?.[2] instanceof Map) {
let members = result[2] as Map<string, GroupMember>;
member = members.get(uid);
};
return member;
// 原本的方法: (no_cache 下效率很高, cache 下效率一致)
// const [, , , _members] = await this.core.eventWrapper.CallNormalEvent<EventType, ListenerType>
// (
// 'NodeIKernelGroupService/getMemberInfo',
// 'NodeIKernelGroupListener/onMemberInfoChange',
// 1,
// 5000,
// (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
// return groupCode == GroupCode && members.has(uid);
// },
// GroupCode, [uid], forced,
// );
// return _members.get(uid);
}
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {

View File

@@ -16,6 +16,9 @@ export class NTQQMsgApi {
return this.context.session.getMsgService().fetchLongMsg(peer, msgId);
}
async sendShowInputStatusReq(peer: Peer, eventType: number) {
return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid);
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//console.log(peer, msgSeq, emojiId, emojiType, count);
//注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged M likiowa

View File

@@ -100,7 +100,7 @@ export class NTQQUserApi {
return retData;
}
async fetchUserDetailInfo(uid: string) {
async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
@@ -111,7 +111,7 @@ export class NTQQUserApi {
(profile) => profile.uid === uid,
'BuddyProfileStore',
[uid],
UserDetailSource.KSERVER,
mode,
[ProfileBizType.KALL],
);
const RetUser: User = {
@@ -121,13 +121,19 @@ export class NTQQUserApi {
...profile.commonExt,
...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel,
age: profile.simpleInfo.baseInfo.age,
pendantId: '',
};
return RetUser;
}
async getUserDetailInfo(uid: string) {
return this.fetchUserDetailInfo(uid);
const ret = await this.fetchUserDetailInfo(uid, UserDetailSource.KDB);
if (ret.uin === '0') {
console.log('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.')
return await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
}
return ret;
}
async modifySelfProfile(param: ModifyProfileParams) {
@@ -187,9 +193,9 @@ export class NTQQUserApi {
//后期改成流水线处理
async getUidByUinV2(Uin: string) {
let uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
let uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
if (uid) return uid;
uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
if (uid) return uid;
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid;

View File

@@ -1,4 +1,4 @@
import { QQLevel, Sex } from './user';
import { QQLevel, Sex, User } from './user';
export enum GroupListUpdateType {
REFRESHALL,
@@ -65,6 +65,7 @@ export interface GroupMember {
uin: string; // QQ号
isRobot: boolean;
sex?: Sex;
age?: number;
qqLevel?: QQLevel;
isChangeRole: boolean;
joinTime: string;

View File

@@ -231,6 +231,7 @@ export interface User {
longNick?: string; // 签名
remark?: string;
sex?: Sex;
age?: number;
qqLevel?: QQLevel;
qid?: string;
birthday_year?: number;

View File

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

View File

@@ -34,7 +34,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
sex: OB11UserSex.unknown,
age: extendData.detail.simpleInfo.baseInfo.age || 0,
qid: extendData.detail.simpleInfo.baseInfo.qid,
level: calcQQLevel(extendData.detail.commonExt.qqLevel) || 0,
level: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? 0) || 0,
login_days: 0,
uid: ''
};

View File

@@ -38,22 +38,6 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
}
const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
const SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), this.CoreContext.selfInfo.uid, isNocache);
let isPrivilege = false;
if (SelfInfoInGroup) {
isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4;
}
if (isPrivilege) {
const webGroupMembers = await NTQQWebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.join_time = webGroupMembers[i]?.join_time;
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
}
}
}
retMember.last_sent_time = parseInt((await this.CoreContext.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
retMember.join_time = parseInt((await this.CoreContext.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
return retMember;

View File

@@ -78,6 +78,7 @@ import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import GetGuildProfile from './guild/GetGuildProfile';
import SetModelShow from './go-cqhttp/SetModelShow';
import { SetInputStatus } from './extends/SetInputStatus';
export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -165,6 +166,7 @@ export function createActionMap(onebotContext: NapCatOneBot11Adapter, coreContex
new GoCQHTTPUploadPrivateFile(onebotContext, coreContext),
new GetGuildProfile(onebotContext, coreContext),
new SetModelShow(onebotContext, coreContext),
new SetInputStatus(onebotContext, coreContext),
];
const actionMap = new Map();
for (const action of actionHandlers) {

View File

@@ -56,7 +56,7 @@ const _handlers: {
if (atQQ === 'all') return SendMsgElementConstructor.at(coreContext, atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member
// Mlikiowa V2.0.28 Refactor Todo
// Mlikiowa V2.0.32 Refactor Todo
const uid = await coreContext.apis.UserApi.getUidByUinV2(`${atQQ}`);
if (!uid) throw new Error('Get Uid Error');
return SendMsgElementConstructor.at(coreContext, atQQ, uid, AtType.atUser, '');
@@ -161,7 +161,7 @@ const _handlers: {
} else {
postData = data;
}
// Mlikiowa V2.0.28 Refactor Todo
// Mlikiowa V2.0.32 Refactor Todo
const signUrl = obContext.configLoader.configData.musicSignUrl;
if (!signUrl) {
if (data.type === 'qq') {

View File

@@ -88,11 +88,9 @@ async function createContext(coreContext: NapCatCore, payload: OB11PostSendMsg,
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
const NTQQGroupApi = coreContext.apis.GroupApi;
const NTQQFriendApi = coreContext.apis.FriendApi;
const NTQQUserApi = coreContext.apis.UserApi;
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
const group = (await NTQQGroupApi.getGroups()).find(e => e.groupCode == payload.group_id?.toString());
return {
chatType: ChatType.group,
peerUid: payload.group_id.toString(),

View File

@@ -106,5 +106,6 @@ export enum ActionName {
TestApi01 = 'test_api_01',
FetchEmojiLike = 'fetch_emoji_like',
GetGuildProfile = "get_guild_service_profile",
SetModelShow = "_set_model_show"
SetModelShow = "_set_model_show",
SetInputStatus = "set_input_status"
}

View File

@@ -149,6 +149,7 @@ export class OB11Constructor {
message_data['type'] = OB11MessageDataType.reply;
//log("收到回复消息", element.replyElement);
try {
let oldMsgFlag = false;
const records = msg.records.find(msgRecord => msgRecord.msgId === element?.replyElement?.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
@@ -163,12 +164,13 @@ export class OB11Constructor {
chatType: msg.chatType,
}, element.replyElement.replayMsgSeq, 1, true, true)).msgList.find(msg => msg.msgRandom === records.msgRandom);
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
if (!replyMsg && records.msgRandom === '0') oldMsgFlag = true;
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
}
if (msg.peerUin == '284840486') {
//合并消息内侧 消息具体定位不到
}
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
if ((!replyMsg || (records.msgRandom !== replyMsg.msgRandom && !oldMsgFlag || (oldMsgFlag && records.msgSeq !== replyMsg.msgSeq))) && msg.peerUin !== '284840486') {
throw new Error('回复消息消息验证失败');
}
message_data['data']['id'] = MessageUnique.createMsg({
@@ -417,7 +419,7 @@ export class OB11Constructor {
return;
}
//log("group msg", msg);
// Mlikiowa V2.0.28 Refactor Todo
// Mlikiowa V2.0.32 Refactor Todo
// if (msg.senderUin && msg.senderUin !== '0') {
// const member = await getGroupMember(msg.peerUid, msg.senderUin);
// if (member && member.cardName !== msg.sendMemberName) {
@@ -709,7 +711,7 @@ export class OB11Constructor {
nickname: member.nick,
card: member.cardName,
sex: OB11Constructor.sex(member.sex!),
age: 0,
age: member.age ?? 0,
area: '',
level: '0',
qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,

View File

@@ -18,10 +18,11 @@ import {
SendVideoElement,
viedo_type,
} from '@/core';
import * as fsnormal from 'node:fs';
import { promises as fs } from 'node:fs';
import ffmpeg from 'fluent-ffmpeg';
import { calculateFileMD5, isGIF } from '@/common/utils/file';
import { getVideoInfo } from '@/common/utils/video';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/utils/video';
import { encodeSilk } from '@/common/utils/audio';
import faceConfig from '@/core/external/face_config.json';
import * as pathLib from 'node:path';
@@ -140,11 +141,7 @@ export class SendMsgElementConstructor {
}
static async video(coreContext: NapCatCore, filePath: string, fileName: string = '', diyThumbPath: string = '', videotype: viedo_type = viedo_type.VIDEO_FORMAT_MP4): Promise<SendVideoElement> {
const NTQQGroupApi = coreContext.apis.GroupApi;
const NTQQUserApi = coreContext.apis.UserApi;
const NTQQFileApi = coreContext.apis.FileApi;
const NTQQMsgApi = coreContext.apis.MsgApi;
const NTQQFriendApi = coreContext.apis.FriendApi;
const logger = coreContext.context.logger;
const { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) {
@@ -179,7 +176,8 @@ export class SendMsgElementConstructor {
resolve(thumbPath);
}).catch(reject);
} else {
resolve(undefined);
fsnormal.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
resolve(thumbPath);
}
})
.screenshots({
@@ -224,6 +222,21 @@ export class SendMsgElementConstructor {
// sourceVideoCodecFormat: 2
},
};
// "fileElement": {
// "fileMd5": "",
// "fileName": "1.mp4",
// "filePath": "C:\\Users\\nanae\\OneDrive\\Desktop\\1.mp4",
// "fileSize": "1847007",
// "picHeight": 1280,
// "picWidth": 720,
// "picThumbPath": {},
// "file10MMd5": "",
// "fileSha": "",
// "fileSha3": "",
// "fileUuid": "",
// "fileSubId": "",
// "thumbFileSize": 750
// }
return element;
}

View File

@@ -396,12 +396,12 @@ export class NapCatOneBot11Adapter {
} catch (e) {
this.context.logger.logError('获取加群人QQ号失败 Uid:', notify.user1.uid, e);
}
} else if (notify.type == GroupNotifyTypes.INVITE_ME) {
} else if (notify.type == GroupNotifyTypes.INVITE_ME && notify.status == 1) {
this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
const groupInviteEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid)),
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid)),
'invite',
notify.postscript,
flag,

View File

@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V2.0.28', 'napcat-update-button', 'secondary'),
SettingButton('V2.0.32', 'napcat-update-button', 'secondary'),
),
]),
SettingList([

View File

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