LLOneBot/src/renderer.ts
2024-01-20 23:22:44 +08:00

517 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// <reference path="./global.d.ts" />
// import express from "express";
// const { ipcRenderer } = require('electron');
import {AtType, Group, MessageElement, OnebotGroupMemberRole, Peer, PostDataSendMsg, User} from "./common/types";
import * as stream from "stream";
import {raw} from "express";
let self_qq: string = ""
let groups: Group[] = []
let friends: User[] = []
let msgHistory: MessageElement[] = []
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
}
async function getFriends() {
let _friends = await window.LLAPI.getFriendsList(false)
for (let friend of _friends) {
let existFriend = friends.find(f => f.uin == friend.uin)
if (!existFriend) {
friends.push(friend)
}
}
window.llonebot.updateFriends(friends)
return friends
}
async function getFriend(qq: string) {
let friend = friends.find(friend => friend.uin == qq)
if (!friend) {
await getFriends();
friend = friends.find(friend => friend.uin == qq);
}
return friend;
}
async function getGroup(qq: string) {
let group = groups.find(group => group.uid == qq)
if (!group) {
await getGroups();
group = groups.find(group => group.uid == qq)
}
return group
}
async function getGroups() {
let __groups = await window.LLAPI.getGroupsList(false)
for (let group of __groups) {
group.members = [];
let existGroup = groups.find(g => g.uid == group.uid)
if (!existGroup) {
// console.log("更新群列表", groups)
groups.push(group)
}
}
window.llonebot.updateGroups(groups)
return groups
}
async function getGroupMembers(group_qq: string, forced: boolean = false) {
let group = await getGroup(group_qq)
if (!group?.members || group!.members!.length == 0 || forced) {
let res = (await window.LLAPI.getGroupMemberList(group_qq, 5000))
// console.log(`更新群${group}成员列表 await`, _res)
// window.LLAPI.getGroupMemberList(group_qq + "_groupMemberList_MainWindow", 5000).then(res =>{
let members = res.result.infos.values();
console.log("getGroupMemberList api response", res)
if (members && forced) {
group.members = []
}
for (const member of members) {
if (!group!.members!.find(m => m.uid == member.uid)) {
group!.members!.push(member)
}
}
window.llonebot.updateGroups(groups)
console.log(`更新群${group.name}成员列表`, group)
// })
}
return group?.members
}
async function getGroupMember(group_qq: string, member_uid: string) {
let members = await getGroupMembers(group_qq)
if (members) {
let member = members.find(member => member.uid == member_uid)
if (!member) {
members = await getGroupMembers(group_qq, true)
member = members?.find(member => member.uid == member_uid)
}
return member
}
}
async function handleNewMessage(messages: MessageElement[]) {
for (let message of messages) {
let onebot_message_data: any = {
self: {
platform: "qq",
user_id: self_qq
},
self_id: self_qq,
time: 0,
type: "message",
post_type: "message",
message_type: message.peer.chatType,
detail_type: message.peer.chatType,
message_id: message.raw.msgId,
sub_type: "",
message: []
}
if (message.peer.chatType == "group") {
let group_id = message.peer.uid
let group = (await getGroup(group_id))!
onebot_message_data["group_id"] = message.peer.uid
let groupMember = await getGroupMember(group_id, message.sender.uid)
onebot_message_data["user_id"] = groupMember!.uin
onebot_message_data.sender = {
user_id: groupMember!.uin,
nickname: groupMember!.nick,
card: groupMember!.cardName,
role: OnebotGroupMemberRole[groupMember!.role]
}
console.log("收到群消息", onebot_message_data)
} else if (message.peer.chatType == "private") {
onebot_message_data["user_id"] = message.peer.uid
let friend = await getFriend(message.sender.uid)
onebot_message_data.sender = {
user_id: friend!.uin,
nickname: friend!.nickName
}
}
for (let element of message.raw.elements) {
let message_data: any = {
data: {},
type: "unknown"
}
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 = await getGroupMember(message.peer.uid, uid)
message_data["data"]["mention"] = atMember!.uin
message_data["data"]["qq"] = 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
let startS = "file://"
if (!element.picElement.sourcePath.startsWith("/")) {
startS += "/"
}
message_data["data"]["file"] = startS + element.picElement.sourcePath
} else if (element.replyElement) {
message_data["type"] = "reply"
message_data["data"]["id"] = msgHistory.find(msg => msg.raw.msgSeq == element.replyElement.replayMsgSeq)?.raw.msgId
}
onebot_message_data.message.push(message_data)
}
msgHistory.push(message)
console.log("发送上传消息给ipc main", onebot_message_data)
window.llonebot.postData(onebot_message_data);
}
}
async function listenSendMessage(postData: PostDataSendMsg) {
if (postData.action == "send_private_msg" || postData.action == "send_group_msg") {
let peer: Peer | null = null;
if (!postData.params) {
postData.params = {
message: postData.message,
user_id: postData.user_id,
group_id: postData.group_id
}
}
if (postData.action == "send_private_msg") {
let friend = await getFriend(postData.params.user_id)
if (friend) {
peer = {
chatType: "private",
name: friend.nickName,
uid: friend.uid
}
}
} else if (postData.action == "send_group_msg") {
let group = await getGroup(postData.params.group_id)
if (group) {
peer = {
chatType: "group",
name: group.name,
uid: group.uid
}
} else {
console.log("未找到群, 发送群消息失败", postData)
}
}
if (peer) {
let sendFiles: string[] = [];
for (let message of postData.params.message) {
if (message.type == "at") {
// @ts-ignore
message.type = "text"
message.atType = AtType.atUser
let atUid = message.data?.qq || message.atUid
let group = await getGroup(postData.params.group_id)
let atMember = group.members.find(member => member.uin == atUid)
message.atNtUid = atMember.uid
message.atUid = atUid
message.content = `@${atMember.cardName || atMember.nick}`
} else if (message.type == "text") {
message.content = message.data?.text || message.content
} else if (message.type == "image" || message.type == "voice" || message.type == "record") {
// todo: 收到的应该是uri格式的需要转成本地的, uri格式有三种http, file, base64
let url = message.data?.file || message.file
let uri = new URL(url);
let ext: string;
if (message.type == "image") {
ext = ".png"
}
if (message.type == "voice" || message.type == "record") {
message.type = "voice"
ext = ".amr"
}
let localFilePath = `${Date.now()}${ext}`
if (uri.protocol == "file:") {
localFilePath = url.split("file://")[1]
} else {
localFilePath = await window.llonebot.downloadFile({uri: url, localFilePath: localFilePath})
}
message.file = localFilePath
sendFiles.push(localFilePath);
} else if (message.type == "reply") {
let msgId = message.data?.id || message.msgId
let replyMessage = msgHistory.find(msg => msg.raw.msgId == msgId)
message.msgId = msgId
message.msgSeq = replyMessage?.raw.msgSeq || ""
}
}
console.log("发送消息", postData)
window.LLAPI.sendMessage(peer, postData.params.message).then(res => {
console.log("消息发送成功:", peer, postData.params.message)
if (sendFiles.length) {
window.llonebot.deleteFile(sendFiles);
}
},
err => console.log("消息发送失败", postData, err))
}
}
}
function recallMessage(msgId: string) {
let msg = msgHistory.find(msg => msg.raw.msgId == msgId)
window.LLAPI.recallMessage(msg.peer, [msgId]).then()
}
let chatListEle: HTMLCollectionOf<Element>
async function getGroupsMembers(groupsArg: Group[]) {
// 批量获取群成员列表
let failedGroups: Group[] = []
for (const group of groupsArg) {
let handledGroup = await getGroupMembers(group.uid, true)
if (handledGroup.length == 0) {
failedGroups.push(group)
}
}
if (failedGroups.length > 0) {
console.log("获取群成员列表失败,重试", failedGroups.map(group => group.name))
setTimeout(() => {
getGroupsMembers(failedGroups).then()
}, 1000)
} else {
window.llonebot.log("全部群成员获取完毕")
}
}
function onNewMessages(messages: MessageElement[]) {
async function func(messages: MessageElement[]) {
console.log("收到新消息", messages)
if (!self_qq) {
self_qq = (await window.LLAPI.getAccountInfo()).uin
}
await handleNewMessage(messages);
}
func(messages).then(() => {
})
// console.log("chatListEle", chatListEle)
}
async function initAccountInfo(){
let accountInfo = await window.LLAPI.getAccountInfo();
window.llonebot.log("getAccountInfo " + JSON.stringify(accountInfo));
if (!accountInfo.uid) {
return;
}
let selfInfo = await window.LLAPI.getUserInfo(accountInfo.uid);
window.llonebot.setSelfInfo({
user_id: accountInfo.uin,
nickname: selfInfo.nickName
});
window.llonebot.log("selfInfo " + JSON.stringify(selfInfo));
}
function onLoad() {
window.llonebot.log("llonebot render onLoad");
window.llonebot.getRunningStatus().then(running=>{
if (running) {
return;
}
initAccountInfo().then(
()=>{
if (friends.length == 0) {
getFriends().then(()=>{});
}
if (groups.length == 0) {
getGroups().then(()=>{
getGroupsMembers(groups).then(()=>{});
});
}
window.LLAPI.on("new-messages", onNewMessages);
window.LLAPI.on("new-send-messages", onNewMessages);
window.llonebot.log("llonebot render start");
window.llonebot.startExpress();
window.llonebot.listenSendMessage((postData: PostDataSendMsg) => {
listenSendMessage(postData).then().catch(err => console.log("listenSendMessage err", err))
})
window.llonebot.listenRecallMessage((arg: { message_id: string }) => {
recallMessage(arg.message_id)
})
window.llonebot.log("llonebot loaded");
window.LLAPI.add_qmenu((qContextMenu: Node) => {
let btn = document.createElement("a")
btn.className = "q-context-menu-item q-context-menu-item--normal vue-component"
btn.setAttribute("aria-disabled", "false")
btn.setAttribute("role", "menuitem")
btn.setAttribute("tabindex", "-1")
btn.onclick = () => {
// window.LLAPI.getPeer().then(peer => {
// // console.log("current peer", peer)
// if (peer && peer.chatType == "group") {
// getGroupMembers(peer.uid, true).then(()=> {
// console.log("获取群成员列表成功", groups);
// alert("获取群成员列表成功")
// })
// }
// })
async function func() {
for (const group of groups) {
await getGroupMembers(group.uid, true)
}
}
func().then(() => {
console.log("获取群成员列表结果", groups);
// 找到members数量为空的群
groups.map(group => {
if (group.members.length == 0) {
console.log(`${group.name}群成员为空`)
}
})
window.llonebot.updateGroups(groups)
})
}
btn.innerText = "获取群成员列表"
console.log(qContextMenu)
// qContextMenu.appendChild(btn)
})
window.LLAPI.on("context-msg-menu", (event, target, msgIds) => {
console.log("msg menu", event, target, msgIds);
})
// console.log("getAccountInfo", LLAPI.getAccountInfo());
function getChatListEle() {
chatListEle = document.getElementsByClassName("viewport-list__inner")
console.log("chatListEle", chatListEle)
if (chatListEle.length == 0) {
setTimeout(getChatListEle, 500)
} else {
try {
// 选择要观察的目标节点
const targetNode = chatListEle[0];
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
// console.log("chat list changed", mutation.type); // 输出 mutation 的类型
// 获得当前聊天窗口
window.LLAPI.getPeer().then(peer => {
// console.log("current peer", peer)
if (peer && peer.chatType == "group") {
getGroupMembers(peer.uid, false).then()
}
})
});
});
// 配置观察选项
const config = {attributes: true, childList: true, subtree: true};
// 传入目标节点和观察选项
observer.observe(targetNode, config);
} catch (e) {
window.llonebot.log(e)
}
}
}
// getChatListEle();
}
);
});
}
// 打开设置界面时触发
async function onSettingWindowCreated (view: any) {
window.llonebot.log("setting window created");
const {port, hosts} = await window.llonebot.getConfig()
function creatHostEleStr(host: string) {
let eleStr = `
<div class="hostItem vertical-list-item">
<h2>事件上报地址(http)</h2>
<input class="host" type="text" value="${host}"
style="width:60%;padding: 5px"
placeholder="不支持localhost,如果是本机请填写局域网ip"/>
</div>
`
return eleStr
}
let hostsEleStr = ""
for (const host of hosts) {
hostsEleStr += creatHostEleStr(host);
}
const html = `
<section class="wrap">
<div class="vertical-list-item">
<h2>监听端口</h2>
<input id="port" type="number" value="${port}"/>
</div>
<div>
<button id="addHost" class="q-button">添加上报地址</button>
</div>
<div id="hostItems">
${hostsEleStr}
</div>
<button id="save" class="q-button">保存(监听端口重启QQ后生效)</button>
</section>
`
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
function addHostEle(initValue: string = "") {
let addressDoc = parser.parseFromString(creatHostEleStr(initValue), "text/html");
let addressEle = addressDoc.querySelector("div")
let hostItemsEle = document.getElementById("hostItems");
hostItemsEle.appendChild(addressEle);
}
doc.getElementById("addHost").addEventListener("click", () => addHostEle())
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
// 获取端口和host
const port = portEle.value
let hosts: string[] = [];
for (const hostEle of hostEles) {
if (hostEle.value) {
hosts.push(hostEle.value);
}
}
window.llonebot.setConfig({
port: parseInt(port),
hosts: hosts
})
alert("保存成功");
})
doc.querySelectorAll("section").forEach((node) => view.appendChild(node));
}
onLoad()
export {
onSettingWindowCreated
}