群成员获取研究

This commit is contained in:
linyuchen 2023-11-01 17:46:46 +08:00
parent 7a7d21d533
commit e2759417f5
6 changed files with 209 additions and 153 deletions

View File

@ -5,7 +5,8 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc" "build-mac": "tsc && npm run deploy-mac",
"deploy-mac": "cp dist/* ~/Library/Containers/com.tencent.qq/Data/Documents/LiteLoaderQQNT/plugins/LLOnebot/"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",

85
src/global.d.ts vendored
View File

@ -1,68 +1,6 @@
declare enum AtType { import {Group, GroupMemberInfo, MessageElement, Peer, PostDataSendMsg, SendMessage, User} from "./types";
notAt = 0,
atUser = 2
}
declare type Peer = {
chatType: "private" | "group"
name: string
uid: string // qq号
}
interface MessageElement {
raw: {
elements: {
replyElement: {
senderUid: string, // 原消息发送者QQ号
sourceMsgIsIncPic: boolean; // 原消息是否有图片
sourceMsgText: string;
sourceMsgIdInRecords: string; // 原消息id
},
textElement: {
atType: AtType
atUid: string,
content: string
},
picElement: {
sourcePath: string // 图片本地路径
picWidth: number
picHeight: number
fileSize: number
fileName: string
fileUuid: string
}
}[]
}
peer: Peer,
sender: {
uid: string // 一串加密的字符串
memberName: string
nickname: string
}
}
declare type User = {
avatarUrl?: string;
bio?: string; // 签名
nickName: string;
uid?: string; // 加密的字符串
uin: string; // QQ号
}
declare type Group = {
uid: string; // 群号
name: string;
}
declare type SendMessage = {
type: "text",
content: string,
} | {
type: "image",
file: string, // 这是本地路径?
}
declare var LLAPI: { declare var LLAPI: {
on(event: "new-messages", callback: (data: MessageElement[]) => void): void; on(event: "new-messages", callback: (data: MessageElement[]) => void): void;
@ -76,16 +14,12 @@ declare var LLAPI: {
sendMessage(peer: Peer, message: SendMessage[]): Promise<void>; sendMessage(peer: Peer, message: SendMessage[]): Promise<void>;
getGroupsList(forced: boolean): Promise<Group[]> getGroupsList(forced: boolean): Promise<Group[]>
getFriendsList(forced: boolean): Promise<User[]> getFriendsList(forced: boolean): Promise<User[]>
getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Record<string, GroupMemberInfo> }}>
}; };
declare type PostDataSendMsg = {
action: "send_private_msg" | "send_group_msg" | "get_group_list",
params: {
user_id: string,
group_id: string,
message: SendMessage[];
}
}
declare var llonebot: { declare var llonebot: {
postData: (data: any) => void postData: (data: any) => void
@ -94,4 +28,11 @@ declare var llonebot: {
updateFriends: (friends: User[]) => void updateFriends: (friends: User[]) => void
updateGroupMembers: (data: { groupMembers: User[], group_id: string }) => void updateGroupMembers: (data: { groupMembers: User[], group_id: string }) => void
startExpress: () => void startExpress: () => void
}; };
declare global {
interface Window {
LLAPI: typeof LLAPI;
llonebot: typeof llonebot;
}
}

View File

@ -1,5 +1,7 @@
// 运行在 Electron 主进程 下的插件入口 // 运行在 Electron 主进程 下的插件入口
import {Group, PostDataSendMsg, User} from "./types";
const express = require("express") const express = require("express")
const {ipcMain, webContents} = require('electron'); const {ipcMain, webContents} = require('electron');
const fs = require('fs'); const fs = require('fs');
@ -24,8 +26,9 @@ function sendIPCCallSendQQMsg(postData: PostDataSendMsg) {
sendIPCMsg(CHANNEL_SEND_MSG, postData); sendIPCMsg(CHANNEL_SEND_MSG, postData);
} }
function log(msg: string){ function log(msg: any){
fs.appendFile("d:\\llonebot.log", msg + "\n", (err: any) => { let currentDateTime = new Date().toLocaleString();
fs.appendFile("./llonebot.log", currentDateTime + ":" + msg + "\n", (err: any) => {
}) })
} }
@ -143,6 +146,10 @@ function onLoad(plugin: any) {
log(e.toString()) log(e.toString())
} }
}) })
ipcMain.on("llonebot_log", (event: any, arg: any) => {
log(arg)
})
} }

View File

@ -1,5 +1,7 @@
// Electron 主进程 与 渲染进程 交互的桥梁 // Electron 主进程 与 渲染进程 交互的桥梁
import {Group, PostDataSendMsg, User} from "./types";
const {contextBridge} = require("electron"); const {contextBridge} = require("electron");
const {ipcRenderer} = require('electron'); const {ipcRenderer} = require('electron');
@ -24,6 +26,9 @@ contextBridge.exposeInMainWorld("llonebot", {
}, },
startExpress: () => { startExpress: () => {
ipcRenderer.send("startExpress"); ipcRenderer.send("startExpress");
},
log: (data: any) => {
ipcRenderer.send("log", data);
} }
// startExpress, // startExpress,
}); });

