feat: 预添加群成员变动事件

This commit is contained in:
Disy
2024-02-15 21:47:16 +08:00
parent c875cfda15
commit 8f48d1d4ca
6 changed files with 260 additions and 60 deletions

View File

@@ -1,10 +1,12 @@
import {BrowserWindow} from 'electron'; import {BrowserWindow} from 'electron';
import { getConfigUtil, log } from "../common/utils"; import {log} from "../common/utils";
import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall"; import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
import { Group, User } from "./types"; import {Group, GroupMember, RawMessage, User} from "./types";
import { RawMessage } from "./types";
import {addHistoryMsg, friends, groups, msgHistory} from "../common/data"; import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
import {v4 as uuidv4} from 'uuid'; 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<string, (apiReturn: any) => void> = {} export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
@@ -41,7 +43,7 @@ let receiveHooks: Array<{
export function hookNTQQApiReceive(window: BrowserWindow) { export function hookNTQQApiReceive(window: BrowserWindow) {
const originalSend = window.webContents.send; const originalSend = window.webContents.send;
const patchSend = (channel: string, ...args: NTQQApiReturnData) => { 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) { if (args?.[1] instanceof Array) {
for (let receiveData of args?.[1]) { for (let receiveData of args?.[1]) {
const ntQQApiMethodName = receiveData.cmdName; const ntQQApiMethodName = receiveData.cmdName;
@@ -90,25 +92,91 @@ export function removeReceiveHook(id: string) {
receiveHooks.splice(index, 1); receiveHooks.splice(index, 1);
} }
async function updateGroups(_groups: Group[]) { async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
for (let group of _groups) { for (let group of _groups) {
let existGroup = groups.find(g => g.groupCode == group.groupCode) let existGroup = groups.find(g => g.groupCode == group.groupCode);
if (!existGroup) { if (existGroup) {
NTQQApi.getGroupMembers(group.groupCode).then(members => { Object.assign(existGroup, group);
if (members) { }
group.members = members else {
groups.push(group);
}
if (needUpdate) {
const members = await NTQQApi.getGroupMembers(group.groupCode);
if (members) {
group.members = members;
} }
})
groups.push(group)
log("update group members", group.members)
} else {
Object.assign(existGroup, group)
} }
} }
} }
registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS, (payload) => updateGroups(payload.groupList).then()) async function processGroupEvent(payload) {
registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS_UNIX, (payload) => updateGroups(payload.groupList).then()) 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<string>(); // 建立索引降低时间复杂度
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<string>();
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<{ registerReceiveHook<{
data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[] data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
}>(ReceiveCmd.FRIENDS, payload => { }>(ReceiveCmd.FRIENDS, payload => {
@@ -125,10 +193,6 @@ registerReceiveHook<{
} }
}) })
// registerReceiveHook<any>(ReceiveCmd.USER_INFO, (payload)=>{
// log("user info", payload);
// })
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payload) => {
for (const message of payload.msgList) { for (const message of payload.msgList) {
addHistoryMsg(message) addHistoryMsg(message)
@@ -138,6 +202,12 @@ registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payl
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
for (const message of payload.msgList) { for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message) // log("收到新消息push到历史记录", message)
OB11Constructor.message(message).then(
function (message) {
callEvent<OB11Message>(EventType.MESSAGE, message);
}
);
addHistoryMsg(message) addHistoryMsg(message)
} }
const msgIds = Object.keys(msgHistory); const msgIds = Object.keys(msgHistory);

View File

@@ -1,7 +1,6 @@
import { getHistoryMsgByShortId, msgHistory } from "../../common/data"; import { getHistoryMsgByShortId, msgHistory } from "../../common/data";
import { OB11Message } from '../types'; import { OB11Message } from '../types';
import { OB11Constructor } from "../constructor"; import { OB11Constructor } from "../constructor";
import { log } from "../../common/utils";
import BaseAction from "./BaseAction"; import BaseAction from "./BaseAction";
import { ActionName } from "./types"; import { ActionName } from "./types";

38
src/onebot11/event.ts Normal file
View File

@@ -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<DataType>(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)
)
);
}
}

