diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 3b281b3..5680381 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -1,10 +1,12 @@ -import { BrowserWindow } from 'electron'; -import { getConfigUtil, log } from "../common/utils"; -import { NTQQApi, NTQQApiClass, sendMessagePool } from "./ntcall"; -import { Group, User } from "./types"; -import { RawMessage } from "./types"; -import { addHistoryMsg, friends, groups, msgHistory } from "../common/data"; -import { v4 as uuidv4 } from 'uuid'; +import {BrowserWindow} from 'electron'; +import {log} from "../common/utils"; +import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall"; +import {Group, GroupMember, RawMessage, User} from "./types"; +import {addHistoryMsg, friends, groups, msgHistory} from "../common/data"; +import {v4 as uuidv4} from 'uuid'; +import {callEvent, EventType} from "../onebot11/event"; +import {OB11Message} from "../onebot11/types"; +import {OB11Constructor} from "../onebot11/constructor"; export let hookApiCallbacks: Record void> = {} @@ -41,7 +43,7 @@ let receiveHooks: Array<{ 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)) + // console.log(`received ntqq api message: ${channel}`, JSON.stringify(args)) if (args?.[1] instanceof Array) { for (let receiveData of args?.[1]) { const ntQQApiMethodName = receiveData.cmdName; @@ -90,25 +92,91 @@ export function removeReceiveHook(id: string) { receiveHooks.splice(index, 1); } -async function updateGroups(_groups: Group[]) { +async function updateGroups(_groups: Group[], needUpdate: boolean = true) { for (let group of _groups) { - let existGroup = groups.find(g => g.groupCode == group.groupCode) - if (!existGroup) { - NTQQApi.getGroupMembers(group.groupCode).then(members => { - if (members) { - group.members = members - } - }) - groups.push(group) - log("update group members", group.members) - } else { - Object.assign(existGroup, group) + let existGroup = groups.find(g => g.groupCode == group.groupCode); + if (existGroup) { + Object.assign(existGroup, group); + } + else { + groups.push(group); + } + + if (needUpdate) { + const members = await NTQQApi.getGroupMembers(group.groupCode); + + if (members) { + group.members = members; + } } } } -registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS, (payload) => updateGroups(payload.groupList).then()) -registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS_UNIX, (payload) => updateGroups(payload.groupList).then()) +async function processGroupEvent(payload) { + const newGroupList = payload.groupList; + for (const group of newGroupList) { + let existGroup = groups.find(g => g.groupCode == group.groupCode); + console.log(existGroup.members); + if (existGroup) { + if (existGroup.memberCount > group.memberCount) { + console.log("群人数减少力!"); + const oldMembers = existGroup.members; + const newMembers = await NTQQApi.getGroupMembers(group.groupCode); + group.members = newMembers; + const newMembersSet = new Set(); // 建立索引降低时间复杂度 + + for (const member of newMembers) { + newMembersSet.add(member.uin); + } + + console.log(oldMembers); + for (const member of oldMembers) { + if (!newMembersSet.has(member.uin)) { + console.log("减少的群员是:" + member.uin); + break; + } + } + + } + else if (existGroup.memberCount < group.memberCount) { + console.log("群人数增加力!"); + + const oldMembersSet = new Set(); + for (const member of existGroup.members) { + oldMembersSet.add(member.uin); + } + + const newMembers = await NTQQApi.getGroupMembers(group.groupCode); + group.members = newMembers; + for (const member of newMembers) { + if (!oldMembersSet.has(member.uin)) { + console.log("增加的群员是:" + member.uin); + break; + } + } + } + } + } + + updateGroups(newGroupList, false).then(); +} + +registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => { + if (payload.updateType != 2) { + updateGroups(payload.groupList).then(); + } + else { + processGroupEvent(payload).then(); + } +}) +registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => { + if (payload.updateType != 2) { + updateGroups(payload.groupList).then(); + } + else { + processGroupEvent(payload).then(); + } +}) registerReceiveHook<{ data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[] }>(ReceiveCmd.FRIENDS, payload => { @@ -125,10 +193,6 @@ registerReceiveHook<{ } }) -// registerReceiveHook(ReceiveCmd.USER_INFO, (payload)=>{ -// log("user info", payload); -// }) - registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, (payload) => { for (const message of payload.msgList) { addHistoryMsg(message) @@ -138,6 +202,12 @@ registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, (payl registerReceiveHook<{ msgList: Array }>(ReceiveCmd.NEW_MSG, (payload) => { for (const message of payload.msgList) { // log("收到新消息,push到历史记录", message) + OB11Constructor.message(message).then( + function (message) { + callEvent(EventType.MESSAGE, message); + } + ); + addHistoryMsg(message) } const msgIds = Object.keys(msgHistory); diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts index 538c2be..22de0cf 100644 --- a/src/onebot11/actions/GetMsg.ts +++ b/src/onebot11/actions/GetMsg.ts @@ -1,7 +1,6 @@ import { getHistoryMsgByShortId, msgHistory } from "../../common/data"; import { OB11Message } from '../types'; import { OB11Constructor } from "../constructor"; -import { log } from "../../common/utils"; import BaseAction from "./BaseAction"; import { ActionName } from "./types"; diff --git a/src/onebot11/event.ts b/src/onebot11/event.ts new file mode 100644 index 0000000..e334977 --- /dev/null +++ b/src/onebot11/event.ts @@ -0,0 +1,38 @@ +import {selfInfo} from "../common/data"; + +const websocketList = []; + +export enum EventType { + META = "meta_event", + REQUEST = "request", + NOTICE = "notice", + MESSAGE = "message" +} + +export function registerEventSender(ws) { + websocketList.push(ws); +} + +export function unregisterEventSender(ws) { + let index = websocketList.indexOf(ws); + if (index !== -1) { + websocketList.splice(index, 1); + } +} + +export function callEvent(type: EventType, data: DataType) { + const basicEvent = { + time: new Date().getTime(), + self_id: selfInfo.uin, + post_type: type + } + + + for (const ws of websocketList) { + ws.send( + JSON.stringify( + Object.assign(basicEvent, data) + ) + ); + } +} \ No newline at end of file diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts index 74e819a..e46201b 100644 --- a/src/onebot11/server.ts +++ b/src/onebot11/server.ts @@ -11,6 +11,7 @@ import { selfInfo } from "../common/data"; import { OB11Message, OB11Return, OB11MessageData } from './types'; import {actionHandlers, actionMap} from "./actions"; import {OB11Response, OB11WebsocketResponse} from "./actions/utils"; +import {registerEventSender, unregisterEventSender} from "./event"; // @SiberianHusky 2021-08-15 @@ -100,8 +101,8 @@ export function startWebsocketServer(port: number) { export function initWebsocket() { if (getConfigUtil().getConfig().enableWs) { - expressWsApp.ws("/api", onWebsocketMessage); - expressWsApp.ws("/", onWebsocketMessage); + expressWsApp.ws("/api", initWebsocketServer); + expressWsApp.ws("/", initWebsocketServer); } initReverseWebsocket(); @@ -114,8 +115,10 @@ function initReverseWebsocket() { try { const wsClient = new WebSocket(url); websocketClientConnections.push(wsClient); + registerEventSender(wsClient); wsClient.onclose = function (ev) { + unregisterEventSender(wsClient); let index = websocketClientConnections.indexOf(wsClient); if (index !== -1) { websocketClientConnections.splice(index, 1); @@ -149,7 +152,9 @@ function initReverseWebsocket() { } } -function onWebsocketMessage(ws, req) { +function initWebsocketServer(ws, req) { + registerEventSender(ws); + ws.on("message", async function (message) { try { let recv = JSON.parse(message); @@ -167,7 +172,11 @@ function onWebsocketMessage(ws, req) { log(e.stack); ws.send(JSON.stringify(OB11WebsocketResponse.error(e.stack.toString(), 1200))); } - }) + }); + + ws.on("close", function (ev) { + unregisterEventSender(ws); + }); } diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index f78f3ab..fc2d900 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -1,19 +1,19 @@ import { AtType } from "../ntqqapi/types"; import { RawMessage } from "../ntqqapi/types"; -export interface OB11User{ +export interface OB11User { user_id: string; nickname: string; remark?: string } -export enum OB11UserSex{ +export enum OB11UserSex { male = "male", female = "female", unknown = "unknown" } -export enum OB11GroupMemberRole{ +export enum OB11GroupMemberRole { owner = "owner", admin = "admin", member = "member", @@ -33,7 +33,7 @@ export interface OB11GroupMember { title?: string } -export interface OB11Group{ +export interface OB11Group { group_id: string group_name: string member_count?: number diff --git a/src/renderer.ts b/src/renderer.ts index cdb3fdd..03f5a34 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -6,11 +6,11 @@ async function onSettingWindowCreated(view: Element) { window.llonebot.log("setting window created"); let config = await window.llonebot.getConfig() - function creatHostEleStr(host: string) { + function createHttpHostEleStr(host: string) { let eleStr = `

