Merge branch 'main' into extend

This commit is contained in:
手瓜一十雪
2024-08-19 19:03:31 +08:00
15 changed files with 82 additions and 58 deletions

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC Without Social media promotion LICENSE
Version 2, June 1991 Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., 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 does not normally print such an announcement, your work based on
the Program is not required to print an announcement.) 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 These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program, identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in and can be reasonably considered independent and separate works in

View File

@@ -4,7 +4,7 @@
--- ---
## To Be Continued ## To Be Continued
愿我们在更好的开源世界相遇... 当前版本请使用内核构建版本(版本号最后的五位数)为 26702 至 26909 的 PC NTQQ 运行。
## 项目介绍 ## 项目介绍
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。 NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ", "name": "NapCatQQ",
"slug": "NapCat.Framework", "slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现", "description": "高性能的 OneBot 11 协议实现",
"version": "2.0.30", "version": "2.0.33",
"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": "2.0.30", "version": "2.0.33",
"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

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

View File

@@ -253,22 +253,45 @@ export class NTQQGroupApi {
} }
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) { async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange']; //type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'];
type EventType = NodeIKernelGroupService['getMemberInfo']; type EventType = NodeIKernelGroupService['getMemberInfo'];
// NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate', // NTEventDispatch.CreatListenerFunction('NodeIKernelGroupListener/onGroupMemberInfoUpdate',
//return napCatCore.session.getGroupService().getMemberInfo(GroupCode, [uid], forced); //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', 'NodeIKernelGroupListener/onMemberInfoChange',
1, 1,
5000, forced ? 5000 : 250,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => { (params) => {
return groupCode == GroupCode && members.has(uid); 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>> { async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {

View File

@@ -100,7 +100,7 @@ export class NTQQUserApi {
return retData; return retData;
} }
async fetchUserDetailInfo(uid: string) { async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo']; type EventService = NodeIKernelProfileService['fetchUserDetailInfo'];
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged']; type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'];
const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>( const [_retData, profile] = await this.core.eventWrapper.CallNormalEvent<EventService, EventListener>(
@@ -111,7 +111,7 @@ export class NTQQUserApi {
(profile) => profile.uid === uid, (profile) => profile.uid === uid,
'BuddyProfileStore', 'BuddyProfileStore',
[uid], [uid],
UserDetailSource.KSERVER, mode,
[ProfileBizType.KALL], [ProfileBizType.KALL],
); );
const RetUser: User = { const RetUser: User = {
@@ -120,14 +120,20 @@ export class NTQQUserApi {
...profile.simpleInfo.vasInfo, ...profile.simpleInfo.vasInfo,
...profile.commonExt, ...profile.commonExt,
...profile.simpleInfo.baseInfo, ...profile.simpleInfo.baseInfo,
qqLevel: profile.commonExt.qqLevel, qqLevel: profile.commonExt?.qqLevel,
age: profile.simpleInfo.baseInfo.age,
pendantId: '', pendantId: '',
}; };
return RetUser; return RetUser;
} }
async getUserDetailInfo(uid: string) { async getUserDetailInfo(uid: string) {
return this.fetchUserDetailInfo(uid); const ret = await this.fetchUserDetailInfo(uid, UserDetailSource.KDB);
if (ret.uin === '0') {
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.')
return await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
}
return ret;
} }
async modifySelfProfile(param: ModifyProfileParams) { async modifySelfProfile(param: ModifyProfileParams) {
@@ -187,9 +193,9 @@ export class NTQQUserApi {
//后期改成流水线处理 //后期改成流水线处理
async getUidByUinV2(Uin: string) { 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; 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; if (uid) return uid;
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin); uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
if (uid) return uid; if (uid) return uid;
@@ -201,9 +207,9 @@ export class NTQQUserApi {
//后期改成流水线处理 //后期改成流水线处理
async getUinByUidV2(Uid: string) { async getUinByUidV2(Uid: string) {
let uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid); let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
if (uin) return uin; if (uin) return uin;
uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid); uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
if (uin) return uin; if (uin) return uin;
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid); uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
if (uin) return uin; if (uin) return uin;

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import { OB11Constructor } from '../../helper/data';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
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 { GroupMember } from '@/core';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -23,39 +24,25 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
async _handle(payload: Payload) { async _handle(payload: Payload) {
const NTQQUserApi = this.CoreContext.apis.UserApi; const NTQQUserApi = this.CoreContext.apis.UserApi;
const NTQQGroupApi = this.CoreContext.apis.GroupApi; const NTQQGroupApi = this.CoreContext.apis.GroupApi;
const NTQQWebApi = this.CoreContext.apis.WebApi;
const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache; const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache;
const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString()); const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw (`Uin2Uid Error ${payload.user_id}不存在`); if (!uid) throw (`Uin2Uid Error ${payload.user_id}不存在`);
const member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache); const [member, info] = await Promise.allSettled([
if (!member) throw (`群(${payload.group_id})成员${payload.user_id}不存在`); NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache),
try { NTQQUserApi.getUserDetailInfo(uid),
const info = (await NTQQUserApi.getUserDetailInfo(member.uid)); ]);
this.CoreContext.context.logger.logDebug('群成员详细信息结果', info); if (member.status !== 'fulfilled') throw (`群(${payload.group_id})成员${payload.user_id}不存在 ${member.reason}`);
Object.assign(member, info); if (info.status === 'fulfilled') {
} catch (e) { this.CoreContext.context.logger.logDebug("群成员详细信息结果", info.value);
this.CoreContext.context.logger.logDebug('获取群成员详细信息失败, 只能返回基础信息', e); Object.assign(member, info.value);
} else {
this.CoreContext.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息 ${info.reason}`);
} }
const date = Math.round(Date.now() / 1000); const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member); const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member.value as GroupMember);
const SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), this.CoreContext.selfInfo.uid, isNocache); const Member = await this.CoreContext.apis.GroupApi.getGroupMember(payload.group_id.toString(), retMember.user_id);
let isPrivilege = false; retMember.last_sent_time = parseInt(Member?.lastSpeakTime || date.toString());
if (SelfInfoInGroup) { retMember.join_time = parseInt(Member?.joinTime || date.toString());
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; return retMember;
} }
} }

View File

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

View File

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

View File

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

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("V2.0.30", "napcat-update-button", "secondary") SettingButton("V2.0.33", "napcat-update-button", "secondary")
) )
]), ]),
SettingList([ SettingList([