View File

@@ -11,6 +11,7 @@ import { selfInfo } from "../common/data";
import { OB11Message, OB11Return, OB11MessageData } from './types'; import { OB11Message, OB11Return, OB11MessageData } from './types';
import {actionHandlers, actionMap} from "./actions"; import {actionHandlers, actionMap} from "./actions";
import {OB11Response, OB11WebsocketResponse} from "./actions/utils"; import {OB11Response, OB11WebsocketResponse} from "./actions/utils";
import {registerEventSender, unregisterEventSender} from "./event";
// @SiberianHusky 2021-08-15 // @SiberianHusky 2021-08-15
@@ -100,8 +101,8 @@ export function startWebsocketServer(port: number) {
export function initWebsocket() { export function initWebsocket() {
if (getConfigUtil().getConfig().enableWs) { if (getConfigUtil().getConfig().enableWs) {
expressWsApp.ws("/api", onWebsocketMessage); expressWsApp.ws("/api", initWebsocketServer);
expressWsApp.ws("/", onWebsocketMessage); expressWsApp.ws("/", initWebsocketServer);
} }
initReverseWebsocket(); initReverseWebsocket();
@@ -114,8 +115,10 @@ function initReverseWebsocket() {
try { try {
const wsClient = new WebSocket(url); const wsClient = new WebSocket(url);
websocketClientConnections.push(wsClient); websocketClientConnections.push(wsClient);
registerEventSender(wsClient);
wsClient.onclose = function (ev) { wsClient.onclose = function (ev) {
unregisterEventSender(wsClient);
let index = websocketClientConnections.indexOf(wsClient); let index = websocketClientConnections.indexOf(wsClient);
if (index !== -1) { if (index !== -1) {
websocketClientConnections.splice(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) { ws.on("message", async function (message) {
try { try {
let recv = JSON.parse(message); let recv = JSON.parse(message);
@@ -167,7 +172,11 @@ function onWebsocketMessage(ws, req) {
log(e.stack); log(e.stack);
ws.send(JSON.stringify(OB11WebsocketResponse.error(e.stack.toString(), 1200))); ws.send(JSON.stringify(OB11WebsocketResponse.error(e.stack.toString(), 1200)));
} }
}) });
ws.on("close", function (ev) {
unregisterEventSender(ws);
});
} }

View File

@@ -6,11 +6,11 @@ async function onSettingWindowCreated(view: Element) {
window.llonebot.log("setting window created"); window.llonebot.log("setting window created");
let config = await window.llonebot.getConfig() let config = await window.llonebot.getConfig()
function creatHostEleStr(host: string) { function createHttpHostEleStr(host: string) {
let eleStr = ` let eleStr = `
<setting-item data-direction="row" class="hostItem vertical-list-item"> <setting-item data-direction="row" class="hostItem vertical-list-item">
<h2>事件上报地址(http)</h2> <h2>事件上报地址(http)</h2>
<input class="host input-text" type="text" value="${host}" <input class="httpHost input-text" type="text" value="${host}"
style="width:60%;padding: 5px" style="width:60%;padding: 5px"
placeholder="如果localhost上报失败试试局域网ip"/> placeholder="如果localhost上报失败试试局域网ip"/>
</setting-item> </setting-item>
@@ -19,29 +19,87 @@ async function onSettingWindowCreated(view: Element) {
return eleStr return eleStr
} }
let hostsEleStr = "" function createWsHostEleStr(host: string) {
for (const host of config.httpHosts) { let eleStr = `
hostsEleStr += creatHostEleStr(host); <setting-item data-direction="row" class="hostItem vertical-list-item">
<h2>事件上报地址(反向websocket)</h2>
<input class="wsHost input-text" type="text" value="${host}"
style="width:60%;padding: 5px"
placeholder="如果localhost上报失败试试局域网ip"/>
</setting-item>
`
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 = ` let html = `
<div class="config_view llonebot"> <div class="config_view llonebot">
<setting-section> <setting-section>
<setting-panel> <setting-panel>
<setting-list class="wrap"> <setting-list class="wrap">
<setting-item class="vertical-list-item" data-direction="row"> <setting-item class="vertical-list-item" data-direction="row">
<setting-text>监听端口</setting-text> <setting-text>HTTP监听端口</setting-text>
<input id="port" type="number" value="${config.httpPort}"/> <input id="httpPort" type="number" value="${config.httpPort}"/>
</setting-item> </setting-item>
<div> <div>
<button id="addHost" class="q-button">添加上报地址</button> <button id="addHttpHost" class="q-button">添加HTTP POST上报地址</button>
</div> </div>
<div id="hostItems"> <div id="httpHostItems">
${hostsEleStr} ${httpHostsEleStr}
</div>
<setting-item class="vertical-list-item" data-direction="row">
<setting-text>正向Websocket监听端口</setting-text>
<input id="wsPort" type="number" value="${config.wsPort}"/>
</setting-item>
<div>
<button id="addWsHost" class="q-button">添加反向Websocket上报地址</button>
</div>
<div id="wsHostItems">
${wsHostsEleStr}
</div> </div>
<button id="save" class="q-button">保存(监听端口重启QQ后生效)</button> <button id="save" class="q-button">保存(监听端口重启QQ后生效)</button>
</setting-list> </setting-list>
</setting-panel> </setting-panel>
<setting-panel> <setting-panel>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>启用HTTP支持</div>
<div class="tips">修改后须重启QQ生效</div>
</div>
<setting-switch id="http" ${config.enableHttp ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>启用HTTP POST支持</div>
<div class="tips">修改后须重启QQ生效</div>
</div>
<setting-switch id="httpPost" ${config.enableHttpPost ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>启用正向Websocket支持</div>
<div class="tips">修改后须重启QQ生效</div>
</div>
<setting-switch id="websocket" ${config.enableWs ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>启用反向Websocket支持</div>
<div class="tips">修改后须重启QQ生效</div>
</div>
<setting-switch id="websocketReverse" ${config.enableWsReverse ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item"> <setting-item data-direction="row" class="hostItem vertical-list-item">
<div> <div>
<div>上报文件进行base64编码</div> <div>上报文件进行base64编码</div>
@@ -92,15 +150,23 @@ async function onSettingWindowCreated(view: Element) {
const doc = parser.parseFromString(html, "text/html"); const doc = parser.parseFromString(html, "text/html");
function addHostEle(initValue: string = "") { function addHostEle(type: string, initValue: string = "") {
let addressDoc = parser.parseFromString(creatHostEleStr(initValue), "text/html"); let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html");
let addressEle = addressDoc.querySelector("setting-item") 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); 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) { function switchClick(eleId: string, configKey: string) {
doc.getElementById(eleId)?.addEventListener("click", (e) => { 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("debug", "debug");
switchClick("switchBase64", "enableBase64"); switchClick("switchBase64", "enableBase64");
switchClick("reportSelfMessage", "reportSelfMessage"); switchClick("reportSelfMessage", "reportSelfMessage");
@@ -123,20 +193,35 @@ async function onSettingWindowCreated(view: Element) {
doc.getElementById("save")?.addEventListener("click", doc.getElementById("save")?.addEventListener("click",
() => { () => {
const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement const httpPortEle: HTMLInputElement = document.getElementById("httpPort") as HTMLInputElement;
const hostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>; const httpHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("httpHost") as HTMLCollectionOf<HTMLInputElement>;
// const port = doc.querySelector("input[type=number]")?.value const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement;
// const host = doc.querySelector("input[type=text]")?.value const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("wsHost") as HTMLCollectionOf<HTMLInputElement>;
// 获取端口和host // 获取端口和host
const port = portEle.value const httpPort = httpPortEle.value
let hosts: string[] = []; let httpHosts: string[] = [];
for (const hostEle of hostEles) {
for (const hostEle of httpHostEles) {
if (hostEle.value) { 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); window.llonebot.setConfig(config);
alert("保存成功"); alert("保存成功");
}) })
@@ -146,7 +231,6 @@ async function onSettingWindowCreated(view: Element) {
view.appendChild(node); view.appendChild(node);
}); });
} }