View File

@ -2,16 +2,23 @@
// import express from "express"; // import express from "express";
// const { ipcRenderer } = require('electron'); // const { ipcRenderer } = require('electron');
enum AtType { import {AtType, Group, MessageElement, Peer, PostDataSendMsg, User} from "./types";
notAt = 0,
atUser = 2
}
const host = "http://localhost:5000" const host = "http://localhost:5000"
let self_qq: string = ""
let groups: Group[] = [] let groups: Group[] = []
let friends: User[] = [] let friends: User[] = []
let groupMembers: { group_id: string, groupMembers: User[] }[] = [] let uid_maps: Record<string, User> = {} // 一串加密的字符串 -> qq号
async function getUserInfo(uid: string): Promise<User>{
let user = uid_maps[uid]
if (!user){
// 从服务器获取用户信息
user = await window.LLAPI.getUserInfo(uid)
uid_maps[uid] = user
}
return user
}
function getFriend(qq: string) { function getFriend(qq: string) {
return friends.find(friend => friend.uid == qq) return friends.find(friend => friend.uid == qq)
@ -21,9 +28,7 @@ function getGroup(qq: string) {
return groups.find(group => group.uid == qq) return groups.find(group => group.uid == qq)
} }
let self_qq: string = ""
let uid_maps: Record<string, string> = {} // 一串加密的字符串 -> qq号
function forwardMessage(message: MessageElement) { function forwardMessage(message: MessageElement) {
try { try {
@ -36,79 +41,92 @@ function forwardMessage(message: MessageElement) {
type: "message", type: "message",
detail_type: message.peer.chatType, detail_type: message.peer.chatType,
sub_type: "", sub_type: "",
message: message.raw.elements.map(element => { message: []
let message_data: any = {
data: {}
}
if (element.textElement?.atType == AtType.atUser) {
message_data["type"] = "at"
message_data["data"]["mention"] = element.textElement.atUid
} else if (element.textElement) {
message_data["type"] = "text"
message_data["data"]["text"] = element.textElement.content
} else if (element.picElement) {
message_data["type"] = "image"
message_data["data"]["file_id"] = element.picElement.fileUuid
message_data["data"]["path"] = element.picElement.sourcePath
} else if (element.replyElement) {
message_data["type"] = "reply"
message_data["data"]["reply"] = element.replyElement.sourceMsgIdInRecords
}
return message_data
})
} }
let group: Group
if (message.peer.chatType == "group") { if (message.peer.chatType == "group") {
let group_id = message.peer.uid
group = groups.find(group => group.uid == group_id)!
onebot_message_data["group_id"] = message.peer.uid onebot_message_data["group_id"] = message.peer.uid
// todo: 将加密的uid转成qq号 let groupMember = group.members!.find(member => member.uid == message.sender.uid)
let groupMember = groupMembers.find(group => group.group_id == message.peer.uid)?.groupMembers.find(member => member.uid == message.sender.uid) if (groupMember) {
onebot_message_data["user_id"] = groupMember!.uin console.log("群成员信息存在,使用群成员信息")
onebot_message_data["user_id"] = groupMember.uin
}
else{
console.log("群成员信息不存在使用原始uid")
onebot_message_data["user_id"] = message.sender.uid
}
console.log("收到群消息", onebot_message_data) console.log("收到群消息", onebot_message_data)
} else if (message.peer.chatType == "private") { } else if (message.peer.chatType == "private") {
onebot_message_data["user_id"] = message.peer.uid onebot_message_data["user_id"] = message.peer.uid
} }
console.log("发送上传消息给ipcmain", onebot_message_data) for (let element of message.raw.elements) {
llonebot.postData(onebot_message_data); let message_data: any = {
data: {}
}
if (element.textElement?.atType == AtType.atUser) {
message_data["type"] = "at"
if (element.textElement.atUid != "0") {
message_data["data"]["mention"] = element.textElement.atUid
} else {
let uid = element.textElement.atNtUid
let atMember = group!.members!.find(member => member.uid == uid)
message_data["data"]["mention"] = atMember!.uin
}
} else if (element.textElement) {
message_data["type"] = "text"
message_data["data"]["text"] = element.textElement.content
} else if (element.picElement) {
message_data["type"] = "image"
message_data["data"]["file_id"] = element.picElement.fileUuid
message_data["data"]["path"] = element.picElement.sourcePath
} else if (element.replyElement) {
message_data["type"] = "reply"
message_data["data"]["reply"] = element.replyElement.sourceMsgIdInRecords
}
onebot_message_data.message.push(message_data)
}
console.log("发送上传消息给ipc main", onebot_message_data)
window.llonebot.postData(onebot_message_data);
} catch (e) { } catch (e) {
console.log("上传消息事件失败", e) console.log("上传消息事件失败", e)
} }
} }
function handleNewMessage(messages: MessageElement[]) { async function handleNewMessage(messages: MessageElement[]) {
messages.forEach(message => { for (let message of messages) {
if (message.peer.chatType == "group") { if (message.peer.chatType == "group") {
let group = groupMembers.find(group => group.group_id == message.peer.uid) let group = groups.find(group => group.uid == message.peer.uid)
if (!group) { if (!group) {
let members = (await window.LLAPI.getGroupMemberList(message.peer.uid + "_groupMemberList_MainWindow", 5000)).result.infos
let membersList = Object.values(members)
group = { group = {
group_id: message.peer.uid, name: message.peer.name,
groupMembers: [] uid: message.peer.uid,
members: membersList
} }
groupMembers.push(group) groups.push(group)
} window.llonebot.updateGroups(groups);
let existMember = group!.groupMembers.find(member => member.uid == message.sender.uid)
if (!existMember) {
window.LLAPI.getUserInfo(message.sender.uid).then(user => {
let member = {memberName: message.sender.memberName, uid: user.uin, nickName: user.nickName}
// group!.groupMembers.push(member)
group!.groupMembers.push(user)
llonebot.updateGroupMembers(group!)
forwardMessage(message)
}).catch(err => {
console.log("获取群成员信息失败", err)
})
}else{
forwardMessage(message)
} }
} }
else{ forwardMessage(message);
forwardMessage(message); }
} }
})
async function getGroups(){
groups = await window.LLAPI.getGroupsList(false)
for (let group of groups) {
group.members = [];
}
window.llonebot.updateGroups(groups)
} }
function onLoad() { function onLoad() {
llonebot.startExpress(); window.llonebot.startExpress();
llonebot.listenSendMessage((postData: PostDataSendMsg) => { window.llonebot.listenSendMessage((postData: PostDataSendMsg) => {
if (postData.action == "send_private_msg" || postData.action == "send_group_msg") { if (postData.action == "send_private_msg" || postData.action == "send_group_msg") {
let peer: Peer | null = null; let peer: Peer | null = null;
if (postData.action == "send_private_msg") { if (postData.action == "send_private_msg") {
@ -131,30 +149,25 @@ function onLoad() {
} }
} }
if (peer) { if (peer) {
LLAPI.sendMessage(peer, postData.params.message).then(res => console.log("消息发送成功:", res), window.LLAPI.sendMessage(peer, postData.params.message).then(res => console.log("消息发送成功:", res),
err => console.log("消息发送失败", postData, err)) err => console.log("消息发送失败", postData, err))
} }
} }
}); });
getGroups().then()
window.LLAPI.getGroupsList(false).then(groupsList => { function onNewMessages(messages: MessageElement[]){
groups = groupsList async function func(messages: MessageElement[]){
llonebot.updateGroups(groupsList) console.log("收到新消息", messages)
}) if (!self_qq) {
self_qq = (await window.LLAPI.getAccountInfo()).uin
window.LLAPI.on("new-messages", (messages) => { }
console.log("收到新消息", messages) await handleNewMessage(messages);
// 往groupMembers里面添加群成员
if (!self_qq){
window.LLAPI.getAccountInfo().then(accountInfo => {
console.log("getAccountInfo", accountInfo)
self_qq = accountInfo.uin
handleNewMessage(messages)
})
}else{
handleNewMessage(messages)
} }
}); func(messages).then(() => {})
}
window.LLAPI.on("new-messages", onNewMessages);
// console.log("getAccountInfo", LLAPI.getAccountInfo()); // console.log("getAccountInfo", LLAPI.getAccountInfo());
} }

