From 1b04cd48433c053636b97ca3cb2f8d991df266fd Mon Sep 17 00:00:00 2001 From: zhangzemeng Date: Wed, 7 Feb 2024 10:44:42 +0800 Subject: [PATCH 01/14] =?UTF-8?q?fix:=20preload.ts=20sendSendMsgResult=20?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/channels.ts | 1 + src/common/types.ts | 1 - src/global.d.ts | 3 ++- src/main/ipcsend.ts | 34 +++++++++++++++++++++++----------- src/preload.ts | 15 +++++++++++---- src/renderer.ts | 16 +++++++++------- 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/common/channels.ts b/src/common/channels.ts index 70ac91a..76d1e08 100644 --- a/src/common/channels.ts +++ b/src/common/channels.ts @@ -1,4 +1,5 @@ export const CHANNEL_SEND_MSG = "llonebot_send_msg" +export const CHANNEL_SEND_BACK_MSG = "llonebot_send_back_msg" export const CHANNEL_RECALL_MSG = "llonebot_recall_msg" export const CHANNEL_GET_CONFIG = "llonebot_get_config" export const CHANNEL_SET_CONFIG = "llonebot_set_config" diff --git a/src/common/types.ts b/src/common/types.ts index ae79f8a..64acaee 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -136,7 +136,6 @@ export interface PostDataSendMsg { user_id: string, group_id: string, message?: OB11MessageData[]; - ipc_uuid?: string } export interface Config { diff --git a/src/global.d.ts b/src/global.d.ts index b035248..4be039f 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -8,6 +8,7 @@ import { SelfInfo, User } from "./common/types"; +import { SendIPCMsgSession } from "./main/ipcsend"; import {OB11Return, OB11MessageData, OB11SendMsgReturn} from "./onebot11/types"; @@ -36,7 +37,7 @@ declare var LLAPI: { declare var llonebot: { postData: (data: any) => void - listenSendMessage: (handle: (msg: PostDataSendMsg) => void) => void + listenSendMessage: (handle: (msg: SendIPCMsgSession) => void) => void listenRecallMessage: (handle: (msg: {message_id: string}) => void) => void updateGroups: (groups: Group[]) => void updateFriends: (friends: User[]) => void diff --git a/src/main/ipcsend.ts b/src/main/ipcsend.ts index 1a026af..db640c3 100644 --- a/src/main/ipcsend.ts +++ b/src/main/ipcsend.ts @@ -1,6 +1,6 @@ import {ipcMain, webContents} from 'electron'; import {PostDataSendMsg} from "../common/types"; -import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG} from "../common/channels"; +import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG,CHANNEL_SEND_BACK_MSG} from "../common/channels"; import {v4 as uuid4} from "uuid"; import {log} from "../common/utils"; @@ -18,21 +18,33 @@ function sendIPCMsg(channel: string, data: any) { } } +export interface SendIPCMsgSession { + id: string + data: T +} export function sendIPCSendQQMsg(postData: PostDataSendMsg, handleSendResult: (data: OB11Return) => void) { - const onceSessionId = "llonebot_send_msg_" + uuid4(); - postData.ipc_uuid = onceSessionId; - ipcMain.once(onceSessionId, (event: any, sendResult: OB11Return) => { + const onceSessionId = uuid4(); + const handler = (event: any, session: SendIPCMsgSession>) => { // log("llonebot send msg ipcMain.once:" + JSON.stringify(sendResult)); - try { - handleSendResult(sendResult) - } catch (e) { - log("llonebot send msg ipcMain.once error:" + JSON.stringify(e)) + if (session?.id !== onceSessionId) { + return } - }) - sendIPCMsg(CHANNEL_SEND_MSG, postData); + try { + handleSendResult(session.data) + ipcMain.off(CHANNEL_SEND_BACK_MSG, handler) + return + } catch (e) { + log("llonebot send msg sendIPCSendQQMsg handler error:" + JSON.stringify(e)) + } + } + ipcMain.on(CHANNEL_SEND_BACK_MSG, handler) + sendIPCMsg(CHANNEL_SEND_MSG, { + id: onceSessionId, + data: postData, + }); } export function sendIPCRecallQQMsg(message_id: string) { - sendIPCMsg(CHANNEL_RECALL_MSG, {message_id: message_id}); + sendIPCMsg(CHANNEL_RECALL_MSG, { message_id: message_id }); } \ No newline at end of file diff --git a/src/preload.ts b/src/preload.ts index a8d591a..438a141 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -14,11 +14,15 @@ import { CHANNEL_UPDATE_FRIENDS, CHANNEL_UPDATE_GROUPS, CHANNEL_DELETE_FILE, - CHANNEL_GET_RUNNING_STATUS, CHANNEL_FILE2BASE64, CHANNEL_GET_HISTORY_MSG + CHANNEL_GET_RUNNING_STATUS, + CHANNEL_FILE2BASE64, + CHANNEL_GET_HISTORY_MSG, + CHANNEL_SEND_BACK_MSG, } from "./common/channels"; import {OB11Return, OB11SendMsgReturn} from "./onebot11/types"; +import { SendIPCMsgSession } from "./main/ipcsend"; const {contextBridge} = require("electron"); @@ -37,11 +41,14 @@ contextBridge.exposeInMainWorld("llonebot", { ipcRenderer.send(CHANNEL_UPDATE_FRIENDS, friends); }, sendSendMsgResult: (sessionId: string, msgResult: OB11SendMsgReturn)=>{ - ipcRenderer.send(sessionId, msgResult); + ipcRenderer.send(CHANNEL_SEND_BACK_MSG, { + id: sessionId, + data: msgResult, + }); }, - listenSendMessage: (handle: (jsonData: PostDataSendMsg) => void) => { + listenSendMessage: (handle: (jsonData: SendIPCMsgSession) => void) => { ipcRenderer.send(CHANNEL_LOG, "发送消息API已注册"); - ipcRenderer.on(CHANNEL_SEND_MSG, (event: any, args: PostDataSendMsg) => { + ipcRenderer.on(CHANNEL_SEND_MSG, (event: any, args: SendIPCMsgSession) => { handle(args) }) }, diff --git a/src/renderer.ts b/src/renderer.ts index 565d1e5..914e409 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -6,6 +6,7 @@ import {AtType, ChatType, Group, MessageElement, Peer, PostDataSendMsg, RawMessa import {OB11SendMsgReturn} from "./onebot11/types"; import {ipcRenderer} from "electron"; import {CHANNEL_GET_HISTORY_MSG} from "./common/channels"; +import { SendIPCMsgSession } from "./main/ipcsend"; let groups: Group[] = [] let friends: User[] = [] @@ -118,7 +119,8 @@ async function getGroupMember(group_qq: string, member_uid: string) { } -async function listenSendMessage(postData: PostDataSendMsg) { +async function listenSendMessage(session: SendIPCMsgSession) { + const postData = session.data console.log("收到发送消息请求", postData); let sendMsgResult: OB11SendMsgReturn = { retcode: 0, @@ -242,7 +244,7 @@ async function listenSendMessage(postData: PostDataSendMsg) { } console.log("发送消息", postData) if (sendMsgResult.status !== 0) { - window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) + window.llonebot.sendSendMsgResult(session.id, sendMsgResult) return; } window.LLAPI.sendMessage(peer, postData.params.message).then( @@ -252,18 +254,18 @@ async function listenSendMessage(postData: PostDataSendMsg) { window.llonebot.deleteFile(sendFiles); } sendMsgResult.data.message_id = res.raw.msgId; - window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) + window.llonebot.sendSendMsgResult(session.id, sendMsgResult) }, err => { sendMsgResult.status = -1; sendMsgResult.retcode = -1; sendMsgResult.message = `发送失败,${err}`; - window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) + window.llonebot.sendSendMsgResult(session.id, sendMsgResult) console.log("消息发送失败", postData, err) }) } else { console.log(sendMsgResult, postData); - window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) + window.llonebot.sendSendMsgResult(session.id, sendMsgResult) } } } @@ -341,8 +343,8 @@ function onLoad() { window.llonebot.log("llonebot render start"); window.llonebot.startExpress(); - window.llonebot.listenSendMessage((postData: PostDataSendMsg) => { - listenSendMessage(postData).then().catch(err => console.log("listenSendMessage err", err)) + window.llonebot.listenSendMessage((session: SendIPCMsgSession) => { + listenSendMessage(session).then().catch(err => console.log("listenSendMessage err", err)) }) window.llonebot.listenRecallMessage((arg: { message_id: string }) => { // console.log("listenRecallMessage", arg) From 2c55d84b9f20950b91d7fcdb6882a76d13733bc6 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Thu, 8 Feb 2024 01:45:56 +0800 Subject: [PATCH 02/14] refactor: hook some api --- src/common/types.ts | 4 +- src/global.d.ts | 4 +- src/main/main.ts | 11 +- src/ntqqapi/constructor.ts | 98 +++++++++++ src/ntqqapi/hook.ts | 27 ++- src/ntqqapi/ntcall.ts | 159 ++++++++++++++++-- src/ntqqapi/types.ts | 70 ++++++++ src/onebot11/{construct.ts => constructor.ts} | 8 +- src/server/httpserver.ts | 8 +- 9 files changed, 351 insertions(+), 38 deletions(-) create mode 100644 src/ntqqapi/constructor.ts create mode 100644 src/ntqqapi/types.ts rename src/onebot11/{construct.ts => constructor.ts} (95%) diff --git a/src/common/types.ts b/src/common/types.ts index 64acaee..ceb6c92 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -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 { diff --git a/src/global.d.ts b/src/global.d.ts index 4be039f..b792d3d 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -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; getGroupsList(forced: boolean): Promise getFriendsList(forced: boolean): Promise - getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Map }}> + getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Map }}> getPeer(): Promise add_qmenu(func: (qContextMenu: Node)=>void): void Ptt2Text(msgId:string, peer: Peer, elements: MessageElement[]): Promise diff --git a/src/main/main.ts b/src/main/main.ts index 14b7223..e62bfa7 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -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) } diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts new file mode 100644 index 0000000..b20a854 --- /dev/null +++ b/src/ntqqapi/constructor.ts @@ -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{ + 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 { + 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, + } + }; + } +} \ No newline at end of file diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 40013fd..e86b33e 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -4,22 +4,25 @@ import {NTQQApiClass} from "./ntcall"; import {RawMessage} from "../common/types"; import {msgHistory} from "../common/data"; +export let hookApiCallbacks: Recordvoid>={} + export enum ReceiveCmd { UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate", NEW_MSG = "nodeIKernelMsgListener/onRecvMsg", SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg" } -interface NTQQApiReturnData extends Array { +interface NTQQApiReturnData extends Array { 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; diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index dfdccff..52aeb40 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -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(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(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(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(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(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 } + }]>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO, + [{force: true, uids: [uid]}, undefined]) + return new Promise(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(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_MD5, [filePath]) + } + + static copyFile(filePath: string, destPath: string) { + return callNTQQApi(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(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(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]) + } } \ No newline at end of file diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts new file mode 100644 index 0000000..3ca483a --- /dev/null +++ b/src/ntqqapi/types.ts @@ -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 \ No newline at end of file diff --git a/src/onebot11/construct.ts b/src/onebot11/constructor.ts similarity index 95% rename from src/onebot11/construct.ts rename to src/onebot11/constructor.ts index 0e3f9ef..cab642b 100644 --- a/src/onebot11/construct.ts +++ b/src/onebot11/constructor.ts @@ -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 { +export class OB11Constructor { + static async message(msg: RawMessage): Promise { 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, diff --git a/src/server/httpserver.ts b/src/server/httpserver.ts index 112b528..90f9ecc 100644 --- a/src/server/httpserver.ts +++ b/src/server/httpserver.ts @@ -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) => 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) => 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, {}, "消息不存在") From 5e64df0eaa5992d901fc525c92a99cee3c4fcd3b Mon Sep 17 00:00:00 2001 From: linyuchen Date: Thu, 8 Feb 2024 11:32:17 +0800 Subject: [PATCH 03/14] save --- src/ntqqapi/hook.ts | 8 ++++++-- src/server/httpserver.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index e86b33e..b55e307 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -38,11 +38,15 @@ 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) { new Promise((resolve, reject) => { - hook.hookFunc(receiveData.payload); + try { + hook.hookFunc(receiveData.payload); + }catch (e) { + log("hook error", e, receiveData.payload) + } }).then() } } diff --git a/src/server/httpserver.ts b/src/server/httpserver.ts index 90f9ecc..1c269c3 100644 --- a/src/server/httpserver.ts +++ b/src/server/httpserver.ts @@ -1,7 +1,7 @@ import {getConfigUtil, log} from "../common/utils"; // const express = require("express"); -import express from "express"; +import express = require("express"); import {Request} from 'express'; import {Response} from 'express'; @@ -180,14 +180,18 @@ export function startExpress(port: number) { }); async function registerRouter(action: OB11ApiName, handle: (payload: PayloadType) => Promise>) { + let url = action.toString() + if (!action.startsWith("/")){ + url = "/" + action + } async function _handle(res: Response, payload: PayloadType) { res.send(await handle(payload)) } - app.post('/' + action, (req: Request, res: Response) => { + app.post(url, (req: Request, res: Response) => { _handle(res, req.body).then() }); - app.get('/' + action, (req: Request, res: Response) => { + app.get(url, (req: Request, res: Response) => { _handle(res, req.query as any).then() }); } From f9c376f6c5507c6be0ccf2cc23be7517be199d04 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Wed, 7 Feb 2024 15:41:57 +0800 Subject: [PATCH 04/14] chore: GitHub issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c299c54 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug反馈 +about: 报个Bug +title: '' +labels: bug +assignees: '' + +--- + +QQ版本: + +LLAPI版本: + +LLOneBot版本: + +调用LLoneBot的方式或者应用端(如postman直接调用,或NoneBot2、Koishi): + +您安装LLOneBot的步骤: + +BUG描述: + +复现步骤: \ No newline at end of file From d08601505b8ddd1241252fe775c40a0ed0d19722 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Sun, 11 Feb 2024 02:58:34 +0800 Subject: [PATCH 05/14] refactor --- .gitignore | 3 +- package-lock.json | 743 +----------------------------------- package.json | 4 +- src/common/data.ts | 44 ++- src/common/types.ts | 133 ------- src/global.d.ts | 13 +- src/main/ipcsend.ts | 4 +- src/main/main.ts | 166 ++------ src/ntqqapi/constructor.ts | 6 +- src/ntqqapi/hook.ts | 54 ++- src/ntqqapi/ntcall.ts | 88 ++++- src/ntqqapi/types.ts | 145 ++++++- src/onebot11/constructor.ts | 49 ++- src/onebot11/types.ts | 41 +- src/onebot11/utils.ts | 63 +++ src/preload.ts | 12 +- src/renderer.ts | 436 --------------------- src/server/httpserver.ts | 406 ++++++++++++++------ webpack.base.config.js | 2 +- 19 files changed, 797 insertions(+), 1615 deletions(-) create mode 100644 src/onebot11/utils.ts diff --git a/.gitignore b/.gitignore index 2576880..0332118 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ -.idea/ \ No newline at end of file +.idea/ +.DS_Store \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3b11f41..19da495 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "llonebot", "version": "1.0.0", + "hasInstallScript": true, "license": "ISC", "dependencies": { "express": "^4.18.2", @@ -18,7 +19,6 @@ "@types/express": "^4.17.20", "@types/uuid": "^9.0.8", "babel-loader": "^9.1.3", - "electron": "^27.0.2", "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.89.0", @@ -2006,59 +2006,6 @@ "node": ">=10.0.0" } }, - "node_modules/@electron/get": { - "version": "2.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/@electron/get/-/get-2.0.3.tgz", - "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", - "progress": "^2.0.3", - "semver": "^6.2.0", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/@electron/get/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@electron/get/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -2117,30 +2064,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://mirrors.cloud.tencent.com/npm/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@types/body-parser": { "version": "1.19.4", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/body-parser/-/body-parser-1.19.4.tgz", @@ -2151,18 +2074,6 @@ "@types/node": "*" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/connect": { "version": "3.4.37", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/connect/-/connect-3.4.37.tgz", @@ -2222,12 +2133,6 @@ "@types/send": "*" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==", - "dev": true - }, "node_modules/@types/http-errors": { "version": "2.0.3", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/http-errors/-/http-errors-2.0.3.tgz", @@ -2240,15 +2145,6 @@ "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mime": { "version": "1.3.4", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/mime/-/mime-1.3.4.tgz", @@ -2276,15 +2172,6 @@ "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", "dev": true }, - "node_modules/@types/responselike": { - "version": "1.0.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/@types/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/send": { "version": "0.17.3", "resolved": "https://mirrors.cloud.tencent.com/npm/@types/send/-/send-0.17.3.tgz", @@ -2312,16 +2199,6 @@ "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "dev": true }, - "node_modules/@types/yauzl": { - "version": "2.10.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/@types/yauzl/-/yauzl-2.10.2.tgz", - "integrity": "sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -2790,13 +2667,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "dev": true, - "optional": true - }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -2841,15 +2711,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://mirrors.cloud.tencent.com/npm/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2864,33 +2725,6 @@ "node": ">= 0.8" } }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://mirrors.cloud.tencent.com/npm/call-bind/-/call-bind-1.0.5.tgz", @@ -2975,18 +2809,6 @@ "node": ">=6" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3097,42 +2919,6 @@ "ms": "2.0.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.1", "resolved": "https://mirrors.cloud.tencent.com/npm/define-data-property/-/define-data-property-1.1.1.tgz", @@ -3146,24 +2932,6 @@ "node": ">= 0.4" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "optional": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/depd/-/depd-2.0.0.tgz", @@ -3181,51 +2949,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "optional": true - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://mirrors.cloud.tencent.com/npm/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "node_modules/electron": { - "version": "27.0.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/electron/-/electron-27.0.2.tgz", - "integrity": "sha512-4fbcHQ40ZDlqhr5Pamm+M5BF7ry2lGqjFTWTJ/mrBwuiPWu6xhV/RWgUhKBaLqKNfAaNl3eMxV3Jc82gv6JauQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^18.11.18", - "extract-zip": "^2.0.1" - }, - "bin": { - "electron": "cli.js" - }, - "engines": { - "node": ">= 12.20.55" - } - }, "node_modules/electron-to-chromium": { "version": "1.4.567", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.567.tgz", "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, - "node_modules/electron/node_modules/@types/node": { - "version": "18.18.7", - "resolved": "https://mirrors.cloud.tencent.com/npm/@types/node/-/node-18.18.7.tgz", - "integrity": "sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://mirrors.cloud.tencent.com/npm/encodeurl/-/encodeurl-1.0.2.tgz", @@ -3234,15 +2968,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -3256,15 +2981,6 @@ "node": ">=10.13.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/envinfo": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", @@ -3283,13 +2999,6 @@ "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "dev": true }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "optional": true - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3304,19 +3013,6 @@ "resolved": "https://mirrors.cloud.tencent.com/npm/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -3427,49 +3123,6 @@ "node": ">= 0.10.0" } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/extract-zip/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3491,15 +3144,6 @@ "node": ">= 4.9.1" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3668,20 +3312,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://mirrors.cloud.tencent.com/npm/function-bind/-/function-bind-1.1.2.tgz", @@ -3714,61 +3344,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "optional": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://mirrors.cloud.tencent.com/npm/gopd/-/gopd-1.0.1.tgz", @@ -3780,31 +3361,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://mirrors.cloud.tencent.com/npm/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3864,12 +3420,6 @@ "node": ">= 0.4" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/http-errors/-/http-errors-2.0.0.tgz", @@ -3885,19 +3435,6 @@ "node": ">= 0.8" } }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://mirrors.cloud.tencent.com/npm/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4033,12 +3570,6 @@ "bignumber.js": "^9.0.0" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4051,13 +3582,6 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true, - "optional": true - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://mirrors.cloud.tencent.com/npm/json5/-/json5-2.2.3.tgz", @@ -4071,24 +3595,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -4125,15 +3631,6 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4146,19 +3643,6 @@ "node": ">=10" } }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://mirrors.cloud.tencent.com/npm/media-typer/-/media-typer-0.3.0.tgz", @@ -4229,15 +3713,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.0.0.tgz", @@ -4263,18 +3738,6 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://mirrors.cloud.tencent.com/npm/object-inspect/-/object-inspect-1.13.1.tgz", @@ -4283,16 +3746,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://mirrors.cloud.tencent.com/npm/on-finished/-/on-finished-2.4.1.tgz", @@ -4304,24 +3757,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4395,12 +3830,6 @@ "resolved": "https://mirrors.cloud.tencent.com/npm/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.0.0.tgz", @@ -4431,15 +3860,6 @@ "node": ">=8" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://mirrors.cloud.tencent.com/npm/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4452,16 +3872,6 @@ "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -4485,18 +3895,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4631,12 +4029,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -4658,36 +4050,6 @@ "node": ">=8" } }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://mirrors.cloud.tencent.com/npm/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4745,13 +4107,6 @@ "node": ">=10" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true, - "optional": true - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://mirrors.cloud.tencent.com/npm/send/-/send-0.18.0.tgz", @@ -4780,22 +4135,6 @@ "resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "optional": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -4903,13 +4242,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://mirrors.cloud.tencent.com/npm/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "optional": true - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://mirrors.cloud.tencent.com/npm/statuses/-/statuses-2.0.1.tgz", @@ -4918,41 +4250,6 @@ "node": ">= 0.8" } }, - "node_modules/sumchecker": { - "version": "3.0.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/sumchecker/-/sumchecker-3.0.1.tgz", - "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "dev": true, - "dependencies": { - "debug": "^4.1.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/sumchecker/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://mirrors.cloud.tencent.com/npm/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/sumchecker/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -5090,19 +4387,6 @@ "node": ">= 8" } }, - "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://mirrors.cloud.tencent.com/npm/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://mirrors.cloud.tencent.com/npm/type-is/-/type-is-1.6.18.tgz", @@ -5174,15 +4458,6 @@ "node": ">=4" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/unpipe/-/unpipe-1.0.0.tgz", @@ -5416,28 +4691,12 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://mirrors.cloud.tencent.com/npm/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://mirrors.cloud.tencent.com/npm/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "1.0.0", "resolved": "https://mirrors.cloud.tencent.com/npm/yocto-queue/-/yocto-queue-1.0.0.tgz", diff --git a/package.json b/package.json index f3327b2..1ec8b58 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,13 @@ "main": "dist/main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "postinstall": "ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install electron --no-save", "build": "npm run build-main && npm run build-preload && npm run build-renderer", "build-main": "webpack --config webpack.main.config.js", "build-preload": "webpack --config webpack.preload.config.js", "build-renderer": "webpack --config webpack.renderer.config.js", "build-mac": "npm run build && cp manifest.json dist/ && npm run deploy-mac", - "deploy-mac": "cp dist/* ~/Library/Containers/com.tencent.qq/Data/Documents/LiteLoaderQQNT/plugins/LLOnebot/", + "deploy-mac": "cp dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOnebot/", "build-win": "npm run build && cp manifest.json dist/ && npm run deploy-win", "deploy-win": "cmd /c \"copy dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOnebot\\\"" }, @@ -26,7 +27,6 @@ "@types/express": "^4.17.20", "@types/uuid": "^9.0.8", "babel-loader": "^9.1.3", - "electron": "^27.0.2", "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.89.0", diff --git a/src/common/data.ts b/src/common/data.ts index 8c7606c..bd4fcd0 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -1,21 +1,38 @@ -import {Group, MessageElement, RawMessage, SelfInfo, User} from "./types"; +import {SelfInfo} from "./types"; +import { NTQQApi } from '../ntqqapi/ntcall'; +import { Group, RawMessage, User } from "../ntqqapi/types"; export let groups: Group[] = [] export let friends: User[] = [] export let msgHistory: Record = {} // msgId: RawMessage -export function getFriend(qq: string): User | undefined { - return friends.find(friend => friend.uin === qq) +export async function getFriend(qq: string): Promise { + let friend = friends.find(friend => friend.uin === qq) + if (!friend){ + friends = await NTQQApi.getFriends(true) + friend = friends.find(friend => friend.uin === qq) + } + return friend } -export function getGroup(qq: string): Group | undefined { - return groups.find(group => group.uid === qq) +export async function getGroup(qq: string): Promise { + let group = groups.find(group => group.groupCode === qq) + if (!group){ + groups = await NTQQApi.getGroups(true); + group = groups.find(group => group.groupCode === qq) + } + return group } -export function getGroupMember(groupQQ: string, memberQQ: string) { - const group = getGroup(groupQQ) +export async function getGroupMember(groupQQ: string, memberQQ: string) { + const group = await getGroup(groupQQ) if (group) { - return group.members?.find(member => member.uin === memberQQ) + let member = group.members?.find(member => member.uin === memberQQ) + if (!member){ + group.members = await NTQQApi.getGroupMembers(groupQQ) + member = group.members?.find(member => member.uin === memberQQ) + } + return member } } @@ -28,3 +45,14 @@ export let selfInfo: SelfInfo = { export function getHistoryMsgBySeq(seq: string) { return Object.values(msgHistory).find(msg => msg.msgSeq === seq) } + + +export let uidMaps:Record = {} // 一串加密的字符串(uid) -> qq号 + +export function getStrangerByUin(uin: string) { + for (const key in uidMaps) { + if (uidMaps[key].uin === uin) { + return uidMaps[key]; + } + } +} \ No newline at end of file diff --git a/src/common/types.ts b/src/common/types.ts index ceb6c92..0a9d7b2 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,143 +1,10 @@ import {OB11ApiName, OB11MessageData} from "../onebot11/types"; -export enum AtType { - notAt = 0, - atAll = 1, - atUser = 2 -} - -export enum ChatType { - friend = 1, - group = 2, - temp = 100 -} - -export interface GroupMember { - avatarPath: string; - cardName: string; - cardType: number; - isDelete: boolean; - nick: string; - qid: string; - remark: string; - role: number; // 群主:4, 管理员:3,群员:2 - shutUpTime: number; // 禁言时间,单位是什么暂时不清楚 - uid: string; // 加密的字符串 - uin: string; // QQ号 -} - - export interface SelfInfo { user_id: string; nickname: string; } -export interface User { - avatarUrl?: string; - bio?: string; // 签名 - nickName: string; - uid?: string; // 加密的字符串 - uin: string; // QQ号 -} - -export interface Group { - uid: string; // 群号 - name: string; - members?: GroupMember[]; -} - -export interface Peer { - chatType: ChatType - name: string - uid: string // qq号 -} - -export interface PttElement { - canConvert2Text: boolean - duration: number // 秒数 - fileBizId: null - fileId: number // 0 - fileName: string // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr" - filePath: string // "/Users//Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr" - fileSize: string // "4261" - fileSubId: string // "0" - fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" - formatType: string // 1 - invalidState: number // 0 - md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6" - playState: number // 0 - progress: number // 0 - text: string // "" - transferStatus: number // 0 - translateStatus: number // 0 - voiceChangeType: number // 0 - voiceType: number // 0 - waveAmplitudes: number[] -} - -export interface ArkElement { - bytesData: string -} - -export interface RawMessage { - msgId: string, - msgTime: string, - msgSeq: string, - senderUin: string; // 发送者QQ号 - peerUid: string; // 群号 或者 QQ uid - peerUin: string; // 群号 或者 发送者QQ号 - sendNickName: string; - sendMemberName?: string; // 发送者群名片 - chatType: ChatType, - elements: { - replyElement: { - senderUid: string, // 原消息发送者QQ号 - sourceMsgIsIncPic: boolean; // 原消息是否有图片 - sourceMsgText: string; - replayMsgSeq: string; // 源消息的msgSeq,可以通过这个找到源消息的msgId - }, - textElement: { - atType: AtType - atUid: string, - content: string, - atNtUid: string - }, - picElement: { - sourcePath: string // 图片本地路径 - picWidth: number - picHeight: number - fileSize: number - fileName: string - fileUuid: string - }, - pttElement: PttElement, - arkElement: ArkElement - }[] -} - -export interface MessageElement { - raw: RawMessage - peer: Peer, - sender: { - uid: string // 一串加密的字符串 - memberName: string - nickname: string - } -} - -export interface PostDataSendMsg { - action: OB11ApiName - message_type?: "private" | "group" - params?: { - user_id: string, - group_id: string, - message: OB11MessageData[]; - }, - user_id: string, - group_id: string, - message?: OB11MessageData[]; -} - export interface Config { port: number hosts: string[] diff --git a/src/global.d.ts b/src/global.d.ts index b792d3d..0cdba03 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,13 +1,12 @@ import { Config, - Group, - GroupMember, - MessageElement, Peer, - PostDataSendMsg, PttElement, RawMessage, - SelfInfo, - User -} from "./common/types"; + PostDataSendMsg, + SelfInfo} from "./common/types"; +import { Group } from "./ntqqapi/types"; +import { GroupMember } from "./ntqqapi/types"; +import { MessageElement, PttElement, RawMessage } from "./ntqqapi/types"; +import { User } from "./ntqqapi/types"; import { SendIPCMsgSession } from "./main/ipcsend"; diff --git a/src/main/ipcsend.ts b/src/main/ipcsend.ts index db640c3..34c4016 100644 --- a/src/main/ipcsend.ts +++ b/src/main/ipcsend.ts @@ -1,5 +1,5 @@ import {ipcMain, webContents} from 'electron'; -import {PostDataSendMsg} from "../common/types"; +import {OB11PostSendMsg} from "../onebot11/types" import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG,CHANNEL_SEND_BACK_MSG} from "../common/channels"; import {v4 as uuid4} from "uuid"; import {log} from "../common/utils"; @@ -23,7 +23,7 @@ export interface SendIPCMsgSession { data: T } -export function sendIPCSendQQMsg(postData: PostDataSendMsg, handleSendResult: (data: OB11Return) => void) { +export function sendIPCSendQQMsg(postData: OB11PostSendMsg, handleSendResult: (data: OB11Return) => void) { const onceSessionId = uuid4(); const handler = (event: any, session: SendIPCMsgSession>) => { // log("llonebot send msg ipcMain.once:" + JSON.stringify(sendResult)); diff --git a/src/main/main.ts b/src/main/main.ts index e62bfa7..254ce51 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -43,134 +43,14 @@ function onLoad() { ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => { return getConfigUtil().getConfig() }) - ipcMain.handle(CHANNEL_DOWNLOAD_FILE, async (event: any, arg: { uri: string, fileName: string }): Promise<{ - success: boolean, - errMsg: string, - path: string - }> => { - let filePath = path.join(CONFIG_DIR, arg.fileName) - let url = new URL(arg.uri); - if (url.protocol == "base64:") { - // base64转成文件 - let base64Data = arg.uri.split("base64://")[1] - try { - const buffer = Buffer.from(base64Data, 'base64'); - fs.writeFileSync(filePath, buffer); - } catch (e: any) { - return { - success: false, - errMsg: `base64文件下载失败,` + e.toString(), - path: "" - } - } - } else if (url.protocol == "http:" || url.protocol == "https:") { - // 下载文件 - let res = await fetch(url) - if (!res.ok) { - return { - success: false, - errMsg: `${url}下载失败,` + res.statusText, - path: "" - } - } - let blob = await res.blob(); - let buffer = await blob.arrayBuffer(); - try { - fs.writeFileSync(filePath, Buffer.from(buffer)); - } catch (e: any) { - return { - success: false, - errMsg: `${url}下载失败,` + e.toString(), - path: "" - } - } - } - else{ - return { - success: false, - errMsg: `不支持的file协议,` + url.protocol, - path: "" - } - } - if (isGIF(filePath)) { - fs.renameSync(filePath, filePath + ".gif"); - filePath += ".gif"; - } - return { - success: true, - errMsg: "", - path: filePath - }; - }) ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => { getConfigUtil().setConfig(arg) }) - ipcMain.on(CHANNEL_START_HTTP_SERVER, (event: any, arg: any) => { - startExpress(getConfigUtil().getConfig().port) - }) - - ipcMain.on(CHANNEL_UPDATE_GROUPS, (event: any, arg: Group[]) => { - for (const group of arg) { - let existGroup = groups.find(g => g.uid == group.uid) - if (existGroup) { - if (!existGroup.members) { - existGroup.members = [] - } - existGroup.name = group.name - for (const member of group.members || []) { - let existMember = existGroup.members?.find(m => m.uin == member.uin) - if (existMember) { - existMember.nick = member.nick - existMember.cardName = member.cardName - } else { - existGroup.members?.push(member) - } - } - } else { - groups.push(group) - } - } - groups.length = 0 - groups.push(...arg) - }) - - ipcMain.on(CHANNEL_UPDATE_FRIENDS, (event: any, arg: User[]) => { - friends.length = 0 - friends.push(...arg) - }) - - ipcMain.on(CHANNEL_POST_ONEBOT_DATA, (event: any, arg: any) => { - postMsg(arg); - }) - ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => { log(arg) }) - ipcMain.handle(CHANNEL_SET_SELF_INFO, (event: any, arg: SelfInfo) => { - selfInfo.user_id = arg.user_id; - selfInfo.nickname = arg.nickname; - running = true; - }) - - ipcMain.on(CHANNEL_DELETE_FILE, (event: any, arg: string[]) => { - for (const path of arg) { - fs.unlinkSync(path); - } - }) - - ipcMain.handle(CHANNEL_GET_RUNNING_STATUS, (event: any, arg: any) => { - return running; - }) - - ipcMain.handle(CHANNEL_FILE2BASE64, async (event: any, path: string): Promise<{err: string, data: string}> => { - return await file2base64(path); - }) - - ipcMain.handle(CHANNEL_GET_HISTORY_MSG, (event: any, arg: string): RawMessage | undefined => { - return msgHistory[arg] || null; - }) function postRawMsg(msgList:RawMessage[]) { const {debug, reportSelfMessage} = getConfigUtil().getConfig(); @@ -207,12 +87,48 @@ function onLoad() { log("report self message error: ", e.toString()) } }) + + async function getSelfInfo(){ + if (!selfInfo.user_id){ + setTimeout(()=>{ + getSelfInfo().then() + }) + } + const _ = await NTQQApi.getSelfInfo() + if (_.uin){ + log("get self info success", _) + selfInfo.user_id = _.uin + let nickName = _.uin + try{ + const userInfo = (await NTQQApi.getUserInfo(_.uid)) + if (userInfo){ + nickName = userInfo.nickName + } + } + catch(e){ + log("get self nickname failed", e.toString()) + } + selfInfo.nickname = nickName + try{ + // let _friends = await NTQQApi.getFriends(true) + // log("friends api:", _friends) + // for (let f of _friends){ + // friends.push(f) + // } + let _groups = await NTQQApi.getGroups(true) + log("groups api:", _groups) + for (let g of _groups){ + g.members = (await NTQQApi.getGroupMembers(g.uid)) + groups.push(g) + } - setTimeout(()=>{ - NTQQApi.getSelfInfo().then(r=>{ - log(r); - }) - }, 10000) + }catch(e){ + log("!!!初始化失败", e.stack.toString()) + } + startExpress(getConfigUtil().getConfig().port) + } + } + getSelfInfo().then() } diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index b20a854..d140385 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -32,7 +32,7 @@ export class SendMsgElementConstructor { }; } - reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { + static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { return { elementType: ElementType.REPLY, elementId: "", @@ -45,7 +45,7 @@ export class SendMsgElementConstructor { } } - async pic(picPath: string): Promise{ + static async pic(picPath: string): Promise{ const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath); const imageSize = await NTQQApi.getImageSize(picPath); const picElement = { @@ -71,7 +71,7 @@ export class SendMsgElementConstructor { }; } - async ptt(pttPath: string):Promise { + static async ptt(pttPath: string):Promise { const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath); return { elementType: ElementType.PTT, diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index b55e307..fc3cba7 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -1,15 +1,19 @@ import {BrowserWindow} from 'electron'; import {getConfigUtil, log} from "../common/utils"; -import {NTQQApiClass} from "./ntcall"; -import {RawMessage} from "../common/types"; -import {msgHistory} from "../common/data"; +import {NTQQApiClass, sendMessagePool} from "./ntcall"; +import { Group } from "./types"; +import { RawMessage } from "./types"; +import {groups, msgHistory} from "../common/data"; export let hookApiCallbacks: Recordvoid>={} export enum ReceiveCmd { UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate", NEW_MSG = "nodeIKernelMsgListener/onRecvMsg", - SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg" + SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg", + USER_INFO = "nodeIKernelProfileListener/onProfileDetailInfoChanged", + GROUPS = "nodeIKernelGroupListener/onGroupListUpdate", + GROUPS_UNIX = "onGroupListUpdate" } interface NTQQApiReturnData extends Array { @@ -28,13 +32,14 @@ interface NTQQApiReturnData extends Array { let receiveHooks: Array<{ method: ReceiveCmd, - hookFunc: (payload: unknown) => void + hookFunc: (payload: any) => void }> = [] export function hookNTQQApiReceive(window: BrowserWindow) { const originalSend = window.webContents.send; const patchSend = (channel: string, ...args: NTQQApiReturnData) => { // 判断是否是列表 + log(`received ntqq api message: ${channel}`, JSON.stringify(args)) if (args?.[1] instanceof Array) { for (let receiveData of args?.[1]) { const ntQQApiMethodName = receiveData.cmdName; @@ -75,6 +80,25 @@ export function registerReceiveHook(method: ReceiveCmd, hookFunc: ( }) } +function updateGroups(_groups: Group[]){ + for(let group of _groups){ + let existGroup = groups.find(g=>g.groupCode == group.groupCode) + if (!existGroup){ + groups.push(group) + } + else{ + Object.assign(existGroup, group); + } + } +} + +registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS, (payload)=>updateGroups(payload.groupList)) +registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS_UNIX, (payload)=>updateGroups(payload.groupList)) + +registerReceiveHook(ReceiveCmd.USER_INFO, (payload)=>{ + log("user info", payload); +}) + registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, (payload) => { for (const message of payload.msgList) { msgHistory[message.msgId] = message; @@ -84,7 +108,25 @@ registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, (payl registerReceiveHook<{ msgList: Array }>(ReceiveCmd.NEW_MSG, (payload) => { for (const message of payload.msgList) { log("收到新消息,push到历史记录", message) - msgHistory[message.msgId] = message; + if (!msgHistory[message.msgId]){ + msgHistory[message.msgId] = message + } + else{ + Object.assign(msgHistory[message.msgId], message) + } } }) +registerReceiveHook<{msgRecord: RawMessage}>(ReceiveCmd.SELF_SEND_MSG, ({msgRecord})=>{ + const message = msgRecord; + const peerUid = message.peerUid; + // log("收到自己发送成功的消息", Object.keys(sendMessagePool), message); + const sendCallback = sendMessagePool[peerUid]; + if (sendCallback){ + try{ + sendCallback(message); + }catch(e){ + log("receive self msg error", e.stack) + } + } +}) diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 52aeb40..0b9ed34 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -2,7 +2,11 @@ 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 { ChatType } from "./types"; +import { Group } from "./types"; +import { GroupMember } from "./types"; +import { RawMessage } from "./types"; +import { User } from "./types"; import {SendMessageElement} from "./types"; interface IPCReceiveEvent { @@ -48,9 +52,9 @@ enum NTQQApiChannel { IPC_UP_1 = "IPC_UP_1", } -interface Peer { +export interface Peer { chatType: ChatType - peerUid: string // 是uid还是QQ号 + peerUid: string // 如果是群聊uid为群号,私聊uid就是加密的字符串 guildId?: "" } @@ -70,6 +74,13 @@ function callNTQQApi(channel: NTQQApiChannel, className: NTQQApiClas }) } +export let sendMessagePool: Recordvoid) | null> = {}// peerUid: callbackFunnc + +interface GeneralCallResult{ + result:0, + errMsg: string +} + export class NTQQApi { // static likeFriend = defineNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND) @@ -91,14 +102,14 @@ export class NTQQApi { } static getFriends(forced = false) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{force_update: forced}, undefined]) + return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{force_update: forced}, undefined]) } static getGroups(forced = false) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{force_update: forced}, undefined]) + return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{force_update: forced}, undefined]) } - static async getGroupMembers(groupQQ: string, num = 3000) { + static async getGroupMembers(groupQQ: string, num = 5000) { const sceneId = callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{ groupCode: groupQQ, scene: "groupMemberList_MainWindow" @@ -114,13 +125,11 @@ export class NTQQApi { } static async getUserInfo(uid: string) { - const result = await callNTQQApi<[{ - payload: { profiles: Map } - }]>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO, + const result = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO, [{force: true, uids: [uid]}, undefined]) - return new Promise(resolve => { - resolve(result[0].payload.profiles.get(uid)) - }) + log("get user info result", result); + return result[0].payload.profiles.get(uid); + } static getFileType(filePath: string) { @@ -134,7 +143,7 @@ export class NTQQApi { } static copyFile(filePath: string, destPath: string) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_COPY, [filePath, destPath]) + return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_COPY, [{ fromPath: filePath, toPath: destPath }]) } static getImageSize(filePath: string) { @@ -152,7 +161,7 @@ export class NTQQApi { static async uploadFile(filePath: string) { const md5 = await NTQQApi.getFileMd5(filePath); const fileName = `${md5}.${(await NTQQApi.getFileType(filePath)).ext}`; - const mediaPath = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.MEDIA_FILE_PATH, [{ + const mediaPath = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.MEDIA_FILE_PATH, [{ path_info: { md5HexStr: md5, fileName: fileName, @@ -164,6 +173,7 @@ export class NTQQApi { file_uuid: "" } }]) + log("media path", mediaPath) await NTQQApi.copyFile(filePath, mediaPath); const fileSize = await NTQQApi.getFileSize(filePath); return { @@ -178,11 +188,49 @@ export class NTQQApi { 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]) + static sendMsg(peer: Peer, msgElements: SendMessageElement[]){ + const sendTimeout = 10 * 1000 + + return new Promise((resolve, reject)=>{ + const peerUid = peer.peerUid; + let usingTime = 0; + let success = false; + + const checkSuccess = ()=>{ + if (!success){ + sendMessagePool[peerUid] = null; + reject("发送超时") + } + } + setTimeout(checkSuccess, sendTimeout); + + const checkLastSend = ()=>{ + let lastSending = sendMessagePool[peerUid] + if (sendTimeout < usingTime){ + sendMessagePool[peerUid] = null; + reject("发送超时") + } + if (!!lastSending){ + // log("有正在发送的消息,等待中...") + usingTime += 100; + setTimeout(checkLastSend, 100); + } + else{ + log("可以进行发送消息,设置发送成功回调", sendMessagePool) + sendMessagePool[peerUid] = (rawMessage: RawMessage)=>{ + success = true; + sendMessagePool[peerUid] = null; + resolve(rawMessage); + } + } + } + checkLastSend() + callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.SEND_MSG, [{ + msgId: "0", + peer, msgElements, + msgAttributeInfos: new Map(), + }, null]).then() + }) + } } \ No newline at end of file diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts index 3ca483a..fa32cd0 100644 --- a/src/ntqqapi/types.ts +++ b/src/ntqqapi/types.ts @@ -1,3 +1,60 @@ + +export interface User { + uid: string; // 加密的字符串 + uin: string; // QQ号 + nick: string; + avatarUrl?: string; + longNick: string; // 签名 + raw: { + remark: string; + }; +} + +export interface Group{ + groupCode: string, + maxMember: number, + memberCount: number, + groupName: string, + groupStatus: 0, + memberRole: 2, + isTop: boolean, + toppedTimestamp: "0", + privilegeFlag: number, //65760 + isConf: boolean, + hasModifyConfGroupFace: boolean, + hasModifyConfGroupName: boolean, + remarkName: string, + hasMemo: boolean, + groupShutupExpireTime: string, //"0", + personShutupExpireTime: string, //"0", + discussToGroupUin: string, //"0", + discussToGroupMaxMsgSeq: number, + discussToGroupTime: number, + groupFlagExt: number, //1073938496, + authGroupType: number, //0, + groupCreditLevel: number, //0, + groupFlagExt3: number, //0, + groupOwnerId: { + "memberUin": string, //"0", + "memberUid": string, //"u_fbf8N7aeuZEnUiJAbQ9R8Q" + }, + members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段 +} + +export interface GroupMember { + avatarPath: string; + cardName: string; + cardType: number; + isDelete: boolean; + nick: string; + qid: string; + remark: string; + role: number; // 群主:4, 管理员:3,群员:2 + shutUpTime: number; // 禁言时间,单位是什么暂时不清楚 + uid: string; // 加密的字符串 + uin: string; // QQ号 +} + export enum ElementType { TEXT = 1, PIC = 2, @@ -67,4 +124,90 @@ export interface SendReplyElement { } } -export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement \ No newline at end of file +export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement + +export enum AtType { + notAt = 0, + atAll = 1, + atUser = 2 +} + +export enum ChatType { + friend = 1, + group = 2, + temp = 100 +} +export interface PttElement { + canConvert2Text: boolean; + duration: number; // 秒数 + fileBizId: null; + fileId: number; // 0 + fileName: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr" + filePath: string; // "/Users//Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr" + fileSize: string; // "4261" + fileSubId: string; // "0" + fileUuid: string; // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" + formatType: string; // 1 + invalidState: number; // 0 + md5HexStr: string; // "e4d09c784d5a2abcb2f9980bdc7acfe6" + playState: number; // 0 + progress: number; // 0 + text: string; // "" + transferStatus: number; // 0 + translateStatus: number; // 0 + voiceChangeType: number; // 0 + voiceType: number; // 0 + waveAmplitudes: number[]; +} + +export interface ArkElement { + bytesData: string; +} + +export interface RawMessage { + msgId: string; + msgTime: string; + msgSeq: string; + senderUin: string; // 发送者QQ号 + peerUid: string; // 群号 或者 QQ uid + peerUin: string; // 群号 或者 发送者QQ号 + sendNickName: string; + sendMemberName?: string; // 发送者群名片 + chatType: ChatType; + elements: { + replyElement: { + senderUid: string; // 原消息发送者QQ号 + sourceMsgIsIncPic: boolean; // 原消息是否有图片 + sourceMsgText: string; + replayMsgSeq: string; // 源消息的msgSeq,可以通过这个找到源消息的msgId + }; + textElement: { + atType: AtType; + atUid: string; + content: string; + atNtUid: string; + }; + picElement: { + sourcePath: string; // 图片本地路径 + picWidth: number; + picHeight: number; + fileSize: number; + fileName: string; + fileUuid: string; + }; + pttElement: PttElement; + arkElement: ArkElement; + }[]; +} + +export interface MessageElement { + raw: RawMessage; + peer: any; + sender: { + uid: string; // 一串加密的字符串 + memberName: string; + nickname: string; + }; +} + + diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index cab642b..2b5e1a0 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -1,5 +1,5 @@ -import {OB11MessageDataType, OB11GroupMemberRole, OB11Message, OB11MessageData} from "./types"; -import {AtType, ChatType, RawMessage} from "../common/types"; +import {OB11MessageDataType, OB11GroupMemberRole, OB11Message, OB11MessageData, OB11Group, OB11GroupMember, Friend} from "./types"; +import { AtType, ChatType, Group, GroupMember, RawMessage, User } from '../ntqqapi/types'; import {getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo} from "../common/data"; import {file2base64, getConfigUtil} from "../common/utils"; @@ -28,13 +28,13 @@ export class OB11Constructor { } if (msg.chatType == ChatType.group) { resMsg.group_id = msg.peerUin - const member = getGroupMember(msg.peerUin, msg.senderUin); + const member = await getGroupMember(msg.peerUin, msg.senderUin); if (member) { resMsg.sender.role = OB11Constructor.groupMemberRole(member.role); } } else if (msg.chatType == ChatType.friend) { resMsg.sub_type = "friend" - const friend = getFriend(msg.senderUin); + const friend = await getFriend(msg.senderUin); if (friend) { resMsg.sender.nickname = friend.nickName; } @@ -54,7 +54,7 @@ export class OB11Constructor { message_data["data"]["qq"] = "all" } else { let uid = element.textElement.atUid - let atMember = getGroupMember(msg.peerUin, uid) + let atMember = await getGroupMember(msg.peerUin, uid) message_data["data"]["mention"] = atMember?.uin message_data["data"]["qq"] = atMember?.uin } @@ -118,12 +118,49 @@ export class OB11Constructor { // } return resMsg; } + + static friend(friend: User): Friend{ + return { + user_id: friend.uin, + nickname: friend.nickName, + remark: friend.raw.remark + } + + } - static groupMemberRole(role: number): OB11GroupMemberRole { + static friends(friends: User[]): Friend[]{ + return friends.map(OB11Constructor.friend) + } + + static groupMemberRole(role: number): OB11GroupMemberRole | undefined { return { 4: OB11GroupMemberRole.owner, 3: OB11GroupMemberRole.admin, 2: OB11GroupMemberRole.member }[role] } + + static groupMember(group_id: string, member: GroupMember): OB11GroupMember{ + return { + group_id, + user_id: member.uin, + nickname: member.nick, + card: member.cardName + } + } + + static groupMembers(group: Group): OB11GroupMember[]{ + return group.members.map(m=>OB11Constructor.groupMember(group.groupCode, m)) + } + + static group(group: Group): OB11Group{ + return { + group_id: group.groupCode, + group_name: group.groupName + } + } + + static groups(groups: Group[]): OB11Group[]{ + return groups.map(OB11Constructor.group) + } } \ No newline at end of file diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 34975d6..2d719c6 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -1,4 +1,11 @@ -import {AtType, RawMessage} from "../common/types"; +import {SelfInfo} from "../common/types"; +import { AtType } from "../ntqqapi/types"; +import { RawMessage } from "../ntqqapi/types"; +import { User } from "../ntqqapi/types"; + +export interface Friend extends SelfInfo{ + remark?: string +} export enum OB11UserSex{ male = "male", @@ -12,6 +19,27 @@ export enum OB11GroupMemberRole{ member = "member", } +export interface OB11GroupMember { + group_id: string + user_id: string + nickname: string + card?: string + sex?: OB11UserSex + age?: number + join_time?: number + last_sent_time?: number + level?: number + role?: OB11GroupMemberRole + title?: string +} + +export interface OB11Group{ + group_id: string + group_name: string + member_count?: number + max_member_count?: number +} + interface OB11Sender { user_id: string, nickname: string, @@ -45,9 +73,11 @@ export interface OB11Message { } export type OB11ApiName = - "send_private_msg" + "send_msg" + | "send_private_msg" | "send_group_msg" | "get_group_list" + | "get_group_info" | "get_friend_list" | "delete_msg" | "get_login_info" @@ -102,4 +132,11 @@ export type OB11MessageData = { data: { id: string, } +} + +export interface OB11PostSendMsg { + message_type?: "private" | "group" + user_id: string, + group_id?: string, + message: OB11MessageData[] | string | OB11MessageData; } \ No newline at end of file diff --git a/src/onebot11/utils.ts b/src/onebot11/utils.ts new file mode 100644 index 0000000..67d5931 --- /dev/null +++ b/src/onebot11/utils.ts @@ -0,0 +1,63 @@ +import { CONFIG_DIR, isGIF } from "../common/utils"; +import * as path from 'path'; +import { NTQQApi } from '../ntqqapi/ntcall'; +const fs = require("fs").promises; + +export async function uri2local(fileName: string, uri: string){ + let filePath = path.join(CONFIG_DIR, fileName) + let url = new URL(uri); + if (url.protocol == "base64:") { + // base64转成文件 + let base64Data = uri.split("base64://")[1] + try { + const buffer = Buffer.from(base64Data, 'base64'); + await fs.writeFile(filePath, buffer); + } catch (e: any) { + return { + success: false, + errMsg: `base64文件下载失败,` + e.toString(), + path: "" + } + } + } else if (url.protocol == "http:" || url.protocol == "https:") { + // 下载文件 + let res = await fetch(url) + if (!res.ok) { + return { + success: false, + errMsg: `${url}下载失败,` + res.statusText, + path: "" + } + } + let blob = await res.blob(); + let buffer = await blob.arrayBuffer(); + try { + await fs.writeFile(filePath, Buffer.from(buffer)); + } catch (e: any) { + return { + success: false, + errMsg: `${url}下载失败,` + e.toString(), + path: "" + } + } + } else if (url.protocol === "file:"){ + await fs.copyFile(url.pathname, filePath); + // filePath = (await NTQQApi.uploadFile(url.pathname)).path; + } + else{ + return { + success: false, + errMsg: `不支持的file协议,` + url.protocol, + path: "" + } + } + if (isGIF(filePath)) { + await fs.rename(filePath, filePath + ".gif"); + filePath += ".gif"; + } + return { + success: true, + errMsg: "", + path: filePath + }; +} \ No newline at end of file diff --git a/src/preload.ts b/src/preload.ts index 438a141..3fc8bcc 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,6 +1,9 @@ // Electron 主进程 与 渲染进程 交互的桥梁 -import {Config, Group, PostDataSendMsg, RawMessage, SelfInfo, User} from "./common/types"; +import {Config, SelfInfo} from "./common/types"; +import { Group } from "./ntqqapi/types"; +import { RawMessage } from "./ntqqapi/types"; +import { User } from "./ntqqapi/types"; import { CHANNEL_DOWNLOAD_FILE, CHANNEL_GET_CONFIG, @@ -46,12 +49,7 @@ contextBridge.exposeInMainWorld("llonebot", { data: msgResult, }); }, - listenSendMessage: (handle: (jsonData: SendIPCMsgSession) => void) => { - ipcRenderer.send(CHANNEL_LOG, "发送消息API已注册"); - ipcRenderer.on(CHANNEL_SEND_MSG, (event: any, args: SendIPCMsgSession) => { - handle(args) - }) - }, + listenRecallMessage: (handle: (jsonData: {message_id: string}) => void) => { ipcRenderer.on(CHANNEL_RECALL_MSG, (event: any, args: {message_id: string}) => { handle(args) diff --git a/src/renderer.ts b/src/renderer.ts index 914e409..83651f1 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,440 +1,5 @@ /// -import {AtType, ChatType, Group, MessageElement, Peer, PostDataSendMsg, RawMessage, User} from "./common/types"; - - -import {OB11SendMsgReturn} from "./onebot11/types"; -import {ipcRenderer} from "electron"; -import {CHANNEL_GET_HISTORY_MSG} from "./common/channels"; -import { SendIPCMsgSession } from "./main/ipcsend"; - -let groups: Group[] = [] -let friends: User[] = [] -let uid_maps: Record = {} // 一串加密的字符串 -> qq号 - -function getStrangerByUin(uin: string) { - for (const key in uid_maps) { - if (uid_maps[key].uin === uin) { - return uid_maps[key]; - } - } -} - -async function getHistoryMsg(msgId: string) { - return await window.llonebot.getHistoryMsg(msgId); -} - -async function getUserInfo(uid: string): Promise { - let user = uid_maps[uid] - if (!user) { - // 从服务器获取用户信息 - user = await window.LLAPI.getUserInfo(uid) - uid_maps[uid] = user - } - return user -} - -async function getFriends() { - let _friends = await window.LLAPI.getFriendsList(false) - for (let friend of _friends) { - let existFriend = friends.find(f => f.uin == friend.uin) - if (!existFriend) { - friends.push(friend) - } - } - window.llonebot.updateFriends(friends) - return friends -} - -async function getFriend(qq: string) { - let friend = friends.find(friend => friend.uin == qq) - if (!friend) { - await getFriends(); - friend = friends.find(friend => friend.uin == qq); - } - return friend; -} - -async function getGroup(qq: string) { - let group = groups.find(group => group.uid == qq) - if (!group) { - await getGroups(); - group = groups.find(group => group.uid == qq) - } - return group -} - -async function getGroups() { - let __groups = await window.LLAPI.getGroupsList(false) - for (let group of __groups) { - group.members = []; - let existGroup = groups.find(g => g.uid == group.uid) - if (!existGroup) { - // console.log("更新群列表", groups) - groups.push(group) - } - } - window.llonebot.updateGroups(groups) - return groups -} - -async function getGroupMembers(group_qq: string, forced: boolean = false) { - let group = await getGroup(group_qq) - if (!group?.members || group!.members!.length == 0 || forced) { - let res = (await window.LLAPI.getGroupMemberList(group_qq, 5000)) - // console.log(`更新群${group}成员列表 await`, _res) - // window.LLAPI.getGroupMemberList(group_qq + "_groupMemberList_MainWindow", 5000).then(res =>{ - let members = res.result.infos.values(); - console.log("getGroupMemberList api response:", res) - if (members && forced) { - group.members = [] - } - for (const member of members) { - if (!group!.members!.find(m => m.uid == member.uid)) { - group!.members!.push(member) - } - uid_maps[member.uid] = { - uin: member.uin, - uid: member.uid, - nickName: member.nick - }; - } - window.llonebot.updateGroups(groups) - console.log(`更新群${group.name}成员列表`, group) - // }) - } - return group?.members -} - -async function getGroupMember(group_qq: string, member_uid: string) { - let members = await getGroupMembers(group_qq) - if (members) { - let member = members.find(member => member.uid == member_uid) - if (!member) { - members = await getGroupMembers(group_qq, true) - member = members?.find(member => member.uid == member_uid) - } - return member - } -} - - -async function listenSendMessage(session: SendIPCMsgSession) { - const postData = session.data - console.log("收到发送消息请求", postData); - let sendMsgResult: OB11SendMsgReturn = { - retcode: 0, - status: 0, - data: { - message_id: "" - }, - message: "发送成功" - } - if (postData.action == "send_private_msg" || postData.action == "send_group_msg") { - let peer: Peer | null = null; - if (!postData.params) { - postData.params = { - message: postData.message, - user_id: postData.user_id, - group_id: postData.group_id - } - } - if (postData.action == "send_private_msg") { - let friend = await getFriend(postData.params.user_id) - if (friend) { - console.log("好友消息", postData) - peer = { - chatType: ChatType.friend, - name: friend.nickName, - uid: friend.uid - } - } else { - // 临时消息 - console.log("发送临时消息", postData) - let receiver = getStrangerByUin(postData.params.user_id); - if (receiver) { - peer = { - chatType: ChatType.temp, - name: receiver.nickName, - uid: receiver.uid - } - } else { - sendMsgResult.status = -1; - sendMsgResult.retcode = -1; - sendMsgResult.message = `发送失败,未找到对象${postData.params.user_id},检查他是否为好友或是群友`; - } - } - } else if (postData.action == "send_group_msg") { - let group = await getGroup(postData.params.group_id) - if (group) { - peer = { - chatType: ChatType.group, - name: group.name, - uid: group.uid - } - } else { - sendMsgResult.status = -1; - sendMsgResult.retcode = -1; - sendMsgResult.message = `发送失败,未找到群${postData.params.group_id}`; - console.log("未找到群, 发送群消息失败", postData) - } - } - if (peer) { - let sendFiles: string[] = []; - for (let message of postData.params.message) { - if (message.type == "at") { - // @ts-ignore - message.type = "text" - message.atType = AtType.atUser - let atUid = message.data?.qq || message.atUid - if (atUid == "all") { - message.atType = AtType.atAll - atUid = "0"; - message.content = `@全体成员` - } else { - let group = await getGroup(postData.params.group_id) - let atMember = group.members.find(member => member.uin == atUid) - message.atNtUid = atMember.uid - message.atUid = atUid - message.content = `@${atMember.cardName || atMember.nick}` - } - } else if (message.type == "text") { - message.content = message.data?.text || message.content - } else if (message.type == "image" || message.type == "voice" || message.type == "record") { - // 收到的是uri格式的,需要转成本地的, uri格式有三种,http, file, base64 - let url = message.data?.file || message.file - let uri = new URL(url); - let ext: string; - if (message.type == "image") { - ext = ".png" - } - if (message.type == "voice" || message.type == "record") { - message.type = "voice" - ext = ".amr" - } - let localFilePath = `${Date.now()}${ext}` - if (uri.protocol == "file:") { - localFilePath = url.split("file://")[1] - } else { - const {errMsg, path} = await window.llonebot.downloadFile({ - uri: url, - fileName: `${Date.now()}${ext}` - }) - console.log("下载文件结果", errMsg, path) - if (errMsg) { - console.log("下载文件失败", errMsg); - sendMsgResult.status = -1; - sendMsgResult.retcode = -1; - sendMsgResult.message = `发送失败,下载文件失败,${errMsg}`; - break; - } else { - localFilePath = path; - } - sendFiles.push(localFilePath); - } - message.file = localFilePath - } else if (message.type == "reply") { - let msgId = message.data?.id || message.msgId - const rawMsg: RawMessage = await getHistoryMsg(msgId) - if (rawMsg){ - message.msgId = msgId - message.msgSeq = rawMsg.msgSeq - } - } - } - console.log("发送消息", postData) - if (sendMsgResult.status !== 0) { - window.llonebot.sendSendMsgResult(session.id, sendMsgResult) - return; - } - window.LLAPI.sendMessage(peer, postData.params.message).then( - (res: MessageElement) => { - console.log("消息发送成功:", res, peer, postData.params.message) - if (sendFiles.length) { - window.llonebot.deleteFile(sendFiles); - } - sendMsgResult.data.message_id = res.raw.msgId; - window.llonebot.sendSendMsgResult(session.id, sendMsgResult) - }, - err => { - sendMsgResult.status = -1; - sendMsgResult.retcode = -1; - sendMsgResult.message = `发送失败,${err}`; - window.llonebot.sendSendMsgResult(session.id, sendMsgResult) - console.log("消息发送失败", postData, err) - }) - } else { - console.log(sendMsgResult, postData); - window.llonebot.sendSendMsgResult(session.id, sendMsgResult) - } - } -} - -function recallMessage(msgId: string) { - getHistoryMsg(msgId).then((msg: RawMessage)=>{ - const peer: Peer ={ - chatType: msg.chatType, - name: "", - uid: msg.peerUin - } - window.LLAPI.recallMessage(peer, [msgId]).then() - }) - -} - -let chatListEle: HTMLCollectionOf - -async function getGroupsMembers(groupsArg: Group[]) { - // 批量获取群成员列表 - let failedGroups: Group[] = [] - for (const group of groupsArg) { - let handledGroup = await getGroupMembers(group.uid, true) - if (handledGroup.length == 0) { - failedGroups.push(group) - } - } - if (failedGroups.length > 0) { - console.log("获取群成员列表失败,重试", failedGroups.map(group => group.name)) - setTimeout(() => { - getGroupsMembers(failedGroups).then() - }, 1000) - } else { - window.llonebot.log("全部群成员获取完毕") - } -} - - -async function initAccountInfo() { - let accountInfo = await window.LLAPI.getAccountInfo(); - window.llonebot.log("getAccountInfo " + JSON.stringify(accountInfo)); - if (!accountInfo.uid) { - return false; - } - let selfInfo = await window.LLAPI.getUserInfo(accountInfo.uid); - window.llonebot.setSelfInfo({ - user_id: accountInfo.uin, - nickname: selfInfo.nickName - }); - window.llonebot.log("selfInfo " + JSON.stringify(selfInfo)); - return true; -} - -function onLoad() { - window.llonebot.log("llonebot render onLoad"); - window.llonebot.getRunningStatus().then(running => { - if (running) { - return; - } - initAccountInfo().then( - (initSuccess) => { - if (!initSuccess) { - return; - } - if (friends.length == 0) { - getFriends().then(() => { - }); - } - if (groups.length == 0) { - getGroups().then(() => { - getGroupsMembers(groups).then(() => { - }); - }); - } - window.llonebot.log("llonebot render start"); - window.llonebot.startExpress(); - - window.llonebot.listenSendMessage((session: SendIPCMsgSession) => { - listenSendMessage(session).then().catch(err => console.log("listenSendMessage err", err)) - }) - window.llonebot.listenRecallMessage((arg: { message_id: string }) => { - // console.log("listenRecallMessage", arg) - recallMessage(arg.message_id) - }) - window.llonebot.log("llonebot loaded"); - // window.LLAPI.add_qmenu((qContextMenu: Node) => { - // let btn = document.createElement("a") - // btn.className = "q-context-menu-item q-context-menu-item--normal vue-component" - // btn.setAttribute("aria-disabled", "false") - // btn.setAttribute("role", "menuitem") - // btn.setAttribute("tabindex", "-1") - // btn.onclick = () => { - // // window.LLAPI.getPeer().then(peer => { - // // // console.log("current peer", peer) - // // if (peer && peer.chatType == "group") { - // // getGroupMembers(peer.uid, true).then(()=> { - // // console.log("获取群成员列表成功", groups); - // // alert("获取群成员列表成功") - // // }) - // // } - // // }) - // async function func() { - // for (const group of groups) { - // await getGroupMembers(group.uid, true) - // } - // } - // - // func().then(() => { - // console.log("获取群成员列表结果", groups); - // // 找到members数量为空的群 - // groups.map(group => { - // if (group.members.length == 0) { - // console.log(`${group.name}群成员为空`) - // } - // }) - // window.llonebot.updateGroups(groups) - // }) - // } - // btn.innerText = "获取群成员列表" - // console.log(qContextMenu) - // // qContextMenu.appendChild(btn) - // }) - // - // window.LLAPI.on("context-msg-menu", (event, target, msgIds) => { - // console.log("msg menu", event, target, msgIds); - // }) - // - // // console.log("getAccountInfo", LLAPI.getAccountInfo()); - // function getChatListEle() { - // chatListEle = document.getElementsByClassName("viewport-list__inner") - // console.log("chatListEle", chatListEle) - // if (chatListEle.length == 0) { - // setTimeout(getChatListEle, 500) - // } else { - // try { - // // 选择要观察的目标节点 - // const targetNode = chatListEle[0]; - // - // // 创建一个观察器实例并传入回调函数 - // const observer = new MutationObserver(function (mutations) { - // mutations.forEach(function (mutation) { - // // console.log("chat list changed", mutation.type); // 输出 mutation 的类型 - // // 获得当前聊天窗口 - // window.LLAPI.getPeer().then(peer => { - // // console.log("current peer", peer) - // if (peer && peer.chatType == "group") { - // getGroupMembers(peer.uid, false).then() - // } - // }) - // }); - // }); - // - // // 配置观察选项 - // const config = {attributes: true, childList: true, subtree: true}; - // - // // 传入目标节点和观察选项 - // observer.observe(targetNode, config); - // - // } catch (e) { - // window.llonebot.log(e) - // } - // } - // } - // - // // getChatListEle(); - } - ); - }); -} // 打开设置界面时触发 async function onSettingWindowCreated(view: Element) { @@ -584,7 +149,6 @@ async function onSettingWindowCreated(view: Element) { } -setTimeout(onLoad, 5000) export { onSettingWindowCreated diff --git a/src/server/httpserver.ts b/src/server/httpserver.ts index 1c269c3..ea4006b 100644 --- a/src/server/httpserver.ts +++ b/src/server/httpserver.ts @@ -1,16 +1,21 @@ import {getConfigUtil, log} from "../common/utils"; -// const express = require("express"); -import express = require("express"); -import {Request} from 'express'; +const express = require("express"); +import {Request, text} from 'express'; import {Response} from 'express'; -const JSONbig = require('json-bigint'); +const JSONbig = require('json-bigint')({storeAsString: true}); 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 {AtType, ChatType, Group, SelfInfo} from "../common/types"; +import {friends, getGroup, getGroupMember, getStrangerByUin, groups, msgHistory, selfInfo} from "../common/data"; +import { OB11ApiName, OB11Message, OB11Return, OB11MessageData, OB11Group, OB11GroupMember, OB11PostSendMsg, OB11MessageDataType, Friend } from '../onebot11/types'; import {OB11Constructor} from "../onebot11/constructor"; +import { NTQQApi } from "../ntqqapi/ntcall"; +import { Peer } from "../ntqqapi/ntcall"; +import { ElementType, SendMessageElement } from "../ntqqapi/types"; +import { SendMsgElementConstructor } from "../ntqqapi/constructor"; +import { uri2local } from "../onebot11/utils"; +import { v4 as uuid4 } from 'uuid'; // @SiberianHusky 2021-08-15 @@ -51,16 +56,20 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) { // ==end== -function constructReturnData(status: number, data: any = {}, message: string = "") { +function constructReturnData(data: T, status: number=0, message: string = ""): OB11Return { return { status: status, retcode: status, data: data, message: message } - } +function constructErrorReturn(err: string){ + return constructReturnData(null, -1, err); +} + + function handlePost(jsonData: any, handleSendResult: (data: OB11Return) => void) { log("API receive post:" + JSON.stringify(jsonData)) if (!jsonData.params) { @@ -74,9 +83,7 @@ function handlePost(jsonData: any, handleSendResult: (data: OB11Return) => message: '' } - if (jsonData.action == "get_login_info") { - resData["data"] = selfInfo - } else if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") { + if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") { if (jsonData.action == "send_private_msg") { jsonData.message_type = "private" } else { @@ -99,8 +106,8 @@ function handlePost(jsonData: any, handleSendResult: (data: OB11Return) => return { group_id: group.uid, group_name: group.name, - member_count: group.members.length, - group_members: group.members.map(member => { + member_count: group.members?.length, + group_members: group.members?.map(member => { return { user_id: member.uin, user_name: member.cardName || member.nick, @@ -115,18 +122,18 @@ function handlePost(jsonData: any, handleSendResult: (data: OB11Return) => resData["data"] = { group_id: group.uid, group_name: group.name, - member_count: group.members.length, + member_count: group.members?.length, } } } else if (jsonData.action == "get_group_member_info") { let member = groups.find(group => group.uid == jsonData.params.group_id)?.members?.find(member => member.uin == jsonData.params.user_id) resData["data"] = { - user_id: member.uin, - user_name: member.nick, - user_display_name: member.cardName || member.nick, - nickname: member.nick, - card: member.cardName, - role: OB11Constructor.groupMemberRole(member.role), + user_id: member?.uin, + user_name: member?.nick, + user_display_name: member?.cardName || member?.nick, + nickname: member?.nick, + card: member?.cardName, + role: member && OB11Constructor.groupMemberRole(member.role), } } else if (jsonData.action == "get_group_member_list") { let group = groups.find(group => group.uid == jsonData.params.group_id) @@ -159,112 +166,93 @@ function handlePost(jsonData: any, handleSendResult: (data: OB11Return) => } -export function startExpress(port: number) { - const app = express(); - // 中间件,用于解析POST请求的请求体 - app.use(express.urlencoded({extended: true, limit: "500mb"})); - app.use(express.json({ - limit: '500mb', - verify: (req: any, res: any, buf: any, encoding: any) => { - req.rawBody = buf; - } - })); - app.use((req: any, res: any, next: any) => { - try { - req.body = JSONbig.parse(req.rawBody.toString()); - next(); - } catch (error) { - // next(error); - next(); - } +const expressAPP = express(); +expressAPP.use(express.urlencoded({extended: true, limit: "500mb"})); + +expressAPP.use((req, res, next) => { + let data = ''; + req.on('data', chunk => { + data += chunk.toString(); }); - - async function registerRouter(action: OB11ApiName, handle: (payload: PayloadType) => Promise>) { - let url = action.toString() - if (!action.startsWith("/")){ - url = "/" + action - } - async function _handle(res: Response, payload: PayloadType) { - res.send(await handle(payload)) + req.on('end', () => { + if (data) { + try { + // log("receive raw", data) + req.body = JSONbig.parse(data); + } catch (e) { + return next(e); } + } + next(); + }); + }); +// expressAPP.use(express.json({ +// limit: '500mb', +// verify: (req: any, res: any, buf: any, encoding: any) => { +// req.rawBody = buf; +// } +// })); - app.post(url, (req: Request, res: Response) => { - _handle(res, req.body).then() - }); - app.get(url, (req: Request, res: Response) => { - _handle(res, req.query as any).then() - }); - } +export function startExpress(port: number) { - function parseToOnebot12(action: OB11ApiName) { - app.post('/' + action, (req: Request, res: Response) => { - let jsonData: PostDataSendMsg = req.body; - jsonData.action = action - let resData = handlePost(jsonData, (data: OB11Return) => { - res.send(data) - }) - if (resData) { - res.send(resData) - } - }); - } + + // function parseToOnebot12(action: OB11ApiName) { + // expressAPP.post('/' + action, (req: Request, res: Response) => { + // let jsonData: PostDataSendMsg = req.body; + // jsonData.action = action + // let resData = handlePost(jsonData, (data: OB11Return) => { + // res.send(data) + // }) + // if (resData) { + // res.send(resData) + // } + // }); + // } const actionList: OB11ApiName[] = ["get_login_info", "send_private_msg", "send_group_msg", "get_group_list", "get_friend_list", "delete_msg", "get_group_member_list", "get_group_member_info"] - for (const action of actionList) { - parseToOnebot12(action as OB11ApiName) - } + // for (const action of actionList) { + // parseToOnebot12(action as OB11ApiName) + // } - app.get('/', (req: Request, res: Response) => { + expressAPP.get('/', (req: Request, res: Response) => { res.send('llonebot已启动'); }) // 处理POST请求的路由 - app.post('/', (req: Request, res: Response) => { - let jsonData: PostDataSendMsg = req.body; - let resData = handlePost(jsonData, (data: OB11Return) => { - res.send(data) - }) - if (resData) { - res.send(resData) - } - }); - app.post('/send_msg', (req: Request, res: Response) => { - let jsonData: PostDataSendMsg = req.body; - if (jsonData.message_type == "private") { - jsonData.action = "send_private_msg" - } else if (jsonData.message_type == "group") { - jsonData.action = "send_group_msg" - } else { - if (jsonData.params.group_id) { - jsonData.action = "send_group_msg" - } else { - jsonData.action = "send_private_msg" - } - } - let resData = handlePost(jsonData, (data: OB11Return) => { - res.send(data) - }) - if (resData) { - res.send(resData) - } - }) + // expressAPP.post('/', (req: Request, res: Response) => { + // let jsonData: PostDataSendMsg = req.body; + // let resData = handlePost(jsonData, (data: OB11Return) => { + // res.send(data) + // }) + // if (resData) { + // res.send(resData) + // } + // }); + // expressAPP.post('/send_msg', (req: Request, res: Response) => { + // let jsonData: PostDataSendMsg = req.body; + // if (jsonData.message_type == "private") { + // jsonData.action = "send_private_msg" + // } else if (jsonData.message_type == "group") { + // jsonData.action = "send_group_msg" + // } else { + // if (jsonData.params?.group_id) { + // jsonData.action = "send_group_msg" + // } else { + // jsonData.action = "send_private_msg" + // } + // } + // let resData = handlePost(jsonData, (data: OB11Return) => { + // res.send(data) + // }) + // if (resData) { + // res.send(resData) + // } + // }) - registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => { - const msg = msgHistory[payload.message_id.toString()] - if (msg) { - const msgData = await OB11Constructor.message(msg); - return constructReturnData(0, msgData) - } else { - return constructReturnData(1, {}, "消息不存在") - } - }).then(()=>{ - - }) - - app.listen(port, "0.0.0.0", () => { + expressAPP.listen(port, "0.0.0.0", () => { console.log(`llonebot started 0.0.0.0:${port}`); }); } @@ -291,4 +279,196 @@ export function postMsg(msg: OB11Message) { log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg)); }); } -} \ No newline at end of file +} + +let routers: RecordPromise>> = {}; + +function registerRouter(action: OB11ApiName, handle: (payload: PayloadType) => Promise>) { + let url = action.toString() + if (!action.startsWith("/")){ + url = "/" + action + } + async function _handle(res: Response, payload: PayloadType) { + log("receive post data", url, payload) + try{ + const result = await handle(payload) + res.send(result) + } + catch(e){ + log(e.stack); + res.send(constructErrorReturn(e.stack.toString())) + } + } + + expressAPP.post(url, (req: Request, res: Response) => { + _handle(res, req.body).then() + }); + expressAPP.get(url, (req: Request, res: Response) => { + _handle(res, req.query as any).then() + }); + routers[url] = handle +} + +registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => { + log("history msg ids", Object.keys(msgHistory)); + const msg = msgHistory[payload.message_id.toString()] + if (msg) { + const msgData = await OB11Constructor.message(msg); + return constructReturnData(msgData) + } else { + return constructErrorReturn("消息不存在") + } +}) + +registerRouter<{}, SelfInfo>("get_login_info", async (payload)=>{ + return constructReturnData(selfInfo); +}) + +registerRouter<{}, Friend[]>("get_friend_list", async (payload)=>{ + return constructReturnData(OB11Constructor.friends(friends)); +}) + +registerRouter<{}, OB11Group[]>("get_group_list", async (payload)=>{ + return constructReturnData(OB11Constructor.groups(groups)); +}) + + +registerRouter<{group_id: number}, OB11Group[]>("get_group_info", async (payload)=>{ + const group = await getGroup(payload.group_id.toString()) + if (group){ + return constructReturnData(OB11Constructor.groups(groups)); + } + else{ + return constructErrorReturn(`群${payload.group_id}不存在`) + } +}) + +registerRouter<{group_id: number}, OB11GroupMember[]>("get_group_member_list", async (payload)=>{ + + const group = await getGroup(payload.group_id.toString()); + if (group){ + return constructReturnData(OB11Constructor.groupMembers(group)); + } + else{ + return constructErrorReturn(`群${payload.group_id}不存在`) + } +}) + +registerRouter<{group_id: number, user_id: number}, OB11GroupMember>("get_group_member_info", async (payload)=>{ + const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) + if (member){ + return constructReturnData(OB11Constructor.groupMember(payload.group_id.toString(), member)) + } + else{ + return constructErrorReturn(`群成员${payload.user_id}不存在`) + } +}) + +const handleSendMsg = async (payload)=>{ + const peer: Peer = { + chatType: ChatType.friend, + peerUid: "" + } + let group: Group | undefined = undefined; + if(payload?.group_id){ + group = groups.find(g=>g.uid == payload.group_id?.toString()) + if (!group){ + return constructErrorReturn(`群${payload.group_id}不存在`) + } + peer.chatType = ChatType.group + // peer.name = group.name + peer.peerUid = group.uid + } + else if (payload?.user_id){ + const friend = friends.find(f=>f.uin == payload.user_id.toString()) + if (friend){ + // peer.name = friend.nickName + peer.peerUid = friend.uid + } + else{ + peer.chatType = ChatType.temp + const tempUser = getStrangerByUin(payload.user_id.toString()) + if (!tempUser){ + return constructErrorReturn(`找不到私聊对象${payload.user_id}`) + } + // peer.name = tempUser.nickName + peer.peerUid = tempUser.uid + } + } + if (typeof payload.message === "string"){ + payload.message = [{ + type: OB11MessageDataType.text, + data: { + text: payload.message + } + }] as OB11MessageData[] + } + else if (!Array.isArray(payload.message)){ + payload.message = [payload.message] + } + const sendElements: SendMessageElement[] = [] + for (let sendMsg of payload.message){ + switch(sendMsg.type){ + case OB11MessageDataType.text: { + const text = sendMsg.data?.text; + if (text){ + sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) + } + }break; + case OB11MessageDataType.at: { + let atQQ = sendMsg.data?.qq; + if (atQQ){ + atQQ = atQQ.toString() + if (atQQ === "all"){ + sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) + } + else{ + const atMember = group?.members.find(m=>m.uin == atQQ) + if (atMember){ + sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) + } + } + } + }break; + case OB11MessageDataType.reply: { + let replyMsgId = sendMsg.data.id; + if (replyMsgId){ + replyMsgId = replyMsgId.toString() + const replyMsg = msgHistory[replyMsgId] + if (replyMsg){ + sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin)) + } + } + }break; + case OB11MessageDataType.image: { + const file = sendMsg.data?.file + if (file){ + const picPath = await (await uri2local(uuid4(), file)).path + if (picPath){ + sendElements.push(await SendMsgElementConstructor.pic(picPath)) + } + } + }break; + case OB11MessageDataType.voice: { + const file = sendMsg.data?.file + if (file){ + const voicePath = await (await uri2local(uuid4(), file)).path + if (voicePath){ + sendElements.push(await SendMsgElementConstructor.ptt(voicePath)) + } + } + } + } + } + log("send msg:", peer, sendElements) + try{ + const returnMsg = await NTQQApi.sendMsg(peer, sendElements) + return constructReturnData({message_id: returnMsg.msgId}) + }catch(e){ + return constructErrorReturn(e.toString()) + } +} + +registerRouter("send_msg", handleSendMsg) +registerRouter("send_private_msg", handleSendMsg) +registerRouter("send_group_msg", handleSendMsg) \ No newline at end of file diff --git a/webpack.base.config.js b/webpack.base.config.js index c08821c..0e33688 100644 --- a/webpack.base.config.js +++ b/webpack.base.config.js @@ -50,7 +50,7 @@ module.exports = { ] }, optimization: { - minimize: true, + minimize: false, minimizer: [ new TerserPlugin({ extractComments: false, From 8d2353a5245e360a83302ab78bf28398a3970230 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Sun, 11 Feb 2024 19:57:20 +0800 Subject: [PATCH 06/14] refactor: pre-release --- src/common/channels.ts | 13 - src/common/data.ts | 35 +-- src/common/types.ts | 7 - src/common/utils.ts | 8 +- src/main/ipcsend.ts | 44 +--- src/main/main.ts | 114 ++++----- src/ntqqapi/constructor.ts | 3 +- src/ntqqapi/hook.ts | 57 +++-- src/ntqqapi/ntcall.ts | 168 +++++++++---- src/ntqqapi/types.ts | 13 +- src/onebot11/constructor.ts | 28 ++- src/onebot11/server.ts | 331 +++++++++++++++++++++++++ src/onebot11/types.ts | 6 +- src/preload.ts | 63 +---- src/renderer.ts | 2 + src/server/httpserver.ts | 474 ------------------------------------ 16 files changed, 607 insertions(+), 759 deletions(-) create mode 100644 src/onebot11/server.ts delete mode 100644 src/server/httpserver.ts diff --git a/src/common/channels.ts b/src/common/channels.ts index 76d1e08..0005841 100644 --- a/src/common/channels.ts +++ b/src/common/channels.ts @@ -1,16 +1,3 @@ -export const CHANNEL_SEND_MSG = "llonebot_send_msg" -export const CHANNEL_SEND_BACK_MSG = "llonebot_send_back_msg" -export const CHANNEL_RECALL_MSG = "llonebot_recall_msg" export const CHANNEL_GET_CONFIG = "llonebot_get_config" export const CHANNEL_SET_CONFIG = "llonebot_set_config" -export const CHANNEL_START_HTTP_SERVER = "llonebot_start_http_server" -export const CHANNEL_UPDATE_GROUPS = "llonebot_update_groups" -export const CHANNEL_UPDATE_FRIENDS = "llonebot_update_friends" export const CHANNEL_LOG = "llonebot_log" -export const CHANNEL_POST_ONEBOT_DATA = "llonebot_post_onebot_data" -export const CHANNEL_SET_SELF_INFO= "llonebot_set_self_info" -export const CHANNEL_DOWNLOAD_FILE= "llonebot_download_file" -export const CHANNEL_DELETE_FILE= "llonebot_delete_file" -export const CHANNEL_GET_RUNNING_STATUS= "llonebot_get_running_status" -export const CHANNEL_FILE2BASE64= "llonebot_file2base64" -export const CHANNEL_GET_HISTORY_MSG= "llonebot_get_history_msg" \ No newline at end of file diff --git a/src/common/data.ts b/src/common/data.ts index bd4fcd0..50e50ee 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -1,26 +1,25 @@ -import {SelfInfo} from "./types"; import { NTQQApi } from '../ntqqapi/ntcall'; -import { Group, RawMessage, User } from "../ntqqapi/types"; +import { Friend, Group, RawMessage, SelfInfo } from "../ntqqapi/types"; export let groups: Group[] = [] -export let friends: User[] = [] +export let friends: Friend[] = [] export let msgHistory: Record = {} // msgId: RawMessage -export async function getFriend(qq: string): Promise { +export async function getFriend(qq: string): Promise { let friend = friends.find(friend => friend.uin === qq) - if (!friend){ - friends = await NTQQApi.getFriends(true) - friend = friends.find(friend => friend.uin === qq) - } + // if (!friend){ + // friends = (await NTQQApi.getFriends(true)) + // friend = friends.find(friend => friend.uin === qq) + // } return friend } export async function getGroup(qq: string): Promise { let group = groups.find(group => group.groupCode === qq) - if (!group){ - groups = await NTQQApi.getGroups(true); - group = groups.find(group => group.groupCode === qq) - } + // if (!group){ + // groups = await NTQQApi.getGroups(true); + // group = groups.find(group => group.groupCode === qq) + // } return group } @@ -29,7 +28,10 @@ export async function getGroupMember(groupQQ: string, memberQQ: string) { if (group) { let member = group.members?.find(member => member.uin === memberQQ) if (!member){ - group.members = await NTQQApi.getGroupMembers(groupQQ) + const _members = await NTQQApi.getGroupMembers(groupQQ) + if (_members.length){ + group.members = _members + } member = group.members?.find(member => member.uin === memberQQ) } return member @@ -37,8 +39,9 @@ export async function getGroupMember(groupQQ: string, memberQQ: string) { } export let selfInfo: SelfInfo = { - user_id: "", - nickname: "" + uid: "", + uin: "", + nick: "", } @@ -47,7 +50,7 @@ export function getHistoryMsgBySeq(seq: string) { } -export let uidMaps:Record = {} // 一串加密的字符串(uid) -> qq号 +export let uidMaps:Record = {} // 一串加密的字符串(uid) -> qq号 export function getStrangerByUin(uin: string) { for (const key in uidMaps) { diff --git a/src/common/types.ts b/src/common/types.ts index 0a9d7b2..5b892ab 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,10 +1,3 @@ -import {OB11ApiName, OB11MessageData} from "../onebot11/types"; - -export interface SelfInfo { - user_id: string; - nickname: string; -} - export interface Config { port: number hosts: string[] diff --git a/src/common/utils.ts b/src/common/utils.ts index 04262b2..78a52c2 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,15 +1,15 @@ import * as path from "path"; -import {json} from "express"; import {selfInfo} from "./data"; import {ConfigUtil} from "./config"; import util from "util"; +import { sendLog } from '../main/ipcsend'; const fs = require('fs'); export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data; export function getConfigUtil() { - const configFilePath = path.join(CONFIG_DIR, `config_${selfInfo.user_id}.json`) + const configFilePath = path.join(CONFIG_DIR, `config_${selfInfo.uin}.json`) return new ConfigUtil(configFilePath) } @@ -23,7 +23,7 @@ export function log(...msg: any[]) { const month = date.getMonth() + 1; const day = date.getDate(); const currentDate = `${year}-${month}-${day}`; - const userInfo = selfInfo.user_id ? `${selfInfo.nickname}(${selfInfo.user_id})` : "" + const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : "" let logMsg = ""; for (let msgItem of msg){ // 判断是否是对象 @@ -34,6 +34,8 @@ export function log(...msg: any[]) { logMsg += msgItem + " "; } logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n` + // sendLog(...msg); + // console.log(msg) fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), logMsg, (err: any) => { }) diff --git a/src/main/ipcsend.ts b/src/main/ipcsend.ts index 34c4016..b40da4f 100644 --- a/src/main/ipcsend.ts +++ b/src/main/ipcsend.ts @@ -1,50 +1,18 @@ -import {ipcMain, webContents} from 'electron'; -import {OB11PostSendMsg} from "../onebot11/types" -import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG,CHANNEL_SEND_BACK_MSG} from "../common/channels"; -import {v4 as uuid4} from "uuid"; -import {log} from "../common/utils"; +import {webContents} from 'electron'; +import { CHANNEL_LOG } from '../common/channels'; -import {OB11Return} from "../onebot11/types"; - -function sendIPCMsg(channel: string, data: any) { +function sendIPCMsg(channel: string, ...data: any) { let contents = webContents.getAllWebContents(); for (const content of contents) { try { - content.send(channel, data) + content.send(channel, ...data) } catch (e) { console.log("llonebot send ipc msg to render error:", e) } } } -export interface SendIPCMsgSession { - id: string - data: T +export function sendLog(...args){ + sendIPCMsg(CHANNEL_LOG, ...args) } - -export function sendIPCSendQQMsg(postData: OB11PostSendMsg, handleSendResult: (data: OB11Return) => void) { - const onceSessionId = uuid4(); - const handler = (event: any, session: SendIPCMsgSession>) => { - // log("llonebot send msg ipcMain.once:" + JSON.stringify(sendResult)); - if (session?.id !== onceSessionId) { - return - } - try { - handleSendResult(session.data) - ipcMain.off(CHANNEL_SEND_BACK_MSG, handler) - return - } catch (e) { - log("llonebot send msg sendIPCSendQQMsg handler error:" + JSON.stringify(e)) - } - } - ipcMain.on(CHANNEL_SEND_BACK_MSG, handler) - sendIPCMsg(CHANNEL_SEND_MSG, { - id: onceSessionId, - data: postData, - }); -} - -export function sendIPCRecallQQMsg(message_id: string) { - sendIPCMsg(CHANNEL_RECALL_MSG, { message_id: message_id }); -} \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 254ce51..83fa96c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,29 +1,23 @@ // 运行在 Electron 主进程 下的插件入口 import * as path from "path"; -import {BrowserWindow, ipcMain} from 'electron'; +import { BrowserWindow, ipcMain } from 'electron'; import * as util from 'util'; -import {Config, Group, RawMessage, SelfInfo, User} from "../common/types"; +import { Config } from "../common/types"; import { - CHANNEL_DOWNLOAD_FILE, CHANNEL_GET_CONFIG, - CHANNEL_SET_SELF_INFO, CHANNEL_LOG, - CHANNEL_POST_ONEBOT_DATA, CHANNEL_SET_CONFIG, - CHANNEL_START_HTTP_SERVER, - CHANNEL_UPDATE_FRIENDS, - CHANNEL_UPDATE_GROUPS, CHANNEL_DELETE_FILE, CHANNEL_GET_RUNNING_STATUS, CHANNEL_FILE2BASE64, CHANNEL_GET_HISTORY_MSG } from "../common/channels"; -import {ConfigUtil} from "../common/config"; -import {postMsg, startExpress} from "../server/httpserver"; -import {checkFileReceived, CONFIG_DIR, file2base64, getConfigUtil, isGIF, log} from "../common/utils"; -import {friends, groups, msgHistory, selfInfo} from "../common/data"; -import {} from "../global"; -import {hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook"; -import {OB11Constructor} from "../onebot11/constructor"; -import {NTQQApi} from "../ntqqapi/ntcall"; +import { ConfigUtil } from "../common/config"; +import { postMsg, startExpress } from "../onebot11/server"; +import { CONFIG_DIR, getConfigUtil, log } from "../common/utils"; +import { friends, groups, msgHistory, selfInfo } from "../common/data"; +import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook"; +import { OB11Constructor } from "../onebot11/constructor"; +import { NTQQApi } from "../ntqqapi/ntcall"; +import { Group, RawMessage, SelfInfo } from "../ntqqapi/types"; const fs = require('fs'); @@ -38,7 +32,7 @@ function onLoad() { if (!fs.existsSync(CONFIG_DIR)) { - fs.mkdirSync(CONFIG_DIR, {recursive: true}); + fs.mkdirSync(CONFIG_DIR, { recursive: true }); } ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => { return getConfigUtil().getConfig() @@ -52,18 +46,18 @@ function onLoad() { }) - function postRawMsg(msgList:RawMessage[]) { - const {debug, reportSelfMessage} = getConfigUtil().getConfig(); + function postRawMsg(msgList: RawMessage[]) { + const { debug, reportSelfMessage } = getConfigUtil().getConfig(); for (const message of msgList) { OB11Constructor.message(message).then((msg) => { if (debug) { msg.raw = message; } - if (msg.user_id == selfInfo.user_id && !reportSelfMessage) { + if (msg.user_id == selfInfo.uin && !reportSelfMessage) { return } postMsg(msg); - }).catch(e=>log("constructMessage error: ", e.toString())); + }).catch(e => log("constructMessage error: ", e.toString())); } } @@ -76,7 +70,7 @@ function onLoad() { }) registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => { - const {reportSelfMessage} = getConfigUtil().getConfig() + const { reportSelfMessage } = getConfigUtil().getConfig() if (!reportSelfMessage) { return } @@ -87,46 +81,56 @@ function onLoad() { log("report self message error: ", e.toString()) } }) - - async function getSelfInfo(){ - if (!selfInfo.user_id){ - setTimeout(()=>{ - getSelfInfo().then() - }) + + async function getSelfInfo() { + try{ + const _ = await NTQQApi.getSelfInfo() + Object.assign(selfInfo, _) + selfInfo.nick = selfInfo.uin + log("get self simple info", _) + }catch(e){ + log("retry get self info") + } - const _ = await NTQQApi.getSelfInfo() - if (_.uin){ - log("get self info success", _) - selfInfo.user_id = _.uin - let nickName = _.uin - try{ - const userInfo = (await NTQQApi.getUserInfo(_.uid)) - if (userInfo){ - nickName = userInfo.nickName + if (selfInfo.uin) { + try { + const userInfo = (await NTQQApi.getUserInfo(selfInfo.uid)) + if (userInfo) { + selfInfo.nick = userInfo.nick } } - catch(e){ + catch (e) { log("get self nickname failed", e.toString()) } - selfInfo.nickname = nickName - try{ - // let _friends = await NTQQApi.getFriends(true) - // log("friends api:", _friends) - // for (let f of _friends){ - // friends.push(f) - // } - let _groups = await NTQQApi.getGroups(true) - log("groups api:", _groups) - for (let g of _groups){ - g.members = (await NTQQApi.getGroupMembers(g.uid)) - groups.push(g) - } + // try { + // friends.push(...(await NTQQApi.getFriends(true))) + // log("get friends", friends) + // let _groups: Group[] = [] + // for(let i=0; i++; i<3){ + // try{ + // _groups = await NTQQApi.getGroups(true) + // log("get groups sucess", _groups) + // break + // } catch(e) { + // log("get groups failed", e) + // } + // } + // for (let g of _groups) { + // g.members = (await NTQQApi.getGroupMembers(g.groupCode)) + // log("group members", g.members) + // groups.push(g) + // } - }catch(e){ - log("!!!初始化失败", e.stack.toString()) - } + // } catch (e) { + // log("!!!初始化失败", e.stack.toString()) + // } startExpress(getConfigUtil().getConfig().port) } + else{ + setTimeout(() => { + getSelfInfo().then() + }, 100) + } } getSelfInfo().then() } @@ -136,7 +140,7 @@ function onLoad() { function onBrowserWindowCreated(window: BrowserWindow) { try { hookNTQQApiReceive(window); - } catch (e){ + } catch (e) { log("llonebot hook error: ", e.toString()) } } diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index d140385..76a38b0 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -1,5 +1,4 @@ -import {AtType} from "../common/types"; -import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement} from "./types"; +import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, AtType} from "./types"; import {NTQQApi} from "./ntcall"; diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index fc3cba7..a24131c 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -1,9 +1,10 @@ import {BrowserWindow} from 'electron'; import {getConfigUtil, log} from "../common/utils"; -import {NTQQApiClass, sendMessagePool} from "./ntcall"; -import { Group } from "./types"; +import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall"; +import { Group, User } from "./types"; import { RawMessage } from "./types"; -import {groups, msgHistory} from "../common/data"; +import {friends, groups, msgHistory} from "../common/data"; +import { v4 as uuidv4 } from 'uuid'; export let hookApiCallbacks: Recordvoid>={} @@ -13,7 +14,8 @@ export enum ReceiveCmd { SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg", USER_INFO = "nodeIKernelProfileListener/onProfileDetailInfoChanged", GROUPS = "nodeIKernelGroupListener/onGroupListUpdate", - GROUPS_UNIX = "onGroupListUpdate" + GROUPS_UNIX = "onGroupListUpdate", + FRIENDS = "onBuddyListChange" } interface NTQQApiReturnData extends Array { @@ -32,14 +34,14 @@ interface NTQQApiReturnData extends Array { let receiveHooks: Array<{ method: ReceiveCmd, - hookFunc: (payload: any) => void + hookFunc: (payload: any) => void, + id: string }> = [] export function hookNTQQApiReceive(window: BrowserWindow) { const originalSend = window.webContents.send; const patchSend = (channel: string, ...args: NTQQApiReturnData) => { - // 判断是否是列表 - log(`received ntqq api message: ${channel}`, JSON.stringify(args)) + // log(`received ntqq api message: ${channel}`, JSON.stringify(args)) if (args?.[1] instanceof Array) { for (let receiveData of args?.[1]) { const ntQQApiMethodName = receiveData.cmdName; @@ -73,32 +75,53 @@ export function hookNTQQApiReceive(window: BrowserWindow) { window.webContents.send = patchSend; } -export function registerReceiveHook(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void) { +export function registerReceiveHook(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void): string { + const id = uuidv4() receiveHooks.push({ method, - hookFunc + hookFunc, + id }) + return id; } -function updateGroups(_groups: Group[]){ +export function removeReceiveHook(id: string){ + const index = receiveHooks.findIndex(h=>h.id === id) + receiveHooks.splice(index, 1); +} + +async function updateGroups(_groups: Group[]){ for(let group of _groups){ let existGroup = groups.find(g=>g.groupCode == group.groupCode) if (!existGroup){ - groups.push(group) + log("update group") + let _membeers = await NTQQApi.getGroupMembers(group.groupCode) + if (_membeers){ + group.members = _membeers + } + log("update group members", group.members) } else{ - Object.assign(existGroup, group); + group.members = [...existGroup.members] } } + groups.length = 0; + groups.push(..._groups) } -registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS, (payload)=>updateGroups(payload.groupList)) -registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS_UNIX, (payload)=>updateGroups(payload.groupList)) - -registerReceiveHook(ReceiveCmd.USER_INFO, (payload)=>{ - log("user info", payload); +registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS, (payload)=>updateGroups(payload.groupList).then()) +registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS_UNIX, (payload)=>updateGroups(payload.groupList).then()) +registerReceiveHook<{data:{categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[]}[]}>(ReceiveCmd.FRIENDS, payload=>{ + friends.length = 0 + for (const fData of payload.data) { + friends.push(...fData.buddyList) + } }) +// registerReceiveHook(ReceiveCmd.USER_INFO, (payload)=>{ +// log("user info", payload); +// }) + registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, (payload) => { for (const message of payload.msgList) { msgHistory[message.msgId] = message; diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 0b9ed34..4a56332 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -1,13 +1,12 @@ -import {ipcMain} from "electron"; -import {v4 as uuidv4} from "uuid"; -import {hookApiCallbacks} from "./hook"; -import {log} from "../common/utils"; -import { ChatType } from "./types"; +import { ipcMain } from "electron"; +import { v4 as uuidv4 } from "uuid"; +import { ReceiveCmd, hookApiCallbacks, registerReceiveHook, removeReceiveHook } from "./hook"; +import { log } from "../common/utils"; +import { ChatType, Friend, SelfInfo, User } from "./types"; import { Group } from "./types"; import { GroupMember } from "./types"; import { RawMessage } from "./types"; -import { User } from "./types"; -import {SendMessageElement} from "./types"; +import { SendMessageElement } from "./types"; interface IPCReceiveEvent { eventName: string @@ -31,7 +30,7 @@ export enum NTQQApiMethod { LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike", UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate", SELF_INFO = "fetchAuthData", - FRIENDS = "nodeIKernelProfileService/getBuddyProfileList", + FRIENDS = "nodeIKernelBuddyService/getBuddyList", GROUPS = "nodeIKernelGroupService/getGroupList", GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene", GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList", @@ -58,26 +57,65 @@ export interface Peer { guildId?: "" } +enum CallBackType { + UUID, + METHOD +} -function callNTQQApi(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[] = []) { + +function callNTQQApi(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[] = [], cbCmd: ReceiveCmd | null = null, timeout = 5) { const uuid = uuidv4(); // 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; + const _timeout = timeout * 1000 + let success = false + if (!cbCmd) { + // QQ后端会返回结果,并且可以插根据uuid识别 + hookApiCallbacks[uuid] = (r: ReturnType) => { + success = true + resolve(r) + }; + } + else { + // 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据 + hookApiCallbacks[uuid] = (result: GeneralCallResult) => { + log(`${methodName} callback`, result) + if (result.result == 0) { + const hookId = registerReceiveHook(cbCmd, (payload) => { + log(methodName, "second callback", cbCmd, payload); + removeReceiveHook(hookId); + success = true + resolve(payload); + }) + } + else { + success = true + reject(`ntqq api call failed, ${result.errMsg}`); + } + } + } + setTimeout(() => { + // log("ntqq api timeout", success, channel, className, methodName) + if (!success) { + log(`ntqq api timeout ${channel}, ${className}, ${methodName}`) + reject(`ntqq api timeout ${channel}, ${className}, ${methodName}`) + } + }, _timeout) + ipcMain.emit( channel, {}, - {type: 'request', callbackId: uuid, eventName: className + "-" + channel[channel.length - 1]}, + { type: 'request', callbackId: uuid, eventName: className + "-" + channel[channel.length - 1] }, [methodName, ...args], ) }) } -export let sendMessagePool: Recordvoid) | null> = {}// peerUid: callbackFunnc +export let sendMessagePool: Record void) | null> = {}// peerUid: callbackFunnc -interface GeneralCallResult{ - result:0, +interface GeneralCallResult { + result: number, // 0: success errMsg: string } @@ -97,41 +135,63 @@ export class NTQQApi { } static getSelfInfo() { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.GLOBAL_DATA, NTQQApiMethod.SELF_INFO, []) + return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.GLOBAL_DATA, NTQQApiMethod.SELF_INFO, [], null, 2) } - 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(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{force_update: forced}, undefined]) - } - - static async getGroupMembers(groupQQ: string, num = 5000) { - const sceneId = callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{ - groupCode: groupQQ, - scene: "groupMemberList_MainWindow" - }] - ) - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBERS, - [{ - sceneId: sceneId, - num: num - }, - null - ]) - } - static async getUserInfo(uid: string) { - const result = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO, - [{force: true, uids: [uid]}, undefined]) - log("get user info result", result); - return result[0].payload.profiles.get(uid); - + const result = await callNTQQApi<{ info: User }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO, + [{ force: true, uids: [uid] }, undefined], ReceiveCmd.USER_INFO) + return result.info + } + // static async getFriends(forced = false) { + // const data = await callNTQQApi<{ data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: Friend[] }[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{ force_update: forced }, undefined], ReceiveCmd.FRIENDS) + // let _friends: Friend[] = []; + // for (const fData of data.data) { + // _friends.push(...fData.buddyList) + // } + // return _friends + // } + + // static async getGroups(forced = false) { + // let cbCmd = ReceiveCmd.GROUPS + // if (process.platform != "win32") { + // cbCmd = ReceiveCmd.GROUPS_UNIX + // } + // const result = await callNTQQApi<{ updateType: number, groupList: Group[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{ force_update: forced }, undefined], cbCmd) + // return result.groupList + // } + + static async getGroupMembers(groupQQ: string, num = 3000) { + const sceneId = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{ + groupCode: groupQQ, + scene: "groupMemberList_MainWindow" + }]) + // log("get group member sceneId", sceneId); + try { + const result = await callNTQQApi<{result:{infos: any}}>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBERS, + [{ + sceneId: sceneId, + num: num + }, + null + ]) + // log("members info", typeof result.result.infos, Object.keys(result.result.infos)) + let values = result.result.infos.values() + + values = Array.from(values) as GroupMember[] + // log("members info", values); + return values + } catch (e) { + log(`get group ${groupQQ} members failed`, e) + return [] + } + } + + + static getFileType(filePath: string) { return callNTQQApi<{ ext: string @@ -184,40 +244,40 @@ export class NTQQApi { } } - static recallMsg(peer: Peer, msgIds: string[]){ - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.RECALL_MSG, [{peer, msgIds}, null]) + 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[]){ + static sendMsg(peer: Peer, msgElements: SendMessageElement[]) { const sendTimeout = 10 * 1000 - return new Promise((resolve, reject)=>{ + return new Promise((resolve, reject) => { const peerUid = peer.peerUid; let usingTime = 0; let success = false; - const checkSuccess = ()=>{ - if (!success){ + const checkSuccess = () => { + if (!success) { sendMessagePool[peerUid] = null; reject("发送超时") } } setTimeout(checkSuccess, sendTimeout); - const checkLastSend = ()=>{ + const checkLastSend = () => { let lastSending = sendMessagePool[peerUid] - if (sendTimeout < usingTime){ + if (sendTimeout < usingTime) { sendMessagePool[peerUid] = null; reject("发送超时") } - if (!!lastSending){ + if (!!lastSending) { // log("有正在发送的消息,等待中...") usingTime += 100; setTimeout(checkLastSend, 100); } - else{ + else { log("可以进行发送消息,设置发送成功回调", sendMessagePool) - sendMessagePool[peerUid] = (rawMessage: RawMessage)=>{ + sendMessagePool[peerUid] = (rawMessage: RawMessage) => { success = true; sendMessagePool[peerUid] = null; resolve(rawMessage); diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts index fa32cd0..493d2fc 100644 --- a/src/ntqqapi/types.ts +++ b/src/ntqqapi/types.ts @@ -1,15 +1,18 @@ - export interface User { uid: string; // 加密的字符串 uin: string; // QQ号 nick: string; avatarUrl?: string; - longNick: string; // 签名 - raw: { - remark: string; - }; + longNick?: string; // 签名 + remark?: string } +export interface SelfInfo extends User{ + +} + +export interface Friend extends User{} + export interface Group{ groupCode: string, maxMember: number, diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index 2b5e1a0..0cc2fd5 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -1,7 +1,7 @@ -import {OB11MessageDataType, OB11GroupMemberRole, OB11Message, OB11MessageData, OB11Group, OB11GroupMember, Friend} from "./types"; -import { AtType, ChatType, Group, GroupMember, RawMessage, User } from '../ntqqapi/types'; -import {getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo} from "../common/data"; -import {file2base64, getConfigUtil} from "../common/utils"; +import {OB11MessageDataType, OB11GroupMemberRole, OB11Message, OB11MessageData, OB11Group, OB11GroupMember, OB11User} from "./types"; +import { AtType, ChatType, Group, GroupMember, RawMessage, SelfInfo, User } from '../ntqqapi/types'; +import { getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo } from '../common/data'; +import {file2base64, getConfigUtil, log} from "../common/utils"; export class OB11Constructor { @@ -9,7 +9,7 @@ export class OB11Constructor { const {enableBase64} = getConfigUtil().getConfig() const message_type = msg.chatType == ChatType.group ? "group" : "private"; const resMsg: OB11Message = { - self_id: selfInfo.user_id, + self_id: selfInfo.uin, user_id: msg.senderUin, time: parseInt(msg.msgTime) || 0, message_id: msg.msgId, @@ -36,7 +36,7 @@ export class OB11Constructor { resMsg.sub_type = "friend" const friend = await getFriend(msg.senderUin); if (friend) { - resMsg.sender.nickname = friend.nickName; + resMsg.sender.nickname = friend.nick; } } else if (msg.chatType == ChatType.temp) { resMsg.sub_type = "group" @@ -119,16 +119,23 @@ export class OB11Constructor { return resMsg; } - static friend(friend: User): Friend{ + static friend(friend: User): OB11User{ return { user_id: friend.uin, - nickname: friend.nickName, - remark: friend.raw.remark + nickname: friend.nick, + remark: friend.remark } } - static friends(friends: User[]): Friend[]{ + static selfInfo(selfInfo: SelfInfo): OB11User{ + return { + user_id: selfInfo.uin, + nickname: selfInfo.nick + } + } + + static friends(friends: User[]): OB11User[]{ return friends.map(OB11Constructor.friend) } @@ -150,6 +157,7 @@ export class OB11Constructor { } static groupMembers(group: Group): OB11GroupMember[]{ + log("construct ob11 group members", group) return group.members.map(m=>OB11Constructor.groupMember(group.groupCode, m)) } diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts new file mode 100644 index 0000000..ec178e5 --- /dev/null +++ b/src/onebot11/server.ts @@ -0,0 +1,331 @@ +import { getConfigUtil, log } from "../common/utils"; + +const express = require("express"); +import { Request } from 'express'; +import { Response } from 'express'; + +const JSONbig = require('json-bigint')({ storeAsString: true }); +import { AtType, ChatType, Group, SelfInfo } from "../ntqqapi/types"; +import { friends, getGroup, getGroupMember, getStrangerByUin, groups, msgHistory, selfInfo } from "../common/data"; +import { OB11ApiName, OB11Message, OB11Return, OB11MessageData, OB11Group, OB11GroupMember, OB11PostSendMsg, OB11MessageDataType, OB11User } from './types'; +import { OB11Constructor } from "./constructor"; +import { NTQQApi } from "../ntqqapi/ntcall"; +import { Peer } from "../ntqqapi/ntcall"; +import { SendMessageElement } from "../ntqqapi/types"; +import { SendMsgElementConstructor } from "../ntqqapi/constructor"; +import { uri2local } from "./utils"; +import { v4 as uuid4 } from 'uuid'; + + +// @SiberianHusky 2021-08-15 +function checkSendMessage(sendMsgList: OB11MessageData[]) { + function checkUri(uri: string): boolean { + const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/; + return pattern.test(uri); + } + + for (let msg of sendMsgList) { + if (msg["type"] && msg["data"]) { + let type = msg["type"]; + let data = msg["data"]; + if (type === "text" && !data["text"]) { + return 400; + } else if (["image", "voice", "record"].includes(type)) { + if (!data["file"]) { + return 400; + } else { + if (checkUri(data["file"])) { + return 200; + } else { + return 400; + } + } + + } else if (type === "at" && !data["qq"]) { + return 400; + } else if (type === "reply" && !data["id"]) { + return 400; + } + } else { + return 400 + } + } + return 200; +} + +// ==end== + + +class OB11Response { + static res(data: T, status: number = 0, message: string = ""): OB11Return { + return { + status: status, + retcode: status, + data: data, + message: message + } + } + static ok(data: T) { + return OB11Response.res(data) + } + static error(err: string) { + return OB11Response.res(null, -1, err) + } +} + +const expressAPP = express(); +expressAPP.use(express.urlencoded({ extended: true, limit: "500mb" })); + +expressAPP.use((req, res, next) => { + let data = ''; + req.on('data', chunk => { + data += chunk.toString(); + }); + req.on('end', () => { + if (data) { + try { + // log("receive raw", data) + req.body = JSONbig.parse(data); + } catch (e) { + return next(e); + } + } + next(); + }); +}); +// expressAPP.use(express.json({ +// limit: '500mb', +// verify: (req: any, res: any, buf: any, encoding: any) => { +// req.rawBody = buf; +// } +// })); + +export function startExpress(port: number) { + + expressAPP.get('/', (req: Request, res: Response) => { + res.send('llonebot已启动'); + }) + + expressAPP.listen(port, "0.0.0.0", () => { + console.log(`llonebot started 0.0.0.0:${port}`); + }); +} + + +export function postMsg(msg: OB11Message) { + const { reportSelfMessage } = getConfigUtil().getConfig() + if (!reportSelfMessage) { + if (msg.user_id == selfInfo.uin) { + return + } + } + for (const host of getConfigUtil().getConfig().hosts) { + fetch(host, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-self-id": selfInfo.uin + }, + body: JSON.stringify(msg) + }).then((res: any) => { + log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg)); + }, (err: any) => { + log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg)); + }); + } +} + +let routers: Record Promise>> = {}; + +function registerRouter(action: OB11ApiName, handle: (payload: PayloadType) => Promise>) { + let url = action.toString() + if (!action.startsWith("/")) { + url = "/" + action + } + async function _handle(res: Response, payload: PayloadType) { + log("receive post data", url, payload) + try { + const result = await handle(payload) + res.send(result) + } + catch (e) { + log(e.stack); + res.send(OB11Response.error(e.stack.toString())) + } + } + + expressAPP.post(url, (req: Request, res: Response) => { + _handle(res, req.body).then() + }); + expressAPP.get(url, (req: Request, res: Response) => { + _handle(res, req.query as any).then() + }); + routers[url] = handle +} + +registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => { + log("history msg ids", Object.keys(msgHistory)); + const msg = msgHistory[payload.message_id.toString()] + if (msg) { + const msgData = await OB11Constructor.message(msg); + return OB11Response.ok(msgData) + } else { + return OB11Response.error("消息不存在") + } +}) + +registerRouter<{}, OB11User>("get_login_info", async (payload) => { + return OB11Response.ok(OB11Constructor.selfInfo(selfInfo)); +}) + +registerRouter<{}, OB11User[]>("get_friend_list", async (payload) => { + return OB11Response.ok(OB11Constructor.friends(friends)); +}) + +registerRouter<{}, OB11Group[]>("get_group_list", async (payload) => { + return OB11Response.ok(OB11Constructor.groups(groups)); +}) + + +registerRouter<{ group_id: number }, OB11Group[]>("get_group_info", async (payload) => { + const group = await getGroup(payload.group_id.toString()) + if (group) { + return OB11Response.ok(OB11Constructor.groups(groups)); + } + else { + return OB11Response.error(`群${payload.group_id}不存在`) + } +}) + +registerRouter<{ group_id: number }, OB11GroupMember[]>("get_group_member_list", async (payload) => { + + const group = await getGroup(payload.group_id.toString()); + if (group) { + if (!group.members?.length){ + group.members = await NTQQApi.getGroupMembers(payload.group_id.toString()) + } + return OB11Response.ok(OB11Constructor.groupMembers(group)); + } + else { + return OB11Response.error(`群${payload.group_id}不存在`) + } +}) + +registerRouter<{ group_id: number, user_id: number }, OB11GroupMember>("get_group_member_info", async (payload) => { + const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) + if (member) { + return OB11Response.ok(OB11Constructor.groupMember(payload.group_id.toString(), member)) + } + else { + return OB11Response.error(`群成员${payload.user_id}不存在`) + } +}) + +const handleSendMsg = async (payload) => { + const peer: Peer = { + chatType: ChatType.friend, + peerUid: "" + } + let group: Group | undefined = undefined; + if (payload?.group_id) { + group = await getGroup(payload.group_id.toString()) + if (!group) { + return OB11Response.error(`群${payload.group_id}不存在`) + } + peer.chatType = ChatType.group + // peer.name = group.name + peer.peerUid = group.groupCode + } + else if (payload?.user_id) { + const friend = friends.find(f => f.uin == payload.user_id.toString()) + if (friend) { + // peer.name = friend.nickName + peer.peerUid = friend.uid + } + else { + peer.chatType = ChatType.temp + const tempUser = getStrangerByUin(payload.user_id.toString()) + if (!tempUser) { + return OB11Response.error(`找不到私聊对象${payload.user_id}`) + } + // peer.name = tempUser.nickName + peer.peerUid = tempUser.uid + } + } + if (typeof payload.message === "string") { + payload.message = [{ + type: OB11MessageDataType.text, + data: { + text: payload.message + } + }] as OB11MessageData[] + } + else if (!Array.isArray(payload.message)) { + payload.message = [payload.message] + } + const sendElements: SendMessageElement[] = [] + for (let sendMsg of payload.message) { + switch (sendMsg.type) { + case OB11MessageDataType.text: { + const text = sendMsg.data?.text; + if (text) { + sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) + } + } break; + case OB11MessageDataType.at: { + let atQQ = sendMsg.data?.qq; + if (atQQ) { + atQQ = atQQ.toString() + if (atQQ === "all") { + sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) + } + else { + const atMember = group?.members.find(m => m.uin == atQQ) + if (atMember) { + sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) + } + } + } + } break; + case OB11MessageDataType.reply: { + let replyMsgId = sendMsg.data.id; + if (replyMsgId) { + replyMsgId = replyMsgId.toString() + const replyMsg = msgHistory[replyMsgId] + if (replyMsg) { + sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin)) + } + } + } break; + case OB11MessageDataType.image: { + const file = sendMsg.data?.file + if (file) { + const picPath = await (await uri2local(uuid4(), file)).path + if (picPath) { + sendElements.push(await SendMsgElementConstructor.pic(picPath)) + } + } + } break; + case OB11MessageDataType.voice: { + const file = sendMsg.data?.file + if (file) { + const voicePath = await (await uri2local(uuid4(), file)).path + if (voicePath) { + sendElements.push(await SendMsgElementConstructor.ptt(voicePath)) + } + } + } + } + } + log("send msg:", peer, sendElements) + try { + const returnMsg = await NTQQApi.sendMsg(peer, sendElements) + return OB11Response.ok({ message_id: returnMsg.msgId }) + } catch (e) { + return OB11Response.error(e.toString()) + } +} + +registerRouter("send_msg", handleSendMsg) +registerRouter("send_private_msg", handleSendMsg) +registerRouter("send_group_msg", handleSendMsg) \ No newline at end of file diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 2d719c6..513cbf0 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -1,9 +1,9 @@ -import {SelfInfo} from "../common/types"; import { AtType } from "../ntqqapi/types"; import { RawMessage } from "../ntqqapi/types"; -import { User } from "../ntqqapi/types"; -export interface Friend extends SelfInfo{ +export interface OB11User{ + user_id: string; + nickname: string; remark?: string } diff --git a/src/preload.ts b/src/preload.ts index 3fc8bcc..afde46f 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,63 +1,18 @@ // Electron 主进程 与 渲染进程 交互的桥梁 -import {Config, SelfInfo} from "./common/types"; -import { Group } from "./ntqqapi/types"; -import { RawMessage } from "./ntqqapi/types"; -import { User } from "./ntqqapi/types"; +import {Config} from "./common/types"; import { - CHANNEL_DOWNLOAD_FILE, CHANNEL_GET_CONFIG, - CHANNEL_SET_SELF_INFO, CHANNEL_LOG, - CHANNEL_POST_ONEBOT_DATA, - CHANNEL_RECALL_MSG, - CHANNEL_SEND_MSG, CHANNEL_SET_CONFIG, - CHANNEL_START_HTTP_SERVER, - CHANNEL_UPDATE_FRIENDS, - CHANNEL_UPDATE_GROUPS, - CHANNEL_DELETE_FILE, - CHANNEL_GET_RUNNING_STATUS, - CHANNEL_FILE2BASE64, - CHANNEL_GET_HISTORY_MSG, - CHANNEL_SEND_BACK_MSG, } from "./common/channels"; -import {OB11Return, OB11SendMsgReturn} from "./onebot11/types"; -import { SendIPCMsgSession } from "./main/ipcsend"; - - const {contextBridge} = require("electron"); const {ipcRenderer} = require('electron'); // 在window对象下导出只读对象 contextBridge.exposeInMainWorld("llonebot", { - - postData: (data: any) => { - ipcRenderer.send(CHANNEL_POST_ONEBOT_DATA, data); - }, - updateGroups: (groups: Group[]) => { - ipcRenderer.send(CHANNEL_UPDATE_GROUPS, groups); - }, - updateFriends: (friends: User[]) => { - ipcRenderer.send(CHANNEL_UPDATE_FRIENDS, friends); - }, - sendSendMsgResult: (sessionId: string, msgResult: OB11SendMsgReturn)=>{ - ipcRenderer.send(CHANNEL_SEND_BACK_MSG, { - id: sessionId, - data: msgResult, - }); - }, - - listenRecallMessage: (handle: (jsonData: {message_id: string}) => void) => { - ipcRenderer.on(CHANNEL_RECALL_MSG, (event: any, args: {message_id: string}) => { - handle(args) - }) - }, - startExpress: () => { - ipcRenderer.send(CHANNEL_START_HTTP_SERVER); - }, log: (data: any) => { ipcRenderer.send(CHANNEL_LOG, data); }, @@ -67,20 +22,4 @@ contextBridge.exposeInMainWorld("llonebot", { getConfig: async () => { return ipcRenderer.invoke(CHANNEL_GET_CONFIG); }, - setSelfInfo(selfInfo: SelfInfo){ - ipcRenderer.invoke(CHANNEL_SET_SELF_INFO, selfInfo) - }, - downloadFile: (arg: {uri: string, localFilePath: string}) => { - return ipcRenderer.invoke(CHANNEL_DOWNLOAD_FILE, arg); - }, - deleteFile: async (localFilePath: string[]) => { - ipcRenderer.send(CHANNEL_DELETE_FILE, localFilePath); - }, - getRunningStatus: () => { - return ipcRenderer.invoke(CHANNEL_GET_RUNNING_STATUS); - }, - getHistoryMsg: async (msgId: string):Promise => { - return await ipcRenderer.invoke(CHANNEL_GET_HISTORY_MSG, msgId) - } - // startExpress, }); \ No newline at end of file diff --git a/src/renderer.ts b/src/renderer.ts index 83651f1..1134514 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,3 +1,5 @@ +import { ipcRenderer } from 'electron'; +import { CHANNEL_LOG } from './common/channels'; /// diff --git a/src/server/httpserver.ts b/src/server/httpserver.ts deleted file mode 100644 index ea4006b..0000000 --- a/src/server/httpserver.ts +++ /dev/null @@ -1,474 +0,0 @@ -import {getConfigUtil, log} from "../common/utils"; - -const express = require("express"); -import {Request, text} from 'express'; -import {Response} from 'express'; - -const JSONbig = require('json-bigint')({storeAsString: true}); -import {sendIPCRecallQQMsg, sendIPCSendQQMsg} from "../main/ipcsend"; -import {AtType, ChatType, Group, SelfInfo} from "../common/types"; -import {friends, getGroup, getGroupMember, getStrangerByUin, groups, msgHistory, selfInfo} from "../common/data"; -import { OB11ApiName, OB11Message, OB11Return, OB11MessageData, OB11Group, OB11GroupMember, OB11PostSendMsg, OB11MessageDataType, Friend } from '../onebot11/types'; -import {OB11Constructor} from "../onebot11/constructor"; -import { NTQQApi } from "../ntqqapi/ntcall"; -import { Peer } from "../ntqqapi/ntcall"; -import { ElementType, SendMessageElement } from "../ntqqapi/types"; -import { SendMsgElementConstructor } from "../ntqqapi/constructor"; -import { uri2local } from "../onebot11/utils"; -import { v4 as uuid4 } from 'uuid'; - - -// @SiberianHusky 2021-08-15 -function checkSendMessage(sendMsgList: OB11MessageData[]) { - function checkUri(uri: string): boolean { - const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/; - return pattern.test(uri); - } - - for (let msg of sendMsgList) { - if (msg["type"] && msg["data"]) { - let type = msg["type"]; - let data = msg["data"]; - if (type === "text" && !data["text"]) { - return 400; - } else if (["image", "voice", "record"].includes(type)) { - if (!data["file"]) { - return 400; - } else { - if (checkUri(data["file"])) { - return 200; - } else { - return 400; - } - } - - } else if (type === "at" && !data["qq"]) { - return 400; - } else if (type === "reply" && !data["id"]) { - return 400; - } - } else { - return 400 - } - } - return 200; -} - -// ==end== - -function constructReturnData(data: T, status: number=0, message: string = ""): OB11Return { - return { - status: status, - retcode: status, - data: data, - message: message - } -} - -function constructErrorReturn(err: string){ - return constructReturnData(null, -1, err); -} - - -function handlePost(jsonData: any, handleSendResult: (data: OB11Return) => void) { - log("API receive post:" + JSON.stringify(jsonData)) - if (!jsonData.params) { - jsonData.params = JSON.parse(JSON.stringify(jsonData)); - delete jsonData.params.params; - } - let resData = { - status: 0, - retcode: 0, - data: {}, - message: '' - } - - if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") { - if (jsonData.action == "send_private_msg") { - jsonData.message_type = "private" - } else { - jsonData.message_type = "group" - } - // @SiberianHuskY 2021-10-20 22:00:00 - resData.status = checkSendMessage(jsonData.message); - if (resData.status == 200) { - resData.message = "发送成功"; - resData.data = jsonData.message; - sendIPCSendQQMsg(jsonData, handleSendResult); - return; - } else { - resData.message = "发送失败, 请检查消息格式"; - resData.data = jsonData.message; - } - // == end == - } else if (jsonData.action == "get_group_list") { - resData["data"] = groups.map(group => { - return { - group_id: group.uid, - group_name: group.name, - member_count: group.members?.length, - group_members: group.members?.map(member => { - return { - user_id: member.uin, - user_name: member.cardName || member.nick, - user_display_name: member.cardName || member.nick - } - }) - } - }) - } else if (jsonData.action == "get_group_info") { - let group = groups.find(group => group.uid == jsonData.params.group_id) - if (group) { - resData["data"] = { - group_id: group.uid, - group_name: group.name, - member_count: group.members?.length, - } - } - } else if (jsonData.action == "get_group_member_info") { - let member = groups.find(group => group.uid == jsonData.params.group_id)?.members?.find(member => member.uin == jsonData.params.user_id) - resData["data"] = { - user_id: member?.uin, - user_name: member?.nick, - user_display_name: member?.cardName || member?.nick, - nickname: member?.nick, - card: member?.cardName, - role: member && OB11Constructor.groupMemberRole(member.role), - } - } else if (jsonData.action == "get_group_member_list") { - let group = groups.find(group => group.uid == jsonData.params.group_id) - if (group) { - resData["data"] = group?.members?.map(member => { - return { - user_id: member.uin, - user_name: member.nick, - user_display_name: member.cardName || member.nick, - nickname: member.nick, - card: member.cardName, - role: OB11Constructor.groupMemberRole(member.role), - } - - }) || [] - } else { - resData["data"] = [] - } - } else if (jsonData.action == "get_friend_list") { - resData["data"] = friends.map(friend => { - return { - user_id: friend.uin, - user_name: friend.nickName, - } - }) - } else if (jsonData.action == "delete_msg") { - sendIPCRecallQQMsg(jsonData.message_id) - } - return resData -} - - -const expressAPP = express(); -expressAPP.use(express.urlencoded({extended: true, limit: "500mb"})); - -expressAPP.use((req, res, next) => { - let data = ''; - req.on('data', chunk => { - data += chunk.toString(); - }); - req.on('end', () => { - if (data) { - try { - // log("receive raw", data) - req.body = JSONbig.parse(data); - } catch (e) { - return next(e); - } - } - next(); - }); - }); -// expressAPP.use(express.json({ -// limit: '500mb', -// verify: (req: any, res: any, buf: any, encoding: any) => { -// req.rawBody = buf; -// } -// })); - -export function startExpress(port: number) { - - - // function parseToOnebot12(action: OB11ApiName) { - // expressAPP.post('/' + action, (req: Request, res: Response) => { - // let jsonData: PostDataSendMsg = req.body; - // jsonData.action = action - // let resData = handlePost(jsonData, (data: OB11Return) => { - // res.send(data) - // }) - // if (resData) { - // res.send(resData) - // } - // }); - // } - - const actionList: OB11ApiName[] = ["get_login_info", "send_private_msg", "send_group_msg", - "get_group_list", "get_friend_list", "delete_msg", "get_group_member_list", "get_group_member_info"] - - // for (const action of actionList) { - // parseToOnebot12(action as OB11ApiName) - // } - - expressAPP.get('/', (req: Request, res: Response) => { - res.send('llonebot已启动'); - }) - - - // 处理POST请求的路由 - // expressAPP.post('/', (req: Request, res: Response) => { - // let jsonData: PostDataSendMsg = req.body; - // let resData = handlePost(jsonData, (data: OB11Return) => { - // res.send(data) - // }) - // if (resData) { - // res.send(resData) - // } - // }); - // expressAPP.post('/send_msg', (req: Request, res: Response) => { - // let jsonData: PostDataSendMsg = req.body; - // if (jsonData.message_type == "private") { - // jsonData.action = "send_private_msg" - // } else if (jsonData.message_type == "group") { - // jsonData.action = "send_group_msg" - // } else { - // if (jsonData.params?.group_id) { - // jsonData.action = "send_group_msg" - // } else { - // jsonData.action = "send_private_msg" - // } - // } - // let resData = handlePost(jsonData, (data: OB11Return) => { - // res.send(data) - // }) - // if (resData) { - // res.send(resData) - // } - // }) - - expressAPP.listen(port, "0.0.0.0", () => { - console.log(`llonebot started 0.0.0.0:${port}`); - }); -} - - -export function postMsg(msg: OB11Message) { - const {reportSelfMessage} = getConfigUtil().getConfig() - if (!reportSelfMessage) { - if (msg.user_id == selfInfo.user_id) { - return - } - } - for (const host of getConfigUtil().getConfig().hosts) { - fetch(host, { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-self-id": selfInfo.user_id - }, - body: JSON.stringify(msg) - }).then((res: any) => { - log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg)); - }, (err: any) => { - log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg)); - }); - } -} - -let routers: RecordPromise>> = {}; - -function registerRouter(action: OB11ApiName, handle: (payload: PayloadType) => Promise>) { - let url = action.toString() - if (!action.startsWith("/")){ - url = "/" + action - } - async function _handle(res: Response, payload: PayloadType) { - log("receive post data", url, payload) - try{ - const result = await handle(payload) - res.send(result) - } - catch(e){ - log(e.stack); - res.send(constructErrorReturn(e.stack.toString())) - } - } - - expressAPP.post(url, (req: Request, res: Response) => { - _handle(res, req.body).then() - }); - expressAPP.get(url, (req: Request, res: Response) => { - _handle(res, req.query as any).then() - }); - routers[url] = handle -} - -registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => { - log("history msg ids", Object.keys(msgHistory)); - const msg = msgHistory[payload.message_id.toString()] - if (msg) { - const msgData = await OB11Constructor.message(msg); - return constructReturnData(msgData) - } else { - return constructErrorReturn("消息不存在") - } -}) - -registerRouter<{}, SelfInfo>("get_login_info", async (payload)=>{ - return constructReturnData(selfInfo); -}) - -registerRouter<{}, Friend[]>("get_friend_list", async (payload)=>{ - return constructReturnData(OB11Constructor.friends(friends)); -}) - -registerRouter<{}, OB11Group[]>("get_group_list", async (payload)=>{ - return constructReturnData(OB11Constructor.groups(groups)); -}) - - -registerRouter<{group_id: number}, OB11Group[]>("get_group_info", async (payload)=>{ - const group = await getGroup(payload.group_id.toString()) - if (group){ - return constructReturnData(OB11Constructor.groups(groups)); - } - else{ - return constructErrorReturn(`群${payload.group_id}不存在`) - } -}) - -registerRouter<{group_id: number}, OB11GroupMember[]>("get_group_member_list", async (payload)=>{ - - const group = await getGroup(payload.group_id.toString()); - if (group){ - return constructReturnData(OB11Constructor.groupMembers(group)); - } - else{ - return constructErrorReturn(`群${payload.group_id}不存在`) - } -}) - -registerRouter<{group_id: number, user_id: number}, OB11GroupMember>("get_group_member_info", async (payload)=>{ - const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) - if (member){ - return constructReturnData(OB11Constructor.groupMember(payload.group_id.toString(), member)) - } - else{ - return constructErrorReturn(`群成员${payload.user_id}不存在`) - } -}) - -const handleSendMsg = async (payload)=>{ - const peer: Peer = { - chatType: ChatType.friend, - peerUid: "" - } - let group: Group | undefined = undefined; - if(payload?.group_id){ - group = groups.find(g=>g.uid == payload.group_id?.toString()) - if (!group){ - return constructErrorReturn(`群${payload.group_id}不存在`) - } - peer.chatType = ChatType.group - // peer.name = group.name - peer.peerUid = group.uid - } - else if (payload?.user_id){ - const friend = friends.find(f=>f.uin == payload.user_id.toString()) - if (friend){ - // peer.name = friend.nickName - peer.peerUid = friend.uid - } - else{ - peer.chatType = ChatType.temp - const tempUser = getStrangerByUin(payload.user_id.toString()) - if (!tempUser){ - return constructErrorReturn(`找不到私聊对象${payload.user_id}`) - } - // peer.name = tempUser.nickName - peer.peerUid = tempUser.uid - } - } - if (typeof payload.message === "string"){ - payload.message = [{ - type: OB11MessageDataType.text, - data: { - text: payload.message - } - }] as OB11MessageData[] - } - else if (!Array.isArray(payload.message)){ - payload.message = [payload.message] - } - const sendElements: SendMessageElement[] = [] - for (let sendMsg of payload.message){ - switch(sendMsg.type){ - case OB11MessageDataType.text: { - const text = sendMsg.data?.text; - if (text){ - sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) - } - }break; - case OB11MessageDataType.at: { - let atQQ = sendMsg.data?.qq; - if (atQQ){ - atQQ = atQQ.toString() - if (atQQ === "all"){ - sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) - } - else{ - const atMember = group?.members.find(m=>m.uin == atQQ) - if (atMember){ - sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) - } - } - } - }break; - case OB11MessageDataType.reply: { - let replyMsgId = sendMsg.data.id; - if (replyMsgId){ - replyMsgId = replyMsgId.toString() - const replyMsg = msgHistory[replyMsgId] - if (replyMsg){ - sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin)) - } - } - }break; - case OB11MessageDataType.image: { - const file = sendMsg.data?.file - if (file){ - const picPath = await (await uri2local(uuid4(), file)).path - if (picPath){ - sendElements.push(await SendMsgElementConstructor.pic(picPath)) - } - } - }break; - case OB11MessageDataType.voice: { - const file = sendMsg.data?.file - if (file){ - const voicePath = await (await uri2local(uuid4(), file)).path - if (voicePath){ - sendElements.push(await SendMsgElementConstructor.ptt(voicePath)) - } - } - } - } - } - log("send msg:", peer, sendElements) - try{ - const returnMsg = await NTQQApi.sendMsg(peer, sendElements) - return constructReturnData({message_id: returnMsg.msgId}) - }catch(e){ - return constructErrorReturn(e.toString()) - } -} - -registerRouter("send_msg", handleSendMsg) -registerRouter("send_private_msg", handleSendMsg) -registerRouter("send_group_msg", handleSendMsg) \ No newline at end of file From 43fbcb819a71cf6f8b9cc2a68e8aab5ded0c4a96 Mon Sep 17 00:00:00 2001 From: zhangzemeng Date: Mon, 12 Feb 2024 00:03:04 +0800 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=E4=BB=8E=20http=20server=20?= =?UTF-8?q?=E4=B8=AD=E5=88=86=E7=A6=BB=20action=20=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/onebot11/actions/ActionTemplate.ts | 41 +++++ src/onebot11/actions/GetFriendList.ts | 38 +++++ src/onebot11/actions/GetGroupInfo.ts | 45 +++++ src/onebot11/actions/GetGroupList.ts | 38 +++++ src/onebot11/actions/GetGroupMemberInfo.ts | 46 ++++++ src/onebot11/actions/GetGroupMemberList.ts | 49 ++++++ src/onebot11/actions/GetLoginInfo.ts | 38 +++++ src/onebot11/actions/GetMsg.ts | 47 ++++++ src/onebot11/actions/SendGroupMsg.ts | 40 +++++ src/onebot11/actions/SendMsg.ts | 148 +++++++++++++++++ src/onebot11/actions/SendPrivateMsg.ts | 40 +++++ src/onebot11/actions/index.ts | 37 +++++ src/onebot11/actions/types.ts | 12 ++ src/onebot11/actions/utils.ts | 61 +++++++ src/onebot11/server.ts | 184 +-------------------- 15 files changed, 687 insertions(+), 177 deletions(-) create mode 100644 src/onebot11/actions/ActionTemplate.ts create mode 100644 src/onebot11/actions/GetFriendList.ts create mode 100644 src/onebot11/actions/GetGroupInfo.ts create mode 100644 src/onebot11/actions/GetGroupList.ts create mode 100644 src/onebot11/actions/GetGroupMemberInfo.ts create mode 100644 src/onebot11/actions/GetGroupMemberList.ts create mode 100644 src/onebot11/actions/GetLoginInfo.ts create mode 100644 src/onebot11/actions/GetMsg.ts create mode 100644 src/onebot11/actions/SendGroupMsg.ts create mode 100644 src/onebot11/actions/SendMsg.ts create mode 100644 src/onebot11/actions/SendPrivateMsg.ts create mode 100644 src/onebot11/actions/index.ts create mode 100644 src/onebot11/actions/types.ts create mode 100644 src/onebot11/actions/utils.ts diff --git a/src/onebot11/actions/ActionTemplate.ts b/src/onebot11/actions/ActionTemplate.ts new file mode 100644 index 0000000..fa2e4cd --- /dev/null +++ b/src/onebot11/actions/ActionTemplate.ts @@ -0,0 +1,41 @@ +// import { OB11Response } from "./utils"; +// import { BaseCheckResult } from "./types"; +// import { OB11Return } from '../types'; + +// export type ActionType = '' + +// export interface PayloadType { +// action: ActionType +// // 参数定义待完善 +// [k: string | number]: any +// } + +// export interface ReturnDataType { +// // 参数定义待完善 +// [k: string | number]: any +// } + + +// class ActionTemplate { +// static ACTION_TYPE: ActionType = '' + +// async check(jsonData: any): Promise { +// return { +// valid: true, +// } +// } + +// async handle(jsonData: any) { +// const result = await this.check(jsonData) +// if (!result.valid) { +// return OB11Response.error(result.message) +// } +// const resData = await this._handle(jsonData) +// return resData +// } + +// async _handle(payload: PayloadType): Promise> { +// } +// } + +// export default ActionTemplate \ No newline at end of file diff --git a/src/onebot11/actions/GetFriendList.ts b/src/onebot11/actions/GetFriendList.ts new file mode 100644 index 0000000..ec6418b --- /dev/null +++ b/src/onebot11/actions/GetFriendList.ts @@ -0,0 +1,38 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { OB11Return, OB11User } from '../types'; +import { OB11Constructor } from "../constructor"; +import { friends } from "../../common/data"; + +export type ActionType = 'get_friend_list' + +export interface PayloadType { + action: ActionType +} + +export type ReturnDataType = OB11User[] + +class GetFriendList { + static ACTION_TYPE: ActionType = 'get_friend_list' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + return OB11Response.ok(OB11Constructor.friends(friends)); + } +} + +export default GetFriendList \ No newline at end of file diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/actions/GetGroupInfo.ts new file mode 100644 index 0000000..06335f1 --- /dev/null +++ b/src/onebot11/actions/GetGroupInfo.ts @@ -0,0 +1,45 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { OB11Group, OB11Return } from '../types'; +import { getGroup, groups } from "../../common/data"; +import { OB11Constructor } from "../constructor"; + +export type ActionType = 'get_group_info' + +export interface PayloadType { + action: ActionType + group_id: number +} + +export type ReturnDataType = OB11Group[] + +class GetGroupInfo { + static ACTION_TYPE: ActionType = 'get_group_info' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + const group = await getGroup(payload.group_id.toString()) + if (group) { + return OB11Response.ok(OB11Constructor.groups(groups)); + } + else { + return OB11Response.error(`群${payload.group_id}不存在`) + } + } +} + +export default GetGroupInfo \ No newline at end of file diff --git a/src/onebot11/actions/GetGroupList.ts b/src/onebot11/actions/GetGroupList.ts new file mode 100644 index 0000000..fdeaed2 --- /dev/null +++ b/src/onebot11/actions/GetGroupList.ts @@ -0,0 +1,38 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { OB11Group, OB11Return } from '../types'; +import { OB11Constructor } from "../constructor"; +import { groups } from "../../common/data"; + +export type ActionType = 'get_group_list' + +export interface PayloadType { + action: ActionType +} + +export type ReturnDataType = OB11Group[] + +class GetGroupList { + static ACTION_TYPE: ActionType = 'get_group_list' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + return OB11Response.ok(OB11Constructor.groups(groups)); + } +} + +export default GetGroupList \ No newline at end of file diff --git a/src/onebot11/actions/GetGroupMemberInfo.ts b/src/onebot11/actions/GetGroupMemberInfo.ts new file mode 100644 index 0000000..3e8d80f --- /dev/null +++ b/src/onebot11/actions/GetGroupMemberInfo.ts @@ -0,0 +1,46 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { OB11GroupMember, OB11Return } from '../types'; +import { getGroupMember } from "../../common/data"; +import { OB11Constructor } from "../constructor"; + +export type ActionType = 'get_group_member_info' + +export interface PayloadType { + action: ActionType + group_id: number + user_id: number +} + +export type ReturnDataType = OB11GroupMember + +class GetGroupMemberInfo { + static ACTION_TYPE: ActionType = 'get_group_member_info' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) + if (member) { + return OB11Response.ok(OB11Constructor.groupMember(payload.group_id.toString(), member)) + } + else { + return OB11Response.error(`群成员${payload.user_id}不存在`) + } + } +} + +export default GetGroupMemberInfo \ No newline at end of file diff --git a/src/onebot11/actions/GetGroupMemberList.ts b/src/onebot11/actions/GetGroupMemberList.ts new file mode 100644 index 0000000..4ae7b42 --- /dev/null +++ b/src/onebot11/actions/GetGroupMemberList.ts @@ -0,0 +1,49 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { OB11GroupMember, OB11Return } from '../types'; +import { getGroup } from "../../common/data"; +import { NTQQApi } from "../../ntqqapi/ntcall"; +import { OB11Constructor } from "../constructor"; + +export type ActionType = 'get_group_member_list' + +export interface PayloadType { + action: ActionType + group_id: number +} + +export type ReturnDataType = OB11GroupMember[] + +class GetGroupMemberList { + static ACTION_TYPE: ActionType = 'get_group_member_list' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + const group = await getGroup(payload.group_id.toString()); + if (group) { + if (!group.members?.length) { + group.members = await NTQQApi.getGroupMembers(payload.group_id.toString()) + } + return OB11Response.ok(OB11Constructor.groupMembers(group)); + } + else { + return OB11Response.error(`群${payload.group_id}不存在`) + } + } +} + +export default GetGroupMemberList \ No newline at end of file diff --git a/src/onebot11/actions/GetLoginInfo.ts b/src/onebot11/actions/GetLoginInfo.ts new file mode 100644 index 0000000..78eb21a --- /dev/null +++ b/src/onebot11/actions/GetLoginInfo.ts @@ -0,0 +1,38 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { OB11Return, OB11User } from '../types'; +import { OB11Constructor } from "../constructor"; +import { selfInfo } from "../../common/data"; + +export type ActionType = 'get_login_info' + +export interface PayloadType { + action: ActionType +} + +export type ReturnDataType = OB11User + +class GetLoginInfo { + static ACTION_TYPE: ActionType = 'get_login_info' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + return OB11Response.ok(OB11Constructor.selfInfo(selfInfo)); + } +} + +export default GetLoginInfo \ No newline at end of file diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts new file mode 100644 index 0000000..f700825 --- /dev/null +++ b/src/onebot11/actions/GetMsg.ts @@ -0,0 +1,47 @@ +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import { msgHistory } from "../../common/data"; +import { OB11Message, OB11Return } from '../types'; +import { OB11Constructor } from "../constructor"; +import { log } from "../../common/utils"; + +export type ActionType = 'get_msg' + +export interface PayloadType { + action: ActionType + message_id: string +} + +export type ReturnDataType = OB11Message + +class GetMsg { + static ACTION_TYPE: ActionType = 'get_msg' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + log("history msg ids", Object.keys(msgHistory)); + const msg = msgHistory[payload.message_id.toString()] + if (msg) { + const msgData = await OB11Constructor.message(msg); + return OB11Response.ok(msgData) + } else { + return OB11Response.error("消息不存在") + } + } +} + +export default GetMsg \ No newline at end of file diff --git a/src/onebot11/actions/SendGroupMsg.ts b/src/onebot11/actions/SendGroupMsg.ts new file mode 100644 index 0000000..147390d --- /dev/null +++ b/src/onebot11/actions/SendGroupMsg.ts @@ -0,0 +1,40 @@ +import { OB11PostSendMsg, OB11Return } from '../types'; +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import SendMsg from "./SendMsg"; + +export type ActionType = 'send_group_msg' + +export interface PayloadType extends OB11PostSendMsg { + action: ActionType +} + +export interface ReturnDataType { + message_id: string +} + +class SendGroupMsg { + static ACTION_TYPE: ActionType = 'send_group_msg' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + // 偷懒借用现有逻辑 + return new SendMsg()._handle(payload as any) + } +} + +export default SendGroupMsg \ No newline at end of file diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts new file mode 100644 index 0000000..766d306 --- /dev/null +++ b/src/onebot11/actions/SendMsg.ts @@ -0,0 +1,148 @@ +import { AtType, ChatType, Group } from "../../ntqqapi/types"; +import { friends, getGroup, getStrangerByUin, msgHistory } from "../../common/data"; +import { OB11Return, OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types'; +import { NTQQApi } from "../../ntqqapi/ntcall"; +import { Peer } from "../../ntqqapi/ntcall"; +import { SendMessageElement } from "../../ntqqapi/types"; +import { SendMsgElementConstructor } from "../../ntqqapi/constructor"; +import { uri2local } from "../utils"; +import { OB11Response } from "./utils"; +import { v4 as uuid4 } from 'uuid'; +import { log } from "../../common/utils"; +import { BaseCheckResult } from "./types"; + +export type ActionType = 'send_msg' + +export interface PayloadType extends OB11PostSendMsg { + action: ActionType +} + +export interface ReturnDataType { + message_id: string +} + +class SendMsg { + static ACTION_TYPE: ActionType = 'send_msg' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + const peer: Peer = { + chatType: ChatType.friend, + peerUid: "" + } + let group: Group | undefined = undefined; + if (payload?.group_id) { + group = await getGroup(payload.group_id.toString()) + if (!group) { + return OB11Response.error(`群${payload.group_id}不存在`) + } + peer.chatType = ChatType.group + // peer.name = group.name + peer.peerUid = group.groupCode + } + else if (payload?.user_id) { + const friend = friends.find(f => f.uin == payload.user_id.toString()) + if (friend) { + // peer.name = friend.nickName + peer.peerUid = friend.uid + } + else { + peer.chatType = ChatType.temp + const tempUser = getStrangerByUin(payload.user_id.toString()) + if (!tempUser) { + return OB11Response.error(`找不到私聊对象${payload.user_id}`) + } + // peer.name = tempUser.nickName + peer.peerUid = tempUser.uid + } + } + if (typeof payload.message === "string") { + payload.message = [{ + type: OB11MessageDataType.text, + data: { + text: payload.message + } + }] as OB11MessageData[] + } + else if (!Array.isArray(payload.message)) { + payload.message = [payload.message] + } + const sendElements: SendMessageElement[] = [] + for (let sendMsg of payload.message) { + switch (sendMsg.type) { + case OB11MessageDataType.text: { + const text = sendMsg.data?.text; + if (text) { + sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) + } + } break; + case OB11MessageDataType.at: { + let atQQ = sendMsg.data?.qq; + if (atQQ) { + atQQ = atQQ.toString() + if (atQQ === "all") { + sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) + } + else { + const atMember = group?.members.find(m => m.uin == atQQ) + if (atMember) { + sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) + } + } + } + } break; + case OB11MessageDataType.reply: { + let replyMsgId = sendMsg.data.id; + if (replyMsgId) { + replyMsgId = replyMsgId.toString() + const replyMsg = msgHistory[replyMsgId] + if (replyMsg) { + sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin)) + } + } + } break; + case OB11MessageDataType.image: { + const file = sendMsg.data?.file + if (file) { + const picPath = await (await uri2local(uuid4(), file)).path + if (picPath) { + sendElements.push(await SendMsgElementConstructor.pic(picPath)) + } + } + } break; + case OB11MessageDataType.voice: { + const file = sendMsg.data?.file + if (file) { + const voicePath = await (await uri2local(uuid4(), file)).path + if (voicePath) { + sendElements.push(await SendMsgElementConstructor.ptt(voicePath)) + } + } + } + } + } + log("send msg:", peer, sendElements) + try { + const returnMsg = await NTQQApi.sendMsg(peer, sendElements) + return OB11Response.ok({ message_id: returnMsg.msgId }) + } catch (e) { + return OB11Response.error(e.toString()) + } + } +} + +export default SendMsg \ No newline at end of file diff --git a/src/onebot11/actions/SendPrivateMsg.ts b/src/onebot11/actions/SendPrivateMsg.ts new file mode 100644 index 0000000..f12bfd5 --- /dev/null +++ b/src/onebot11/actions/SendPrivateMsg.ts @@ -0,0 +1,40 @@ +import { OB11PostSendMsg, OB11Return } from '../types'; +import { OB11Response } from "./utils"; +import { BaseCheckResult } from "./types"; +import SendMsg from "./SendMsg"; + +export type ActionType = 'send_private_msg' + +export interface PayloadType extends OB11PostSendMsg { + action: ActionType +} + +export interface ReturnDataType { + message_id: string +} + +class SendPrivateMsg { + static ACTION_TYPE: ActionType = 'send_private_msg' + + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: PayloadType): Promise> { + // 偷懒借用现有逻辑 + return new SendMsg()._handle(payload as any) + } +} + +export default SendPrivateMsg \ No newline at end of file diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts new file mode 100644 index 0000000..590a545 --- /dev/null +++ b/src/onebot11/actions/index.ts @@ -0,0 +1,37 @@ +import { OB11Return } from '../types'; +import { OB11Response } from './utils' + +import GetMsg from './GetMsg' +import GetLoginInfo from './GetLoginInfo' +import GetFriendList from './GetFriendList' +import GetGroupList from './GetGroupList' +import GetGroupInfo from './GetGroupInfo' +import GetGroupMemberList from './GetGroupMemberList' +import GetGroupMemberInfo from './GetGroupMemberInfo' +import SendGroupMsg from './SendGroupMsg' +import SendPrivateMsg from './SendPrivateMsg' +import SendMsg from './SendMsg' + +export const actionHandles = { + [GetMsg.ACTION_TYPE]: new GetMsg(), + [GetLoginInfo.ACTION_TYPE]: new GetLoginInfo(), + [GetFriendList.ACTION_TYPE]: new GetFriendList(), + [GetGroupList.ACTION_TYPE]: new GetGroupList(), + [GetGroupInfo.ACTION_TYPE]: new GetGroupInfo(), + [GetGroupMemberList.ACTION_TYPE]: new GetGroupMemberList(), + [GetGroupMemberInfo.ACTION_TYPE]: new GetGroupMemberInfo(), + [SendGroupMsg.ACTION_TYPE]: new SendGroupMsg(), + [SendPrivateMsg.ACTION_TYPE]: new SendPrivateMsg(), + [SendMsg.ACTION_TYPE]: new SendMsg(), +} + +export async function handleAction( + jsonData: any, +): Promise> { + const handler = actionHandles[jsonData.action] + if (handler) { + return await handler.handle(jsonData) + } else { + return OB11Response.error(`未知的 action: ${jsonData.action}`) + } +} diff --git a/src/onebot11/actions/types.ts b/src/onebot11/actions/types.ts new file mode 100644 index 0000000..34b63e3 --- /dev/null +++ b/src/onebot11/actions/types.ts @@ -0,0 +1,12 @@ +export type BaseCheckResult = ValidCheckResult | InvalidCheckResult + +export interface ValidCheckResult { + valid: true + [k: string | number]: any +} + +export interface InvalidCheckResult { + valid: false + message: string + [k: string | number]: any +} diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/actions/utils.ts new file mode 100644 index 0000000..20e7e7f --- /dev/null +++ b/src/onebot11/actions/utils.ts @@ -0,0 +1,61 @@ +import { OB11Return } from '../types'; + +// export function createSuccessActionResponse({ +// status = 0, +// retcode = 0, +// data = {} as T, +// ...others +// }: Partial> = {} = {}): OB11Return { +// return { +// status, +// retcode, +// data, +// message: '', +// ...others +// } +// } + +// export function createFailedActionResponse({ +// status = -1, +// retcode = -1, +// message = '', +// ...others +// }: Partial> = {}): OB11Return { +// return { +// status, +// retcode, +// data: {}, +// message, +// ...others, +// } +// } + +// export function createActionParamsErrorResponse(type, others = {}) { +// return createFailedActionResponse({ +// message: `${type} 接收到了不正确的参数`, +// ...others, +// }) +// } + +// export function createErrorResponseWithParams(params = {}) { +// return createFailedActionResponse({ +// ...params, +// }) +// } + +export class OB11Response { + static res(data: T, status: number = 0, message: string = ""): OB11Return { + return { + status: status, + retcode: status, + data: data, + message: message + } + } + static ok(data: T) { + return OB11Response.res(data) + } + static error(err: string) { + return OB11Response.res(null, -1, err) + } +} diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts index ec178e5..d63ae03 100644 --- a/src/onebot11/server.ts +++ b/src/onebot11/server.ts @@ -5,16 +5,9 @@ import { Request } from 'express'; import { Response } from 'express'; const JSONbig = require('json-bigint')({ storeAsString: true }); -import { AtType, ChatType, Group, SelfInfo } from "../ntqqapi/types"; -import { friends, getGroup, getGroupMember, getStrangerByUin, groups, msgHistory, selfInfo } from "../common/data"; -import { OB11ApiName, OB11Message, OB11Return, OB11MessageData, OB11Group, OB11GroupMember, OB11PostSendMsg, OB11MessageDataType, OB11User } from './types'; -import { OB11Constructor } from "./constructor"; -import { NTQQApi } from "../ntqqapi/ntcall"; -import { Peer } from "../ntqqapi/ntcall"; -import { SendMessageElement } from "../ntqqapi/types"; -import { SendMsgElementConstructor } from "../ntqqapi/constructor"; -import { uri2local } from "./utils"; -import { v4 as uuid4 } from 'uuid'; +import { selfInfo } from "../common/data"; +import { OB11Message, OB11Return, OB11MessageData } from './types'; +import { actionHandles } from "./actions"; // @SiberianHusky 2021-08-15 @@ -137,12 +130,12 @@ export function postMsg(msg: OB11Message) { let routers: Record Promise>> = {}; -function registerRouter(action: OB11ApiName, handle: (payload: PayloadType) => Promise>) { +function registerRouter(action: string, handle: (payload: any) => Promise) { let url = action.toString() if (!action.startsWith("/")) { url = "/" + action } - async function _handle(res: Response, payload: PayloadType) { + async function _handle(res: Response, payload: any) { log("receive post data", url, payload) try { const result = await handle(payload) @@ -163,169 +156,6 @@ function registerRouter(action: OB11ApiName, handle routers[url] = handle } -registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => { - log("history msg ids", Object.keys(msgHistory)); - const msg = msgHistory[payload.message_id.toString()] - if (msg) { - const msgData = await OB11Constructor.message(msg); - return OB11Response.ok(msgData) - } else { - return OB11Response.error("消息不存在") - } -}) - -registerRouter<{}, OB11User>("get_login_info", async (payload) => { - return OB11Response.ok(OB11Constructor.selfInfo(selfInfo)); -}) - -registerRouter<{}, OB11User[]>("get_friend_list", async (payload) => { - return OB11Response.ok(OB11Constructor.friends(friends)); -}) - -registerRouter<{}, OB11Group[]>("get_group_list", async (payload) => { - return OB11Response.ok(OB11Constructor.groups(groups)); -}) - - -registerRouter<{ group_id: number }, OB11Group[]>("get_group_info", async (payload) => { - const group = await getGroup(payload.group_id.toString()) - if (group) { - return OB11Response.ok(OB11Constructor.groups(groups)); - } - else { - return OB11Response.error(`群${payload.group_id}不存在`) - } -}) - -registerRouter<{ group_id: number }, OB11GroupMember[]>("get_group_member_list", async (payload) => { - - const group = await getGroup(payload.group_id.toString()); - if (group) { - if (!group.members?.length){ - group.members = await NTQQApi.getGroupMembers(payload.group_id.toString()) - } - return OB11Response.ok(OB11Constructor.groupMembers(group)); - } - else { - return OB11Response.error(`群${payload.group_id}不存在`) - } -}) - -registerRouter<{ group_id: number, user_id: number }, OB11GroupMember>("get_group_member_info", async (payload) => { - const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) - if (member) { - return OB11Response.ok(OB11Constructor.groupMember(payload.group_id.toString(), member)) - } - else { - return OB11Response.error(`群成员${payload.user_id}不存在`) - } -}) - -const handleSendMsg = async (payload) => { - const peer: Peer = { - chatType: ChatType.friend, - peerUid: "" - } - let group: Group | undefined = undefined; - if (payload?.group_id) { - group = await getGroup(payload.group_id.toString()) - if (!group) { - return OB11Response.error(`群${payload.group_id}不存在`) - } - peer.chatType = ChatType.group - // peer.name = group.name - peer.peerUid = group.groupCode - } - else if (payload?.user_id) { - const friend = friends.find(f => f.uin == payload.user_id.toString()) - if (friend) { - // peer.name = friend.nickName - peer.peerUid = friend.uid - } - else { - peer.chatType = ChatType.temp - const tempUser = getStrangerByUin(payload.user_id.toString()) - if (!tempUser) { - return OB11Response.error(`找不到私聊对象${payload.user_id}`) - } - // peer.name = tempUser.nickName - peer.peerUid = tempUser.uid - } - } - if (typeof payload.message === "string") { - payload.message = [{ - type: OB11MessageDataType.text, - data: { - text: payload.message - } - }] as OB11MessageData[] - } - else if (!Array.isArray(payload.message)) { - payload.message = [payload.message] - } - const sendElements: SendMessageElement[] = [] - for (let sendMsg of payload.message) { - switch (sendMsg.type) { - case OB11MessageDataType.text: { - const text = sendMsg.data?.text; - if (text) { - sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) - } - } break; - case OB11MessageDataType.at: { - let atQQ = sendMsg.data?.qq; - if (atQQ) { - atQQ = atQQ.toString() - if (atQQ === "all") { - sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) - } - else { - const atMember = group?.members.find(m => m.uin == atQQ) - if (atMember) { - sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) - } - } - } - } break; - case OB11MessageDataType.reply: { - let replyMsgId = sendMsg.data.id; - if (replyMsgId) { - replyMsgId = replyMsgId.toString() - const replyMsg = msgHistory[replyMsgId] - if (replyMsg) { - sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin)) - } - } - } break; - case OB11MessageDataType.image: { - const file = sendMsg.data?.file - if (file) { - const picPath = await (await uri2local(uuid4(), file)).path - if (picPath) { - sendElements.push(await SendMsgElementConstructor.pic(picPath)) - } - } - } break; - case OB11MessageDataType.voice: { - const file = sendMsg.data?.file - if (file) { - const voicePath = await (await uri2local(uuid4(), file)).path - if (voicePath) { - sendElements.push(await SendMsgElementConstructor.ptt(voicePath)) - } - } - } - } - } - log("send msg:", peer, sendElements) - try { - const returnMsg = await NTQQApi.sendMsg(peer, sendElements) - return OB11Response.ok({ message_id: returnMsg.msgId }) - } catch (e) { - return OB11Response.error(e.toString()) - } +for (const [action, handler] of Object.entries(actionHandles)) { + registerRouter(action, (payload) => handler.handle(payload)) } - -registerRouter("send_msg", handleSendMsg) -registerRouter("send_private_msg", handleSendMsg) -registerRouter("send_group_msg", handleSendMsg) \ No newline at end of file From c510f4acdc29f76b348a3dc446572cd38c622eea Mon Sep 17 00:00:00 2001 From: zhangzemeng Date: Mon, 12 Feb 2024 09:13:06 +0800 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=E6=8A=BD=E7=A6=BB=20action=20?= =?UTF-8?q?=E5=85=AC=E5=85=B1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/onebot11/actions/ActionTemplate.ts | 21 +++--------------- src/onebot11/actions/BaseAction.ts | 25 ++++++++++++++++++++++ src/onebot11/actions/GetFriendList.ts | 19 ++-------------- src/onebot11/actions/GetGroupInfo.ts | 19 ++-------------- src/onebot11/actions/GetGroupList.ts | 19 ++-------------- src/onebot11/actions/GetGroupMemberInfo.ts | 19 ++-------------- src/onebot11/actions/GetGroupMemberList.ts | 19 ++-------------- src/onebot11/actions/GetLoginInfo.ts | 19 ++-------------- src/onebot11/actions/GetMsg.ts | 19 ++-------------- src/onebot11/actions/SendGroupMsg.ts | 20 ++--------------- src/onebot11/actions/SendMsg.ts | 19 ++-------------- src/onebot11/actions/SendPrivateMsg.ts | 20 ++--------------- 12 files changed, 48 insertions(+), 190 deletions(-) create mode 100644 src/onebot11/actions/BaseAction.ts diff --git a/src/onebot11/actions/ActionTemplate.ts b/src/onebot11/actions/ActionTemplate.ts index fa2e4cd..8ecaa68 100644 --- a/src/onebot11/actions/ActionTemplate.ts +++ b/src/onebot11/actions/ActionTemplate.ts @@ -1,6 +1,5 @@ -// import { OB11Response } from "./utils"; -// import { BaseCheckResult } from "./types"; // import { OB11Return } from '../types'; +// import BaseAction from "./BaseAction"; // export type ActionType = '' @@ -16,25 +15,11 @@ // } -// class ActionTemplate { +// class ActionTemplate extends BaseAction { // static ACTION_TYPE: ActionType = '' -// async check(jsonData: any): Promise { -// return { -// valid: true, -// } -// } - -// async handle(jsonData: any) { -// const result = await this.check(jsonData) -// if (!result.valid) { -// return OB11Response.error(result.message) -// } -// const resData = await this._handle(jsonData) -// return resData -// } - // async _handle(payload: PayloadType): Promise> { +// return // } // } diff --git a/src/onebot11/actions/BaseAction.ts b/src/onebot11/actions/BaseAction.ts new file mode 100644 index 0000000..1d2c5e7 --- /dev/null +++ b/src/onebot11/actions/BaseAction.ts @@ -0,0 +1,25 @@ +import { BaseCheckResult } from "./types" +import { OB11Response } from "./utils" + +class BaseAction { + async check(jsonData: any): Promise { + return { + valid: true, + } + } + + async handle(jsonData: any) { + const result = await this.check(jsonData) + if (!result.valid) { + return OB11Response.error(result.message) + } + const resData = await this._handle(jsonData) + return resData + } + + async _handle(payload: unknown): Promise { + return + } +} + +export default BaseAction \ No newline at end of file diff --git a/src/onebot11/actions/GetFriendList.ts b/src/onebot11/actions/GetFriendList.ts index ec6418b..49b44da 100644 --- a/src/onebot11/actions/GetFriendList.ts +++ b/src/onebot11/actions/GetFriendList.ts @@ -1,8 +1,8 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { OB11Return, OB11User } from '../types'; import { OB11Constructor } from "../constructor"; import { friends } from "../../common/data"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_friend_list' @@ -12,24 +12,9 @@ export interface PayloadType { export type ReturnDataType = OB11User[] -class GetFriendList { +class GetFriendList extends BaseAction { static ACTION_TYPE: ActionType = 'get_friend_list' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { return OB11Response.ok(OB11Constructor.friends(friends)); } diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/actions/GetGroupInfo.ts index 06335f1..9ac11be 100644 --- a/src/onebot11/actions/GetGroupInfo.ts +++ b/src/onebot11/actions/GetGroupInfo.ts @@ -1,8 +1,8 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { OB11Group, OB11Return } from '../types'; import { getGroup, groups } from "../../common/data"; import { OB11Constructor } from "../constructor"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_group_info' @@ -13,24 +13,9 @@ export interface PayloadType { export type ReturnDataType = OB11Group[] -class GetGroupInfo { +class GetGroupInfo extends BaseAction { static ACTION_TYPE: ActionType = 'get_group_info' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { const group = await getGroup(payload.group_id.toString()) if (group) { diff --git a/src/onebot11/actions/GetGroupList.ts b/src/onebot11/actions/GetGroupList.ts index fdeaed2..16eba73 100644 --- a/src/onebot11/actions/GetGroupList.ts +++ b/src/onebot11/actions/GetGroupList.ts @@ -1,8 +1,8 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { OB11Group, OB11Return } from '../types'; import { OB11Constructor } from "../constructor"; import { groups } from "../../common/data"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_group_list' @@ -12,24 +12,9 @@ export interface PayloadType { export type ReturnDataType = OB11Group[] -class GetGroupList { +class GetGroupList extends BaseAction { static ACTION_TYPE: ActionType = 'get_group_list' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { return OB11Response.ok(OB11Constructor.groups(groups)); } diff --git a/src/onebot11/actions/GetGroupMemberInfo.ts b/src/onebot11/actions/GetGroupMemberInfo.ts index 3e8d80f..696ecb9 100644 --- a/src/onebot11/actions/GetGroupMemberInfo.ts +++ b/src/onebot11/actions/GetGroupMemberInfo.ts @@ -1,8 +1,8 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { OB11GroupMember, OB11Return } from '../types'; import { getGroupMember } from "../../common/data"; import { OB11Constructor } from "../constructor"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_group_member_info' @@ -14,24 +14,9 @@ export interface PayloadType { export type ReturnDataType = OB11GroupMember -class GetGroupMemberInfo { +class GetGroupMemberInfo extends BaseAction { static ACTION_TYPE: ActionType = 'get_group_member_info' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) if (member) { diff --git a/src/onebot11/actions/GetGroupMemberList.ts b/src/onebot11/actions/GetGroupMemberList.ts index 4ae7b42..04abedc 100644 --- a/src/onebot11/actions/GetGroupMemberList.ts +++ b/src/onebot11/actions/GetGroupMemberList.ts @@ -1,9 +1,9 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { OB11GroupMember, OB11Return } from '../types'; import { getGroup } from "../../common/data"; import { NTQQApi } from "../../ntqqapi/ntcall"; import { OB11Constructor } from "../constructor"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_group_member_list' @@ -14,24 +14,9 @@ export interface PayloadType { export type ReturnDataType = OB11GroupMember[] -class GetGroupMemberList { +class GetGroupMemberList extends BaseAction { static ACTION_TYPE: ActionType = 'get_group_member_list' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { const group = await getGroup(payload.group_id.toString()); if (group) { diff --git a/src/onebot11/actions/GetLoginInfo.ts b/src/onebot11/actions/GetLoginInfo.ts index 78eb21a..6303bb6 100644 --- a/src/onebot11/actions/GetLoginInfo.ts +++ b/src/onebot11/actions/GetLoginInfo.ts @@ -1,8 +1,8 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { OB11Return, OB11User } from '../types'; import { OB11Constructor } from "../constructor"; import { selfInfo } from "../../common/data"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_login_info' @@ -12,24 +12,9 @@ export interface PayloadType { export type ReturnDataType = OB11User -class GetLoginInfo { +class GetLoginInfo extends BaseAction { static ACTION_TYPE: ActionType = 'get_login_info' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { return OB11Response.ok(OB11Constructor.selfInfo(selfInfo)); } diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts index f700825..ab78177 100644 --- a/src/onebot11/actions/GetMsg.ts +++ b/src/onebot11/actions/GetMsg.ts @@ -1,9 +1,9 @@ import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import { msgHistory } from "../../common/data"; import { OB11Message, OB11Return } from '../types'; import { OB11Constructor } from "../constructor"; import { log } from "../../common/utils"; +import BaseAction from "./BaseAction"; export type ActionType = 'get_msg' @@ -14,24 +14,9 @@ export interface PayloadType { export type ReturnDataType = OB11Message -class GetMsg { +class GetMsg extends BaseAction { static ACTION_TYPE: ActionType = 'get_msg' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { log("history msg ids", Object.keys(msgHistory)); const msg = msgHistory[payload.message_id.toString()] diff --git a/src/onebot11/actions/SendGroupMsg.ts b/src/onebot11/actions/SendGroupMsg.ts index 147390d..3b8919b 100644 --- a/src/onebot11/actions/SendGroupMsg.ts +++ b/src/onebot11/actions/SendGroupMsg.ts @@ -1,7 +1,6 @@ import { OB11PostSendMsg, OB11Return } from '../types'; -import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import SendMsg from "./SendMsg"; +import BaseAction from './BaseAction'; export type ActionType = 'send_group_msg' @@ -13,24 +12,9 @@ export interface ReturnDataType { message_id: string } -class SendGroupMsg { +class SendGroupMsg extends BaseAction { static ACTION_TYPE: ActionType = 'send_group_msg' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { // 偷懒借用现有逻辑 return new SendMsg()._handle(payload as any) diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts index 766d306..981a581 100644 --- a/src/onebot11/actions/SendMsg.ts +++ b/src/onebot11/actions/SendMsg.ts @@ -9,7 +9,7 @@ import { uri2local } from "../utils"; import { OB11Response } from "./utils"; import { v4 as uuid4 } from 'uuid'; import { log } from "../../common/utils"; -import { BaseCheckResult } from "./types"; +import BaseAction from "./BaseAction"; export type ActionType = 'send_msg' @@ -21,24 +21,9 @@ export interface ReturnDataType { message_id: string } -class SendMsg { +class SendMsg extends BaseAction { static ACTION_TYPE: ActionType = 'send_msg' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { const peer: Peer = { chatType: ChatType.friend, diff --git a/src/onebot11/actions/SendPrivateMsg.ts b/src/onebot11/actions/SendPrivateMsg.ts index f12bfd5..93c5af6 100644 --- a/src/onebot11/actions/SendPrivateMsg.ts +++ b/src/onebot11/actions/SendPrivateMsg.ts @@ -1,7 +1,6 @@ import { OB11PostSendMsg, OB11Return } from '../types'; -import { OB11Response } from "./utils"; -import { BaseCheckResult } from "./types"; import SendMsg from "./SendMsg"; +import BaseAction from './BaseAction'; export type ActionType = 'send_private_msg' @@ -13,24 +12,9 @@ export interface ReturnDataType { message_id: string } -class SendPrivateMsg { +class SendPrivateMsg extends BaseAction { static ACTION_TYPE: ActionType = 'send_private_msg' - async check(jsonData: any): Promise { - return { - valid: true, - } - } - - async handle(jsonData: any) { - const result = await this.check(jsonData) - if (!result.valid) { - return OB11Response.error(result.message) - } - const resData = await this._handle(jsonData) - return resData - } - async _handle(payload: PayloadType): Promise> { // 偷懒借用现有逻辑 return new SendMsg()._handle(payload as any) From 2d5d1c69c1207ae0dd779907a094985e7907818d Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 12 Feb 2024 14:46:31 +0800 Subject: [PATCH 09/14] refactor: remove unused code --- src/global.d.ts | 50 +------------------------------------------------ src/renderer.ts | 2 -- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index 0cdba03..d410524 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,62 +1,14 @@ -import { - Config, - Peer, - PostDataSendMsg, - SelfInfo} from "./common/types"; -import { Group } from "./ntqqapi/types"; -import { GroupMember } from "./ntqqapi/types"; -import { MessageElement, PttElement, RawMessage } from "./ntqqapi/types"; -import { User } from "./ntqqapi/types"; -import { SendIPCMsgSession } from "./main/ipcsend"; - - -import {OB11Return, OB11MessageData, OB11SendMsgReturn} from "./onebot11/types"; - - -declare var LLAPI: { - on(event: "new-messages" | "new-send-messages", callback: (data: MessageElement[]) => void): void; - on(event: "context-msg-menu", callback: (event: any, target: any, msgIds:any) => void): void; - getAccountInfo(): Promise<{ - uid: string // 一串加密的字符串 - uin: string // qq - }> - - getUserInfo(uid: string): Promise; // uid是一串加密的字符串 - sendMessage(peer: Peer, message: OB11MessageData[]): Promise; - recallMessage(peer: Peer, msgIds: string[]): Promise; - getGroupsList(forced: boolean): Promise - getFriendsList(forced: boolean): Promise - getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Map }}> - getPeer(): Promise - add_qmenu(func: (qContextMenu: Node)=>void): void - Ptt2Text(msgId:string, peer: Peer, elements: MessageElement[]): Promise - -}; +import { Config } from "./common/types"; declare var llonebot: { - postData: (data: any) => void - listenSendMessage: (handle: (msg: SendIPCMsgSession) => void) => void - listenRecallMessage: (handle: (msg: {message_id: string}) => void) => void - updateGroups: (groups: Group[]) => void - updateFriends: (friends: User[]) => void - updateGroupMembers: (data: { groupMembers: User[], group_id: string }) => void - startExpress: () => void log(data: any): void, setConfig(config: Config):void; getConfig():Promise; - setSelfInfo(selfInfo: SelfInfo):void; - downloadFile(arg: {uri: string, fileName: string}):Promise<{errMsg: string, path: string}>; - deleteFile(path: string[]):Promise; - getRunningStatus(): Promise; - sendSendMsgResult(sessionId: string, msgResult: OB11SendMsgReturn): void; - file2base64(path: string): Promise<{err: string, data: string}>; - getHistoryMsg(msgId: string): Promise; }; declare global { interface Window { - LLAPI: typeof LLAPI; llonebot: typeof llonebot; LiteLoader: any; } diff --git a/src/renderer.ts b/src/renderer.ts index 1134514..83651f1 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,5 +1,3 @@ -import { ipcRenderer } from 'electron'; -import { CHANNEL_LOG } from './common/channels'; /// From 0a8e25c121434407a93b63838cd5d42505642dff Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 12 Feb 2024 21:54:58 +0800 Subject: [PATCH 10/14] refactor: Action --- src/ntqqapi/hook.ts | 2 +- src/ntqqapi/ntcall.ts | 32 ++++++++-------- src/onebot11/actions/ActionTemplate.ts | 26 ------------- src/onebot11/actions/BaseAction.ts | 24 +++++++----- src/onebot11/actions/GetFriendList.ts | 19 +++------- src/onebot11/actions/GetGroupInfo.ts | 24 +++++------- src/onebot11/actions/GetGroupList.ts | 18 +++------ src/onebot11/actions/GetGroupMemberInfo.ts | 18 ++++----- src/onebot11/actions/GetGroupMemberList.ts | 18 ++++----- src/onebot11/actions/GetLoginInfo.ts | 19 +++------- src/onebot11/actions/GetMsg.ts | 18 ++++----- src/onebot11/actions/SendGroupMsg.ts | 21 ++--------- src/onebot11/actions/SendMsg.ts | 26 +++++-------- src/onebot11/actions/SendPrivateMsg.ts | 22 ++--------- src/onebot11/actions/index.ts | 33 ++++------------- src/onebot11/actions/types.ts | 14 +++++++ src/onebot11/actions/utils.ts | 43 ---------------------- src/onebot11/server.ts | 8 ++-- 18 files changed, 122 insertions(+), 263 deletions(-) delete mode 100644 src/onebot11/actions/ActionTemplate.ts diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index a24131c..2dc0976 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -94,7 +94,7 @@ async function updateGroups(_groups: Group[]){ for(let group of _groups){ let existGroup = groups.find(g=>g.groupCode == group.groupCode) if (!existGroup){ - log("update group") + // log("update group") let _membeers = await NTQQApi.getGroupMembers(group.groupCode) if (_membeers){ group.members = _membeers diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 4a56332..4ad57a3 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -146,23 +146,23 @@ export class NTQQApi { } - // static async getFriends(forced = false) { - // const data = await callNTQQApi<{ data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: Friend[] }[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{ force_update: forced }, undefined], ReceiveCmd.FRIENDS) - // let _friends: Friend[] = []; - // for (const fData of data.data) { - // _friends.push(...fData.buddyList) - // } - // return _friends - // } + static async getFriends(forced = false) { + const data = await callNTQQApi<{ data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: Friend[] }[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{ force_update: forced }, undefined], ReceiveCmd.FRIENDS) + let _friends: Friend[] = []; + for (const fData of data.data) { + _friends.push(...fData.buddyList) + } + return _friends + } - // static async getGroups(forced = false) { - // let cbCmd = ReceiveCmd.GROUPS - // if (process.platform != "win32") { - // cbCmd = ReceiveCmd.GROUPS_UNIX - // } - // const result = await callNTQQApi<{ updateType: number, groupList: Group[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{ force_update: forced }, undefined], cbCmd) - // return result.groupList - // } + static async getGroups(forced = false) { + let cbCmd = ReceiveCmd.GROUPS + if (process.platform != "win32") { + cbCmd = ReceiveCmd.GROUPS_UNIX + } + const result = await callNTQQApi<{ updateType: number, groupList: Group[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{ force_update: forced }, undefined], cbCmd) + return result.groupList + } static async getGroupMembers(groupQQ: string, num = 3000) { const sceneId = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{ diff --git a/src/onebot11/actions/ActionTemplate.ts b/src/onebot11/actions/ActionTemplate.ts deleted file mode 100644 index 8ecaa68..0000000 --- a/src/onebot11/actions/ActionTemplate.ts +++ /dev/null @@ -1,26 +0,0 @@ -// import { OB11Return } from '../types'; -// import BaseAction from "./BaseAction"; - -// export type ActionType = '' - -// export interface PayloadType { -// action: ActionType -// // 参数定义待完善 -// [k: string | number]: any -// } - -// export interface ReturnDataType { -// // 参数定义待完善 -// [k: string | number]: any -// } - - -// class ActionTemplate extends BaseAction { -// static ACTION_TYPE: ActionType = '' - -// async _handle(payload: PayloadType): Promise> { -// return -// } -// } - -// export default ActionTemplate \ No newline at end of file diff --git a/src/onebot11/actions/BaseAction.ts b/src/onebot11/actions/BaseAction.ts index 1d2c5e7..ab69555 100644 --- a/src/onebot11/actions/BaseAction.ts +++ b/src/onebot11/actions/BaseAction.ts @@ -1,24 +1,30 @@ -import { BaseCheckResult } from "./types" +import {ActionName, BaseCheckResult} from "./types" import { OB11Response } from "./utils" +import { OB11Return } from "../types"; -class BaseAction { - async check(jsonData: any): Promise { +class BaseAction { + actionName: ActionName + protected async check(payload: PayloadType): Promise { return { valid: true, } } - async handle(jsonData: any) { - const result = await this.check(jsonData) + public async handle(payload: PayloadType): Promise> { + const result = await this.check(payload) if (!result.valid) { return OB11Response.error(result.message) } - const resData = await this._handle(jsonData) - return resData + try { + const resData = await this._handle(payload) + return OB11Response.ok(resData) + }catch (e) { + return OB11Response.error(e.toString()) + } } - async _handle(payload: unknown): Promise { - return + protected async _handle(payload: PayloadType): Promise { + throw `pleas override ${this.actionName} _handle` } } diff --git a/src/onebot11/actions/GetFriendList.ts b/src/onebot11/actions/GetFriendList.ts index 49b44da..6827728 100644 --- a/src/onebot11/actions/GetFriendList.ts +++ b/src/onebot11/actions/GetFriendList.ts @@ -1,22 +1,15 @@ -import { OB11Response } from "./utils"; -import { OB11Return, OB11User } from '../types'; +import { OB11User } from '../types'; import { OB11Constructor } from "../constructor"; import { friends } from "../../common/data"; import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; -export type ActionType = 'get_friend_list' -export interface PayloadType { - action: ActionType -} +class GetFriendList extends BaseAction { + actionName = ActionName.GetFriendList -export type ReturnDataType = OB11User[] - -class GetFriendList extends BaseAction { - static ACTION_TYPE: ActionType = 'get_friend_list' - - async _handle(payload: PayloadType): Promise> { - return OB11Response.ok(OB11Constructor.friends(friends)); + protected async _handle(payload: null){ + return OB11Constructor.friends(friends); } } diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/actions/GetGroupInfo.ts index 9ac11be..af59af1 100644 --- a/src/onebot11/actions/GetGroupInfo.ts +++ b/src/onebot11/actions/GetGroupInfo.ts @@ -1,28 +1,22 @@ -import { OB11Response } from "./utils"; -import { OB11Group, OB11Return } from '../types'; +import { OB11Group } from '../types'; import { getGroup, groups } from "../../common/data"; import { OB11Constructor } from "../constructor"; import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; -export type ActionType = 'get_group_info' - -export interface PayloadType { - action: ActionType +interface PayloadType { group_id: number } -export type ReturnDataType = OB11Group[] +class GetGroupInfo extends BaseAction { + actionName = ActionName.GetGroupInfo -class GetGroupInfo extends BaseAction { - static ACTION_TYPE: ActionType = 'get_group_info' - - async _handle(payload: PayloadType): Promise> { + protected async _handle(payload: PayloadType) { const group = await getGroup(payload.group_id.toString()) if (group) { - return OB11Response.ok(OB11Constructor.groups(groups)); - } - else { - return OB11Response.error(`群${payload.group_id}不存在`) + return OB11Constructor.groups(groups) + } else { + throw `群${payload.group_id}不存在` } } } diff --git a/src/onebot11/actions/GetGroupList.ts b/src/onebot11/actions/GetGroupList.ts index 16eba73..d56cf12 100644 --- a/src/onebot11/actions/GetGroupList.ts +++ b/src/onebot11/actions/GetGroupList.ts @@ -1,22 +1,16 @@ -import { OB11Response } from "./utils"; -import { OB11Group, OB11Return } from '../types'; +import { OB11Group } from '../types'; import { OB11Constructor } from "../constructor"; import { groups } from "../../common/data"; import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; -export type ActionType = 'get_group_list' -export interface PayloadType { - action: ActionType -} -export type ReturnDataType = OB11Group[] +class GetGroupList extends BaseAction { + actionName = ActionName.GetGroupList -class GetGroupList extends BaseAction { - static ACTION_TYPE: ActionType = 'get_group_list' - - async _handle(payload: PayloadType): Promise> { - return OB11Response.ok(OB11Constructor.groups(groups)); + protected async _handle(payload: null){ + return OB11Constructor.groups(groups); } } diff --git a/src/onebot11/actions/GetGroupMemberInfo.ts b/src/onebot11/actions/GetGroupMemberInfo.ts index 696ecb9..7010e9a 100644 --- a/src/onebot11/actions/GetGroupMemberInfo.ts +++ b/src/onebot11/actions/GetGroupMemberInfo.ts @@ -1,29 +1,25 @@ -import { OB11Response } from "./utils"; -import { OB11GroupMember, OB11Return } from '../types'; +import { OB11GroupMember } from '../types'; import { getGroupMember } from "../../common/data"; import { OB11Constructor } from "../constructor"; import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; -export type ActionType = 'get_group_member_info' export interface PayloadType { - action: ActionType group_id: number user_id: number } -export type ReturnDataType = OB11GroupMember +class GetGroupMemberInfo extends BaseAction { + actionName = ActionName.GetGroupMemberInfo -class GetGroupMemberInfo extends BaseAction { - static ACTION_TYPE: ActionType = 'get_group_member_info' - - async _handle(payload: PayloadType): Promise> { + protected async _handle(payload: PayloadType){ const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) if (member) { - return OB11Response.ok(OB11Constructor.groupMember(payload.group_id.toString(), member)) + return OB11Constructor.groupMember(payload.group_id.toString(), member) } else { - return OB11Response.error(`群成员${payload.user_id}不存在`) + throw(`群成员${payload.user_id}不存在`) } } } diff --git a/src/onebot11/actions/GetGroupMemberList.ts b/src/onebot11/actions/GetGroupMemberList.ts index 04abedc..f9d9b55 100644 --- a/src/onebot11/actions/GetGroupMemberList.ts +++ b/src/onebot11/actions/GetGroupMemberList.ts @@ -1,32 +1,28 @@ -import { OB11Response } from "./utils"; -import { OB11GroupMember, OB11Return } from '../types'; +import { OB11GroupMember } from '../types'; import { getGroup } from "../../common/data"; import { NTQQApi } from "../../ntqqapi/ntcall"; import { OB11Constructor } from "../constructor"; import BaseAction from "./BaseAction"; - -export type ActionType = 'get_group_member_list' +import { ActionName } from "./types"; export interface PayloadType { - action: ActionType group_id: number } -export type ReturnDataType = OB11GroupMember[] -class GetGroupMemberList extends BaseAction { - static ACTION_TYPE: ActionType = 'get_group_member_list' +class GetGroupMemberList extends BaseAction { + actionName = ActionName.GetGroupMemberList - async _handle(payload: PayloadType): Promise> { + protected async _handle(payload: PayloadType){ const group = await getGroup(payload.group_id.toString()); if (group) { if (!group.members?.length) { group.members = await NTQQApi.getGroupMembers(payload.group_id.toString()) } - return OB11Response.ok(OB11Constructor.groupMembers(group)); + return OB11Constructor.groupMembers(group); } else { - return OB11Response.error(`群${payload.group_id}不存在`) + throw (`群${payload.group_id}不存在`) } } } diff --git a/src/onebot11/actions/GetLoginInfo.ts b/src/onebot11/actions/GetLoginInfo.ts index 6303bb6..56dbfe1 100644 --- a/src/onebot11/actions/GetLoginInfo.ts +++ b/src/onebot11/actions/GetLoginInfo.ts @@ -1,22 +1,15 @@ -import { OB11Response } from "./utils"; -import { OB11Return, OB11User } from '../types'; +import { OB11User } from '../types'; import { OB11Constructor } from "../constructor"; import { selfInfo } from "../../common/data"; import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; -export type ActionType = 'get_login_info' -export interface PayloadType { - action: ActionType -} +class GetLoginInfo extends BaseAction { + actionName = ActionName.GetLoginInfo -export type ReturnDataType = OB11User - -class GetLoginInfo extends BaseAction { - static ACTION_TYPE: ActionType = 'get_login_info' - - async _handle(payload: PayloadType): Promise> { - return OB11Response.ok(OB11Constructor.selfInfo(selfInfo)); + protected async _handle(payload: null){ + return OB11Constructor.selfInfo(selfInfo); } } diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts index ab78177..f1ff314 100644 --- a/src/onebot11/actions/GetMsg.ts +++ b/src/onebot11/actions/GetMsg.ts @@ -1,30 +1,28 @@ -import { OB11Response } from "./utils"; import { msgHistory } from "../../common/data"; -import { OB11Message, OB11Return } from '../types'; +import { OB11Message } from '../types'; import { OB11Constructor } from "../constructor"; import { log } from "../../common/utils"; import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; -export type ActionType = 'get_msg' export interface PayloadType { - action: ActionType message_id: string } export type ReturnDataType = OB11Message -class GetMsg extends BaseAction { - static ACTION_TYPE: ActionType = 'get_msg' +class GetMsg extends BaseAction { + actionName = ActionName.GetMsg - async _handle(payload: PayloadType): Promise> { - log("history msg ids", Object.keys(msgHistory)); + protected async _handle(payload: PayloadType){ + // log("history msg ids", Object.keys(msgHistory)); const msg = msgHistory[payload.message_id.toString()] if (msg) { const msgData = await OB11Constructor.message(msg); - return OB11Response.ok(msgData) + return msgData } else { - return OB11Response.error("消息不存在") + throw("消息不存在") } } } diff --git a/src/onebot11/actions/SendGroupMsg.ts b/src/onebot11/actions/SendGroupMsg.ts index 3b8919b..13df317 100644 --- a/src/onebot11/actions/SendGroupMsg.ts +++ b/src/onebot11/actions/SendGroupMsg.ts @@ -1,24 +1,9 @@ -import { OB11PostSendMsg, OB11Return } from '../types'; import SendMsg from "./SendMsg"; -import BaseAction from './BaseAction'; +import { ActionName } from "./types"; -export type ActionType = 'send_group_msg' -export interface PayloadType extends OB11PostSendMsg { - action: ActionType -} - -export interface ReturnDataType { - message_id: string -} - -class SendGroupMsg extends BaseAction { - static ACTION_TYPE: ActionType = 'send_group_msg' - - async _handle(payload: PayloadType): Promise> { - // 偷懒借用现有逻辑 - return new SendMsg()._handle(payload as any) - } +class SendGroupMsg extends SendMsg{ + actionName = ActionName.SendGroupMsg } export default SendGroupMsg \ No newline at end of file diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts index 981a581..48c2c1b 100644 --- a/src/onebot11/actions/SendMsg.ts +++ b/src/onebot11/actions/SendMsg.ts @@ -1,30 +1,24 @@ import { AtType, ChatType, Group } from "../../ntqqapi/types"; import { friends, getGroup, getStrangerByUin, msgHistory } from "../../common/data"; -import { OB11Return, OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types'; +import { OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types'; import { NTQQApi } from "../../ntqqapi/ntcall"; import { Peer } from "../../ntqqapi/ntcall"; import { SendMessageElement } from "../../ntqqapi/types"; import { SendMsgElementConstructor } from "../../ntqqapi/constructor"; import { uri2local } from "../utils"; -import { OB11Response } from "./utils"; import { v4 as uuid4 } from 'uuid'; import { log } from "../../common/utils"; import BaseAction from "./BaseAction"; - -export type ActionType = 'send_msg' - -export interface PayloadType extends OB11PostSendMsg { - action: ActionType -} +import { ActionName } from "./types"; export interface ReturnDataType { message_id: string } -class SendMsg extends BaseAction { - static ACTION_TYPE: ActionType = 'send_msg' +class SendMsg extends BaseAction { + actionName = ActionName.SendMsg - async _handle(payload: PayloadType): Promise> { + protected async _handle(payload: OB11PostSendMsg){ const peer: Peer = { chatType: ChatType.friend, peerUid: "" @@ -33,7 +27,7 @@ class SendMsg extends BaseAction { if (payload?.group_id) { group = await getGroup(payload.group_id.toString()) if (!group) { - return OB11Response.error(`群${payload.group_id}不存在`) + throw (`群${payload.group_id}不存在`) } peer.chatType = ChatType.group // peer.name = group.name @@ -49,7 +43,7 @@ class SendMsg extends BaseAction { peer.chatType = ChatType.temp const tempUser = getStrangerByUin(payload.user_id.toString()) if (!tempUser) { - return OB11Response.error(`找不到私聊对象${payload.user_id}`) + throw(`找不到私聊对象${payload.user_id}`) } // peer.name = tempUser.nickName peer.peerUid = tempUser.uid @@ -120,12 +114,12 @@ class SendMsg extends BaseAction { } } } - log("send msg:", peer, sendElements) + // log("send msg:", peer, sendElements) try { const returnMsg = await NTQQApi.sendMsg(peer, sendElements) - return OB11Response.ok({ message_id: returnMsg.msgId }) + return { message_id: returnMsg.msgId } } catch (e) { - return OB11Response.error(e.toString()) + throw(e.toString()) } } } diff --git a/src/onebot11/actions/SendPrivateMsg.ts b/src/onebot11/actions/SendPrivateMsg.ts index 93c5af6..26a8b22 100644 --- a/src/onebot11/actions/SendPrivateMsg.ts +++ b/src/onebot11/actions/SendPrivateMsg.ts @@ -1,24 +1,8 @@ -import { OB11PostSendMsg, OB11Return } from '../types'; import SendMsg from "./SendMsg"; -import BaseAction from './BaseAction'; +import { ActionName } from "./types"; -export type ActionType = 'send_private_msg' - -export interface PayloadType extends OB11PostSendMsg { - action: ActionType -} - -export interface ReturnDataType { - message_id: string -} - -class SendPrivateMsg extends BaseAction { - static ACTION_TYPE: ActionType = 'send_private_msg' - - async _handle(payload: PayloadType): Promise> { - // 偷懒借用现有逻辑 - return new SendMsg()._handle(payload as any) - } +class SendPrivateMsg extends SendMsg { + actionName = ActionName.SendPrivateMsg } export default SendPrivateMsg \ No newline at end of file diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts index 590a545..8d8abbc 100644 --- a/src/onebot11/actions/index.ts +++ b/src/onebot11/actions/index.ts @@ -1,6 +1,3 @@ -import { OB11Return } from '../types'; -import { OB11Response } from './utils' - import GetMsg from './GetMsg' import GetLoginInfo from './GetLoginInfo' import GetFriendList from './GetFriendList' @@ -12,26 +9,10 @@ import SendGroupMsg from './SendGroupMsg' import SendPrivateMsg from './SendPrivateMsg' import SendMsg from './SendMsg' -export const actionHandles = { - [GetMsg.ACTION_TYPE]: new GetMsg(), - [GetLoginInfo.ACTION_TYPE]: new GetLoginInfo(), - [GetFriendList.ACTION_TYPE]: new GetFriendList(), - [GetGroupList.ACTION_TYPE]: new GetGroupList(), - [GetGroupInfo.ACTION_TYPE]: new GetGroupInfo(), - [GetGroupMemberList.ACTION_TYPE]: new GetGroupMemberList(), - [GetGroupMemberInfo.ACTION_TYPE]: new GetGroupMemberInfo(), - [SendGroupMsg.ACTION_TYPE]: new SendGroupMsg(), - [SendPrivateMsg.ACTION_TYPE]: new SendPrivateMsg(), - [SendMsg.ACTION_TYPE]: new SendMsg(), -} - -export async function handleAction( - jsonData: any, -): Promise> { - const handler = actionHandles[jsonData.action] - if (handler) { - return await handler.handle(jsonData) - } else { - return OB11Response.error(`未知的 action: ${jsonData.action}`) - } -} +export const actionHandlers = [ + new GetMsg(), + new GetLoginInfo(), + new GetFriendList(), + new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(), + new SendGroupMsg(), new SendPrivateMsg(), new SendMsg() +] \ No newline at end of file diff --git a/src/onebot11/actions/types.ts b/src/onebot11/actions/types.ts index 34b63e3..fdfeeb5 100644 --- a/src/onebot11/actions/types.ts +++ b/src/onebot11/actions/types.ts @@ -10,3 +10,17 @@ export interface InvalidCheckResult { message: string [k: string | number]: any } + +export enum ActionName{ + GetLoginInfo = "get_login_info", + GetFriendList = "get_friend_list", + GetGroupInfo = "get_group_info", + GetGroupList = "get_group_list", + GetGroupMemberInfo = "get_group_member_info", + GetGroupMemberList = "get_group_member_list", + GetMsg = "get_msg", + SendMsg = "send_msg", + SendGroupMsg = "send_group_msg", + SendPrivateMsg = "send_private_msg", + DeleteMsg = "delete_msg" +} \ No newline at end of file diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/actions/utils.ts index 20e7e7f..8120d7f 100644 --- a/src/onebot11/actions/utils.ts +++ b/src/onebot11/actions/utils.ts @@ -1,48 +1,5 @@ import { OB11Return } from '../types'; -// export function createSuccessActionResponse({ -// status = 0, -// retcode = 0, -// data = {} as T, -// ...others -// }: Partial> = {} = {}): OB11Return { -// return { -// status, -// retcode, -// data, -// message: '', -// ...others -// } -// } - -// export function createFailedActionResponse({ -// status = -1, -// retcode = -1, -// message = '', -// ...others -// }: Partial> = {}): OB11Return { -// return { -// status, -// retcode, -// data: {}, -// message, -// ...others, -// } -// } - -// export function createActionParamsErrorResponse(type, others = {}) { -// return createFailedActionResponse({ -// message: `${type} 接收到了不正确的参数`, -// ...others, -// }) -// } - -// export function createErrorResponseWithParams(params = {}) { -// return createFailedActionResponse({ -// ...params, -// }) -// } - export class OB11Response { static res(data: T, status: number = 0, message: string = ""): OB11Return { return { diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts index d63ae03..95b67f6 100644 --- a/src/onebot11/server.ts +++ b/src/onebot11/server.ts @@ -7,7 +7,7 @@ import { Response } from 'express'; const JSONbig = require('json-bigint')({ storeAsString: true }); import { selfInfo } from "../common/data"; import { OB11Message, OB11Return, OB11MessageData } from './types'; -import { actionHandles } from "./actions"; +import { actionHandlers } from "./actions"; // @SiberianHusky 2021-08-15 @@ -156,6 +156,6 @@ function registerRouter(action: string, handle: (payload: any) => Promise) routers[url] = handle } -for (const [action, handler] of Object.entries(actionHandles)) { - registerRouter(action, (payload) => handler.handle(payload)) -} +for (const action of actionHandlers) { + registerRouter(action.actionName, (payload) => action.handle(payload)) +} \ No newline at end of file From edcf3f2592c65d45bfdbf857c8a5827283cd19ae Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 12 Feb 2024 22:12:25 +0800 Subject: [PATCH 11/14] feat: delete msg --- src/onebot11/actions/DeleteMsg.ts | 22 ++++++++++++++++++++++ src/onebot11/actions/index.ts | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/onebot11/actions/DeleteMsg.ts diff --git a/src/onebot11/actions/DeleteMsg.ts b/src/onebot11/actions/DeleteMsg.ts new file mode 100644 index 0000000..37b7f36 --- /dev/null +++ b/src/onebot11/actions/DeleteMsg.ts @@ -0,0 +1,22 @@ +import { ActionName } from "./types"; +import BaseAction from "./BaseAction"; +import { NTQQApi } from "../../ntqqapi/ntcall"; +import { msgHistory } from "../../common/data"; + +interface Payload { + message_id: string +} + +class DeleteMsg extends BaseAction { + actionName = ActionName.DeleteMsg + + protected async _handle(payload:Payload){ + let msg = msgHistory[payload.message_id] + await NTQQApi.recallMsg({ + chatType: msg.chatType, + peerUid: msg.peerUid + }, [payload.message_id]) + } +} + +export default DeleteMsg \ No newline at end of file diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts index 8d8abbc..42fcda6 100644 --- a/src/onebot11/actions/index.ts +++ b/src/onebot11/actions/index.ts @@ -8,11 +8,13 @@ import GetGroupMemberInfo from './GetGroupMemberInfo' import SendGroupMsg from './SendGroupMsg' import SendPrivateMsg from './SendPrivateMsg' import SendMsg from './SendMsg' +import DeleteMsg from "./DeleteMsg"; export const actionHandlers = [ new GetMsg(), new GetLoginInfo(), new GetFriendList(), new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(), - new SendGroupMsg(), new SendPrivateMsg(), new SendMsg() + new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(), + new DeleteMsg() ] \ No newline at end of file From 2727795b2001f6c015adbe0fcb3221586d27ce86 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 12 Feb 2024 22:14:38 +0800 Subject: [PATCH 12/14] docs: update readme --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f4a3575..f863498 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,9 @@ 1.安装[LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) -2.安装修改后的[LiteLoaderQQNT-Plugin-LLAPI](https://github.com/linyuchen/LiteLoaderQQNT-Plugin-LLAPI/releases),原版的功能有缺陷 +2.安装本项目插件[OneBotApi](https://github.com/linyuchen/LiteLoaderQQNT-OneBotApi/releases/), 注意本插件2.0以下的版本不支持LiteLoader 1.0.0及以上版本 -3.安装本项目插件[OneBotApi](https://github.com/linyuchen/LiteLoaderQQNT-OneBotApi/releases/), 注意本插件2.0以下的版本不支持LiteLoader 1.0.0及以上版本 - -*关于插件的安装方法: 上述的两个插件都没有上架NTQQLiteLoader插件市场,需要自己下载复制到插件目录* +*关于插件的安装方法: 下载后解压复制到插件目录* *插件目录:`LiteLoaderQQNT/plugins`* @@ -57,8 +55,6 @@ ![](doc/image/example.jpg) -*暂时不支持`"message": "hello"`这种message为字符串的形式* - ## 一些坑
@@ -102,7 +98,7 @@ - [ ] 转发消息记录 - [ ] 好友点赞api - [ ] 支持websocket,等个有缘人提PR实现 -- [ ] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用 +- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用 ## onebot11文档 From b29134f40ba595a0e4c6d550a84a57a227889520 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 12 Feb 2024 22:15:48 +0800 Subject: [PATCH 13/14] chore: update issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c299c54..04c1e7c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,14 +9,12 @@ assignees: '' QQ版本: -LLAPI版本: - LLOneBot版本: 调用LLoneBot的方式或者应用端(如postman直接调用,或NoneBot2、Koishi): -您安装LLOneBot的步骤: - BUG描述: -复现步骤: \ No newline at end of file +复现步骤: + +LLOneBot日志: \ No newline at end of file From daca59d27d170be222de7b77c66b4bf0ea5b7a5d Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 12 Feb 2024 22:44:42 +0800 Subject: [PATCH 14/14] chore: update issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 04c1e7c..0790e0b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,7 +11,7 @@ QQ版本: LLOneBot版本: -调用LLoneBot的方式或者应用端(如postman直接调用,或NoneBot2、Koishi): +调用LLOneBot的方式或者应用端(如postman直接调用,或NoneBot2、Koishi): BUG描述: