refactor: hook some api

This commit is contained in:
linyuchen 2024-02-08 01:45:56 +08:00
parent 3c4db1d9d9
commit 2c55d84b9f
9 changed files with 351 additions and 38 deletions

View File

@ -12,7 +12,7 @@ export enum ChatType {
temp = 100
}
export interface GroupMemberInfo {
export interface GroupMember {
avatarPath: string;
cardName: string;
cardType: number;
@ -43,7 +43,7 @@ export interface User {
export interface Group {
uid: string; // 群号
name: string;
members?: GroupMemberInfo[];
members?: GroupMember[];
}
export interface Peer {

4
src/global.d.ts vendored
View File

@ -1,7 +1,7 @@
import {
Config,
Group,
GroupMemberInfo,
GroupMember,
MessageElement,
Peer,
PostDataSendMsg, PttElement, RawMessage,
@ -27,7 +27,7 @@ declare var LLAPI: {
recallMessage(peer: Peer, msgIds: string[]): Promise<void>;
getGroupsList(forced: boolean): Promise<Group[]>
getFriendsList(forced: boolean): Promise<User[]>
getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Map<string, GroupMemberInfo> }}>
getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Map<string, GroupMember> }}>
getPeer(): Promise<Peer>
add_qmenu(func: (qContextMenu: Node)=>void): void
Ptt2Text(msgId:string, peer: Peer, elements: MessageElement[]): Promise<any>

View File

@ -22,7 +22,8 @@ import {checkFileReceived, CONFIG_DIR, file2base64, getConfigUtil, isGIF, log} f
import {friends, groups, msgHistory, selfInfo} from "../common/data";
import {} from "../global";
import {hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook";
import {OB11Construct} from "../onebot11/construct";
import {OB11Constructor} from "../onebot11/constructor";
import {NTQQApi} from "../ntqqapi/ntcall";
const fs = require('fs');
@ -174,7 +175,7 @@ function onLoad() {
function postRawMsg(msgList:RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig();
for (const message of msgList) {
OB11Construct.constructMessage(message).then((msg) => {
OB11Constructor.message(message).then((msg) => {
if (debug) {
msg.raw = message;
}
@ -206,6 +207,12 @@ function onLoad() {
log("report self message error: ", e.toString())
}
})
setTimeout(()=>{
NTQQApi.getSelfInfo().then(r=>{
log(r);
})
}, 10000)
}

View File

@ -0,0 +1,98 @@
import {AtType} from "../common/types";
import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement} from "./types";
import {NTQQApi} from "./ntcall";
export class SendMsgElementConstructor {
static text(content: string): SendTextElement {
return {
elementType: ElementType.TEXT,
elementId: "",
textElement: {
content,
atType: AtType.notAt,
atUid: "",
atTinyId: "",
atNtUid: "",
},
};
}
static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement {
return {
elementType: ElementType.TEXT,
elementId: "",
textElement: {
content: `@${atName}`,
atType,
atUid,
atTinyId: "",
atNtUid,
},
};
}
reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement {
return {
elementType: ElementType.REPLY,
elementId: "",
replyElement: {
replayMsgSeq: msgSeq, // raw.msgSeq
replayMsgId: msgId, // raw.msgId
senderUin: senderUin,
senderUinStr: senderUinStr,
}
}
}
async pic(picPath: string): Promise<SendPicElement>{
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath);
const imageSize = await NTQQApi.getImageSize(picPath);
const picElement = {
md5HexStr: md5,
fileSize: fileSize,
picWidth: imageSize.width,
picHeight: imageSize.height,
fileName: fileName,
sourcePath: path,
original: true,
picType: 1001,
picSubType: 0,
fileUuid: "",
fileSubId: "",
thumbFileSize: 0,
summary: "",
};
return {
elementType: ElementType.PIC,
elementId: "",
picElement
};
}
async ptt(pttPath: string):Promise<SendPttElement> {
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath);
return {
elementType: ElementType.PTT,
elementId: "",
pttElement: {
fileName: fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize,
duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算
formatType: 1,
voiceType: 1,
voiceChangeType: 0,
canConvert2Text: true,
waveAmplitudes: [
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
],
fileSubId: "",
playState: 1,
autoConvertText: 0,
}
};
}
}

View File

@ -4,22 +4,25 @@ import {NTQQApiClass} from "./ntcall";
import {RawMessage} from "../common/types";
import {msgHistory} from "../common/data";
export let hookApiCallbacks: Record<string, (apiReturn: any)=>void>={}
export enum ReceiveCmd {
UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate",
NEW_MSG = "nodeIKernelMsgListener/onRecvMsg",
SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg"
}
interface NTQQApiReturnData extends Array<any> {
interface NTQQApiReturnData<PayloadType=unknown> extends Array<any> {
0: {
"type": "request",
"eventName": NTQQApiClass
"eventName": NTQQApiClass,
"callbackId"?: string
},
1:
{
cmdName: ReceiveCmd,
cmdType: "event",
payload: unknown
payload: PayloadType
}[]
}
@ -35,15 +38,27 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
if (args?.[1] instanceof Array) {
for (let receiveData of args?.[1]) {
const ntQQApiMethodName = receiveData.cmdName;
// log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData))
log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData))
for (let hook of receiveHooks) {
if (hook.method === ntQQApiMethodName) {
hook.hookFunc(receiveData.payload);
new Promise((resolve, reject) => {
hook.hookFunc(receiveData.payload);
}).then()
}
}
}
}
if (args[0]?.callbackId){
// log("hookApiCallback", hookApiCallbacks, args)
const callbackId = args[0].callbackId;
if (hookApiCallbacks[callbackId]){
// log("callback found")
new Promise((resolve, reject) => {
hookApiCallbacks[callbackId](args[1]);
}).then()
delete hookApiCallbacks[callbackId];
}
}
return originalSend.call(window.webContents, channel, ...args);
}
window.webContents.send = patchSend;

View File

@ -1,5 +1,9 @@
import {ipcMain} from "electron";
import {v4 as uuidv4} from "uuid";
import {hookApiCallbacks} from "./hook";
import {log} from "../common/utils";
import {ChatType, Group, GroupMember, User} from "../common/types";
import {SendMessageElement} from "./types";
interface IPCReceiveEvent {
eventName: string
@ -15,12 +19,27 @@ export type IPCReceiveDetail = [
export enum NTQQApiClass {
NT_API = "ns-ntApi",
NT_FS_API = "ns-FsApi",
FS_API = "ns-FsApi",
GLOBAL_DATA = "ns-GlobalDataApi"
}
export enum NTQQApiMethod {
LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike",
UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate"
UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate",
SELF_INFO = "fetchAuthData",
FRIENDS = "nodeIKernelProfileService/getBuddyProfileList",
GROUPS = "nodeIKernelGroupService/getGroupList",
GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene",
GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList",
USER_INFO = "nodeIKernelProfileService/getUserSimpleInfo",
FILE_TYPE = "getFileType",
FILE_MD5 = "getFileMd5",
FILE_COPY = "copyFile",
IMAGE_SIZE = "getImageSizeFromPath",
FILE_SIZE = "getFileSize",
MEDIA_FILE_PATH = "nodeIKernelMsgService/getRichMediaFilePathForGuild",
RECALL_MSG = "nodeIKernelMsgService/recallMsg",
SEND_MSG = "nodeIKernelMsgService/sendMsg",
}
enum NTQQApiChannel {
@ -29,19 +48,22 @@ enum NTQQApiChannel {
IPC_UP_1 = "IPC_UP_1",
}
interface Peer {
chatType: ChatType
peerUid: string // 是uid还是QQ号
guildId?: ""
}
function callNTQQApi(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[]=[]) {
function callNTQQApi<ReturnType>(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[] = []) {
const uuid = uuidv4();
return new Promise((resolve, reject) => {
// log("callNTQQApi", channel, className, methodName, args, uuid)
return new Promise((resolve: (data: ReturnType) => void, reject) => {
// log("callNTQQApiPromise", channel, className, methodName, args, uuid)
hookApiCallbacks[uuid] = resolve;
ipcMain.emit(
channel,
{
sender: {
send: (args: [string, IPCReceiveEvent, IPCReceiveDetail]) => {
resolve(args)
},
},
},
{},
{type: 'request', callbackId: uuid, eventName: className + "-" + channel[channel.length - 1]},
[methodName, ...args],
)
@ -53,13 +75,114 @@ export class NTQQApi {
// static likeFriend = defineNTQQApi<void>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND)
static likeFriend(uid: string, count = 1) {
return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND, [{
doLikeUserInfo: {
friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0
}
},
doLikeUserInfo: {
friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0
}
},
null])
}
static getSelfInfo() {
return callNTQQApi<User>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.GLOBAL_DATA, NTQQApiMethod.SELF_INFO, [])
}
static getFriends(forced = false) {
return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{force_update: forced}, undefined])
}
static getGroups(forced = false) {
return callNTQQApi<Group[]>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{force_update: forced}, undefined])
}
static async getGroupMembers(groupQQ: string, num = 3000) {
const sceneId = callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{
groupCode: groupQQ,
scene: "groupMemberList_MainWindow"
}]
)
return callNTQQApi<GroupMember[]>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBERS,
[{
sceneId: sceneId,
num: num
},
null
])
}
static async getUserInfo(uid: string) {
const result = await callNTQQApi<[{
payload: { profiles: Map<string, User> }
}]>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO,
[{force: true, uids: [uid]}, undefined])
return new Promise<User>(resolve => {
resolve(result[0].payload.profiles.get(uid))
})
}
static getFileType(filePath: string) {
return callNTQQApi<{
ext: string
}>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_TYPE, [filePath])
}
static getFileMd5(filePath: string) {
return callNTQQApi<string>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_MD5, [filePath])
}
static copyFile(filePath: string, destPath: string) {
return callNTQQApi<string>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_COPY, [filePath, destPath])
}
static getImageSize(filePath: string) {
return callNTQQApi<{
width: number,
height: number
}>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.IMAGE_SIZE, [filePath])
}
static getFileSize(filePath: string) {
return callNTQQApi<number>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_SIZE, [filePath])
}
// 上传文件到QQ的文件夹
static async uploadFile(filePath: string) {
const md5 = await NTQQApi.getFileMd5(filePath);
const fileName = `${md5}.${(await NTQQApi.getFileType(filePath)).ext}`;
const mediaPath = await callNTQQApi<string>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.MEDIA_FILE_PATH, [{
path_info: {
md5HexStr: md5,
fileName: fileName,
elementType: 2,
elementSubType: 0,
thumbSize: 0,
needCreate: true,
downloadType: 1,
file_uuid: ""
}
}])
await NTQQApi.copyFile(filePath, mediaPath);
const fileSize = await NTQQApi.getFileSize(filePath);
return {
md5,
fileName,
path: mediaPath,
fileSize
}
}
static recallMsg(peer: Peer, msgIds: string[]){
return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.RECALL_MSG, [{peer, msgIds}, null])
}
static sendMsg(peer: Peer, msgElements: SendMessageElement){
return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.SEND_MSG, [{
msgId: "0",
peer, msgElements,
msgAttributeInfos: new Map(),
}, null])
}
}

70
src/ntqqapi/types.ts Normal file
View File

@ -0,0 +1,70 @@
export enum ElementType {
TEXT = 1,
PIC = 2,
PTT = 4,
REPLY = 7,
}
export interface SendTextElement {
elementType: ElementType.TEXT,
elementId: "",
textElement: {
content: string,
atType: number,
atUid: string,
atTinyId: string,
atNtUid: string,
}
}
export interface SendPttElement {
elementType: ElementType.PTT,
elementId: "",
pttElement: {
fileName: string,
filePath: string,
md5HexStr: string,
fileSize: number,
duration: number,
formatType: number,
voiceType: number,
voiceChangeType: number,
canConvert2Text: boolean,
waveAmplitudes: number[],
fileSubId: "",
playState: number,
autoConvertText: number,
}
}
export interface SendPicElement {
elementType: ElementType.PIC,
elementId: "",
picElement: {
md5HexStr: string,
fileSize: number,
picWidth: number,
picHeight: number,
fileName: string,
sourcePath: string,
original: boolean,
picType: number,
picSubType: number,
fileUuid: string,
fileSubId: string,
thumbFileSize: number,
summary: string,
}
}
export interface SendReplyElement {
elementType: ElementType.REPLY,
elementId: "",
replyElement: {
replayMsgSeq: string,
replayMsgId: string,
senderUin: string,
senderUinStr: string,
}
}
export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement

