From 8d2353a5245e360a83302ab78bf28398a3970230 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Sun, 11 Feb 2024 19:57:20 +0800 Subject: [PATCH] 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