事件上报地址(http)

-
@@ -19,29 +19,87 @@ async function onSettingWindowCreated(view: Element) { return eleStr } - let hostsEleStr = "" - for (const host of config.httpHosts) { - hostsEleStr += creatHostEleStr(host); + function createWsHostEleStr(host: string) { + let eleStr = ` + +

事件上报地址(反向websocket)

+ +
+ + ` + return eleStr } + + let httpHostsEleStr = "" + for (const host of config.httpHosts) { + httpHostsEleStr += createHttpHostEleStr(host); + } + + let wsHostsEleStr = "" + for (const host of config.wsHosts) { + wsHostsEleStr += createWsHostEleStr(host); + } + let html = `
- 监听端口 - + HTTP监听端口 +
- +
-
- ${hostsEleStr} +
+ ${httpHostsEleStr} +
+ + + 正向Websocket监听端口 + + +
+ +
+
+ ${wsHostsEleStr}
+ +
+
启用HTTP支持
+
修改后须重启QQ生效
+
+ +
+ +
+
启用HTTP POST支持
+
修改后须重启QQ生效
+
+ +
+ +
+
启用正向Websocket支持
+
修改后须重启QQ生效
+
+ +
+ +
+
启用反向Websocket支持
+
修改后须重启QQ生效
+
+ +
上报文件进行base64编码
@@ -92,15 +150,23 @@ async function onSettingWindowCreated(view: Element) { const doc = parser.parseFromString(html, "text/html"); - function addHostEle(initValue: string = "") { - let addressDoc = parser.parseFromString(creatHostEleStr(initValue), "text/html"); + function addHostEle(type: string, initValue: string = "") { + let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html"); let addressEle = addressDoc.querySelector("setting-item") - let hostItemsEle = document.getElementById("hostItems"); + let hostItemsEle; + if (type === "ws") { + hostItemsEle = document.getElementById("wsHostItems"); + } + else { + hostItemsEle = document.getElementById("httpHostItems"); + } + hostItemsEle.appendChild(addressEle); } - doc.getElementById("addHost").addEventListener("click", () => addHostEle()) + doc.getElementById("addHttpHost").addEventListener("click", () => addHostEle("http")) + doc.getElementById("addWsHost").addEventListener("click", () => addHostEle("ws")) function switchClick(eleId: string, configKey: string) { doc.getElementById(eleId)?.addEventListener("click", (e) => { @@ -116,6 +182,10 @@ async function onSettingWindowCreated(view: Element) { }) } + switchClick("http", "enableHttp"); + switchClick("httpPost", "enableHttpPost"); + switchClick("websocket", "enableWs"); + switchClick("websocketReverse", "enableWsReverse"); switchClick("debug", "debug"); switchClick("switchBase64", "enableBase64"); switchClick("reportSelfMessage", "reportSelfMessage"); @@ -123,20 +193,35 @@ async function onSettingWindowCreated(view: Element) { doc.getElementById("save")?.addEventListener("click", () => { - const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement - const hostEles: HTMLCollectionOf = document.getElementsByClassName("host") as HTMLCollectionOf; - // const port = doc.querySelector("input[type=number]")?.value - // const host = doc.querySelector("input[type=text]")?.value + const httpPortEle: HTMLInputElement = document.getElementById("httpPort") as HTMLInputElement; + const httpHostEles: HTMLCollectionOf = document.getElementsByClassName("httpHost") as HTMLCollectionOf; + const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement; + const wsHostEles: HTMLCollectionOf = document.getElementsByClassName("wsHost") as HTMLCollectionOf; + // 获取端口和host - const port = portEle.value - let hosts: string[] = []; - for (const hostEle of hostEles) { + const httpPort = httpPortEle.value + let httpHosts: string[] = []; + + for (const hostEle of httpHostEles) { if (hostEle.value) { - hosts.push(hostEle.value); + httpHosts.push(hostEle.value); } } - config.httpPort = parseInt(port); - config.httpHosts = hosts; + + const wsPort = wsPortEle.value + let wsHosts: string[] = []; + + for (const hostEle of wsHostEles) { + if (hostEle.value) { + wsHosts.push(hostEle.value); + } + } + + + config.httpPort = parseInt(httpPort); + config.httpHosts = httpHosts; + config.wsPort = parseInt(wsPort); + config.wsHosts = wsHosts; window.llonebot.setConfig(config); alert("保存成功"); }) @@ -146,7 +231,6 @@ async function onSettingWindowCreated(view: Element) { view.appendChild(node); }); - }