View File

@ -4,8 +4,8 @@ import {getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo} fro
import {file2base64, getConfigUtil} from "../common/utils";
export class OB11Construct {
static async constructMessage(msg: RawMessage): Promise<OB11Message> {
export class OB11Constructor {
static async message(msg: RawMessage): Promise<OB11Message> {
const {enableBase64} = getConfigUtil().getConfig()
const message_type = msg.chatType == ChatType.group ? "group" : "private";
const resMsg: OB11Message = {
@ -30,7 +30,7 @@ export class OB11Construct {
resMsg.group_id = msg.peerUin
const member = getGroupMember(msg.peerUin, msg.senderUin);
if (member) {
resMsg.sender.role = OB11Construct.constructGroupMemberRole(member.role);
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
}
} else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = "friend"
@ -119,7 +119,7 @@ export class OB11Construct {
return resMsg;
}
static constructGroupMemberRole(role: number): OB11GroupMemberRole {
static groupMemberRole(role: number): OB11GroupMemberRole {
return {
4: OB11GroupMemberRole.owner,
3: OB11GroupMemberRole.admin,

View File

@ -10,7 +10,7 @@ import {sendIPCRecallQQMsg, sendIPCSendQQMsg} from "../main/ipcsend";
import {PostDataSendMsg} from "../common/types";
import {friends, groups, msgHistory, selfInfo} from "../common/data";
import {OB11ApiName, OB11Message, OB11Return, OB11MessageData} from "../onebot11/types";
import {OB11Construct} from "../onebot11/construct";
import {OB11Constructor} from "../onebot11/constructor";
// @SiberianHusky 2021-08-15
@ -126,7 +126,7 @@ function handlePost(jsonData: any, handleSendResult: (data: OB11Return<any>) =>
user_display_name: member.cardName || member.nick,
nickname: member.nick,
card: member.cardName,
role: OB11Construct.constructGroupMemberRole(member.role),
role: OB11Constructor.groupMemberRole(member.role),
}
} else if (jsonData.action == "get_group_member_list") {
let group = groups.find(group => group.uid == jsonData.params.group_id)
@ -138,7 +138,7 @@ function handlePost(jsonData: any, handleSendResult: (data: OB11Return<any>) =>
user_display_name: member.cardName || member.nick,
nickname: member.nick,
card: member.cardName,
role: OB11Construct.constructGroupMemberRole(member.role),
role: OB11Constructor.groupMemberRole(member.role),
}
}) || []
@ -251,7 +251,7 @@ export function startExpress(port: number) {
registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => {
const msg = msgHistory[payload.message_id.toString()]
if (msg) {
const msgData = await OB11Construct.constructMessage(msg);
const msgData = await OB11Constructor.message(msg);
return constructReturnData(0, msgData)
} else {
return constructReturnData(1, {}, "消息不存在")