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 { 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<string, (apiReturn: any) => 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<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<{
data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
}>(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) => {
for (const message of payload.msgList) {
addHistoryMsg(message)
@@ -138,6 +202,12 @@ registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payl
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message)
OB11Constructor.message(message).then(
function (message) {
callEvent<OB11Message>(EventType.MESSAGE, message);
}
);
addHistoryMsg(message)
}
const msgIds = Object.keys(msgHistory);

View File

@@ -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";

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 {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);
});
}

View File

@@ -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

View File

@@ -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 = `
<setting-item data-direction="row" class="hostItem vertical-list-item">
<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"
placeholder="如果localhost上报失败试试局域网ip"/>
</setting-item>
@@ -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 = `
<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 = `
<div class="config_view llonebot">
<setting-section>
<setting-panel>
<setting-list class="wrap">
<setting-item class="vertical-list-item" data-direction="row">
<setting-text>监听端口</setting-text>
<input id="port" type="number" value="${config.httpPort}"/>
<setting-text>HTTP监听端口</setting-text>
<input id="httpPort" type="number" value="${config.httpPort}"/>
</setting-item>
<div>
<button id="addHost" class="q-button">添加上报地址</button>
<button id="addHttpHost" class="q-button">添加HTTP POST上报地址</button>
</div>
<div id="hostItems">
${hostsEleStr}
<div id="httpHostItems">
${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>
<button id="save" class="q-button">保存(监听端口重启QQ后生效)</button>
</setting-list>
</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">
<div>
<div>上报文件进行base64编码</div>
@@ -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<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>;
// 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<HTMLInputElement> = document.getElementsByClassName("httpHost") as HTMLCollectionOf<HTMLInputElement>;
const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement;
const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("wsHost") as HTMLCollectionOf<HTMLInputElement>;
// 获取端口和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);
});
}