89
src/types.ts Normal file
View File

@ -0,0 +1,89 @@
export enum AtType {
notAt = 0,
atUser = 2
}
export type GroupMemberInfo = {
avatarPath: string;
cardName: string;
cardType: number;
isDelete: boolean;
nick: string;
qid: string;
remark: string;
role: number; // 群主:4, 管理员:3群员:2
shutUpTime: number; // 禁言时间,单位是什么暂时不清楚
uid: string; // 加密的字符串
uin: string; // QQ号
}
export type User = {
avatarUrl?: string;
bio?: string; // 签名
nickName: string;
uid?: string; // 加密的字符串
uin: string; // QQ号
}
export type Group = {
uid: string; // 群号
name: string;
members?: GroupMemberInfo[];
}
export type Peer = {
chatType: "private" | "group"
name: string
uid: string // qq号
}
export type MessageElement = {
raw: {
elements: {
replyElement: {
senderUid: string, // 原消息发送者QQ号
sourceMsgIsIncPic: boolean; // 原消息是否有图片
sourceMsgText: string;
sourceMsgIdInRecords: string; // 原消息id
},
textElement: {
atType: AtType
atUid: string,
content: string,
atNtUid: string
},
picElement: {
sourcePath: string // 图片本地路径
picWidth: number
picHeight: number
fileSize: number
fileName: string
fileUuid: string
}
}[]
}
peer: Peer,
sender: {
uid: string // 一串加密的字符串
memberName: string
nickname: string
}
}
export type SendMessage = {
type: "text",
content: string,
} | {
type: "image",
file: string, // 本地路径
}
export type PostDataSendMsg = {
action: "send_private_msg" | "send_group_msg" | "get_group_list",
params: {
user_id: string,
group_id: string,
message: SendMessage[];
}
}