refactor: 优化代码结构

This commit is contained in:
linyuchen 2023-11-20 15:53:27 +08:00
parent e0624cfe44
commit 64cc46b7f3
14 changed files with 348 additions and 320 deletions

9
src/common/IPCChannel.ts Normal file
View File

@ -0,0 +1,9 @@
export const CHANNEL_SEND_MSG = "llonebot_send_msg"
export const CHANNEL_RECALL_MSG = "llonebot_recall_msg"
export const CHANNEL_GET_CONFIG = "llonebot_get_config"
export const CHANNEL_SET_CONFIG = "llonebot_set_config"
export const CHANNEL_START_HTTP_SERVER = "llonebot_start_http_server"
export const CHANNEL_UPDATE_GROUPS = "llonebot_update_groups"
export const CHANNEL_UPDATE_FRIENDS = "llonebot_update_friends"
export const CHANNEL_LOG = "llonebot_log"
export const CHANNEL_POST_ONEBOT_DATA = "llonebot_post_onebot_data"

4
src/global.d.ts vendored
View File

@ -1,4 +1,4 @@
import {Config, Group, GroupMemberInfo, MessageElement, Peer, PostDataSendMsg, SendMessage, User} from "./types";
import {Config, Group, GroupMemberInfo, MessageElement, Peer, PostDataSendMsg, SendMessage, User} from "./common/types";
declare var LLAPI: {
@ -9,7 +9,7 @@ declare var LLAPI: {
uin: string // 一串加密的字符串
}>
// uid是一串加密的字符串, 收到群消息的时候可以用此函数获取群成员的qq号
// uid是一串加密的字符串
getUserInfo(uid: string): Promise<User>;
sendMessage(peer: Peer, message: SendMessage[]): Promise<any>;
recallMessage(peer: Peer, msgIds: string[]): Promise<void>;

View File

@ -1,266 +0,0 @@
// 运行在 Electron 主进程 下的插件入口
import * as path from "path";
const fs = require('fs');
import {ipcMain, webContents} from 'electron';
const express = require("express");
import {Config, Group, PostDataSendMsg, User} from "./types";
const CHANNEL_SEND_MSG = "llonebot_sendMsg"
const CHANNEL_RECALL_MSG = "llonebot_recallMsg"
let groups: Group[] = []
let friends: User[] = []
function sendIPCMsg(channel: string, data: any) {
let contents = webContents.getAllWebContents();
for (const content of contents) {
try {
content.send(channel, data)
} catch (e) {
}
}
}
function sendIPCCallSendQQMsg(postData: PostDataSendMsg) {
sendIPCMsg(CHANNEL_SEND_MSG, postData);
}
function log(msg: any) {
let currentDateTime = new Date().toLocaleString();
fs.appendFile("./llonebot.log", currentDateTime + ":" + msg + "\n", (err: any) => {
})
}
function startExpress(event: any, port: number) {
// const original_send = (window.webContents.__qqntim_original_object && window.webContents.__qqntim_original_object.send) || window.webContents.send;
const app = express();
// 中间件用于解析POST请求的请求体
app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.get('/', (req: any, res: any) => {
res.send('llonebot已启动');
})
function handlePost(jsonData: any) {
let resData = {
status: 0,
retcode: 0,
data: {},
message: ''
}
if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") {
sendIPCCallSendQQMsg(jsonData);
} else if (jsonData.action == "get_group_list") {
resData["data"] = groups.map(group => {
return {
group_id: group.uid,
group_name: group.name,
group_members: group.members.map(member => {
return {
user_id: member.uin,
user_name: member.cardName || member.nick,
user_display_name: member.cardName || member.nick
}
})
}
})
} else if (jsonData.action == "get_group_member_list") {
let group = groups.find(group => group.uid == jsonData.params.group_id)
if (group) {
resData["data"] = group?.members?.map(member => {
let role = "member"
switch (member.role) {
case 4: {
role = "owner"
break;
}
case 3: {
role = "admin"
break
}
case 2: {
role = "member"
break
}
}
return {
user_id: member.uin,
user_name: member.nick,
user_display_name: member.cardName || member.nick,
nickname: member.nick,
card: member.cardName,
role
}
}) || []
} else {
resData["data"] = []
}
} else if (jsonData.action == "get_friend_list") {
resData["data"] = friends.map(friend => {
return {
user_id: friend.uin,
user_name: friend.nickName,
}
})
} else if (jsonData.action == "delete_msg") {
sendIPCMsg(CHANNEL_RECALL_MSG, jsonData as { message_id: string })
}
return resData
}
// 处理POST请求的路由
app.post('/', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
let resData = handlePost(jsonData)
res.send(resData)
});
app.post('/send_private_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
jsonData.action = "send_private_msg"
let resData = handlePost(jsonData)
res.send(resData)
})
app.post('/send_group_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
jsonData.action = "send_group_msg"
let resData = handlePost(jsonData)
res.send(resData)
})
app.post('/send_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
if (jsonData.message_type == "private") {
jsonData.action = "send_private_msg"
} else if (jsonData.message_type == "group") {
jsonData.action = "send_group_msg"
} else {
if (jsonData.params.group_id) {
jsonData.action = "send_group_msg"
} else {
jsonData.action = "send_private_msg"
}
}
let resData = handlePost(jsonData)
res.send(resData)
})
app.post('/delete_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
jsonData.action = "delete_msg"
let resData = handlePost(jsonData)
res.send(resData)
})
app.listen(port, "0.0.0.0", () => {
console.log(`服务器已启动,监听端口 ${port}`);
});
}
// 加载插件时触发
function onLoad(plugin: any) {
function getConfig(): Config{
if (!fs.existsSync(configFilePath)) {
return {"port":3000, "host": "http://localhost:5000/"}
} else {
const data = fs.readFileSync(configFilePath, "utf-8");
return JSON.parse(data);
}
}
ipcMain.on("startExpress", (event: any, arg: any) => {
startExpress(event, getConfig().port)
})
ipcMain.on("updateGroups", (event: any, arg: Group[]) => {
for (const group of arg) {
let existGroup = groups.find(g => g.uid == group.uid)
if (existGroup) {
if (!existGroup.members) {
existGroup.members = []
}
existGroup.name = group.name
for (const member of group.members || []) {
let existMember = existGroup.members?.find(m => m.uin == member.uin)
if (existMember) {
existMember.nick = member.nick
existMember.cardName = member.cardName
} else {
existGroup.members?.push(member)
}
}
} else {
groups.push(group)
}
}
groups = arg
})
ipcMain.on("updateFriends", (event: any, arg: User[]) => {
friends = arg
})
ipcMain.on("postOnebotData", (event: any, arg: any) => {
// try {
// // const fetch2 = require("./electron-fetch");
// }catch (e) {
// log(e)
// }
log("开始post新消息事件到服务器")
try {
fetch(getConfig().host, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(arg)
}).then((res: any) => {
log("新消息事件上传");
}, (err: any) => {
log("新消息事件上传失败:" + err + JSON.stringify(arg));
});
} catch (e: any) {
log(e.toString())
}
})
ipcMain.on("llonebot_log", (event: any, arg: any) => {
log(arg)
})
if (!fs.existsSync(plugin.path.data)) {
fs.mkdirSync(plugin.path.data, {recursive: true});
}
const configFilePath = path.join(plugin.path.data, "config.json")
ipcMain.handle("llonebot_getConfig", (event: any, arg: any) => {
return getConfig()
})
ipcMain.on("llonebot_setConfig", (event: any, arg: Config) => {
fs.writeFileSync(configFilePath, JSON.stringify(arg, null, 2), "utf-8")
})
}
// 创建窗口时触发
function onBrowserWindowCreated(window: any, plugin: any) {
}
// 这两个函数都是可选的
// module.exports = {
// onLoad,
// onBrowserWindowCreated
// }
// function onLoad(plugin: any) {
//
// }
export {
onLoad, onBrowserWindowCreated
}

132
src/main/HttpServer.ts Normal file
View File

@ -0,0 +1,132 @@
import {sendIPCRecallQQMsg, sendIPCSendQQMsg} from "./IPCSend";
const express = require("express");
import {PostDataSendMsg} from "../common/types";
import {friends, groups} from "./data";
function handlePost(jsonData: any) {
let resData = {
status: 0,
retcode: 0,
data: {},
message: ''
}
if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") {
sendIPCSendQQMsg(jsonData);
} else if (jsonData.action == "get_group_list") {
resData["data"] = groups.map(group => {
return {
group_id: group.uid,
group_name: group.name,
group_members: group.members.map(member => {
return {
user_id: member.uin,
user_name: member.cardName || member.nick,
user_display_name: member.cardName || member.nick
}
})
}
})
} else if (jsonData.action == "get_group_member_list") {
let group = groups.find(group => group.uid == jsonData.params.group_id)
if (group) {
resData["data"] = group?.members?.map(member => {
let role = "member"
switch (member.role) {
case 4: {
role = "owner"
break;
}
case 3: {
role = "admin"
break
}
case 2: {
role = "member"
break
}
}
return {
user_id: member.uin,
user_name: member.nick,
user_display_name: member.cardName || member.nick,
nickname: member.nick,
card: member.cardName,
role
}
}) || []
} else {
resData["data"] = []
}
} else if (jsonData.action == "get_friend_list") {
resData["data"] = friends.map(friend => {
return {
user_id: friend.uin,
user_name: friend.nickName,
}
})
} else if (jsonData.action == "delete_msg") {
sendIPCRecallQQMsg(jsonData.message_id)
}
return resData
}
export function startExpress(port: number) {
const app = express();
// 中间件用于解析POST请求的请求体
app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.get('/', (req: any, res: any) => {
res.send('llonebot已启动');
})
// 处理POST请求的路由
app.post('/', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
let resData = handlePost(jsonData)
res.send(resData)
});
app.post('/send_private_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
jsonData.action = "send_private_msg"
let resData = handlePost(jsonData)
res.send(resData)
})
app.post('/send_group_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
jsonData.action = "send_group_msg"
let resData = handlePost(jsonData)
res.send(resData)
})
app.post('/send_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
if (jsonData.message_type == "private") {
jsonData.action = "send_private_msg"
} else if (jsonData.message_type == "group") {
jsonData.action = "send_group_msg"
} else {
if (jsonData.params.group_id) {
jsonData.action = "send_group_msg"
} else {
jsonData.action = "send_private_msg"
}
}
let resData = handlePost(jsonData)
res.send(resData)
})
app.post('/delete_msg', (req: any, res: any) => {
let jsonData: PostDataSendMsg = req.body;
jsonData.action = "delete_msg"
let resData = handlePost(jsonData)
res.send(resData)
})
app.listen(port, "0.0.0.0", () => {
console.log(`服务器已启动,监听端口 ${port}`);
});
}

22
src/main/IPCSend.ts Normal file
View File

@ -0,0 +1,22 @@
import {webContents} from 'electron';
import {PostDataSendMsg} from "../common/types";
import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG} from "../common/IPCChannel";
function sendIPCMsg(channel: string, data: any) {
let contents = webContents.getAllWebContents();
for (const content of contents) {
try {
content.send(channel, data)
} catch (e) {
}
}
}
export function sendIPCSendQQMsg(postData: PostDataSendMsg) {
sendIPCMsg(CHANNEL_SEND_MSG, postData);
}
export function sendIPCRecallQQMsg(message_id: string) {
sendIPCMsg(CHANNEL_RECALL_MSG, {message_id});
}

20
src/main/config.ts Normal file
View File

@ -0,0 +1,20 @@
import {Config} from "../common/types";
const fs = require("fs")
export class ConfigUtil{
configPath: string;
constructor(configPath: string) {
this.configPath = configPath;
}
getConfig(): Config{
if (!fs.existsSync(this.configPath)) {
return {"port":3000, "host": "http://localhost:5000/"}
} else {
const data = fs.readFileSync(this.configPath, "utf-8");
return JSON.parse(data);
}
}
}

4
src/main/data.ts Normal file
View File

@ -0,0 +1,4 @@
import {Group, User} from "../common/types";
export let groups: Group[] = []
export let friends: User[] = []

107
src/main/main.ts Normal file
View File

@ -0,0 +1,107 @@
// 运行在 Electron 主进程 下的插件入口
import * as path from "path";
const fs = require('fs');
import {ipcMain} from 'electron';
import {Config, Group, User} from "../common/types";
import {
CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_POST_ONEBOT_DATA,
CHANNEL_SET_CONFIG,
CHANNEL_START_HTTP_SERVER, CHANNEL_UPDATE_FRIENDS,
CHANNEL_UPDATE_GROUPS
} from "../common/IPCChannel";
import {ConfigUtil} from "./config";
import {startExpress} from "./HttpServer";
import {log} from "./utils";
import {friends, groups} from "./data";
// 加载插件时触发
function onLoad(plugin: any) {
const configFilePath = path.join(plugin.path.data, "config.json")
let configUtil = new ConfigUtil(configFilePath)
if (!fs.existsSync(plugin.path.data)) {
fs.mkdirSync(plugin.path.data, {recursive: true});
}
ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => {
return configUtil.getConfig()
})
ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
fs.writeFileSync(configFilePath, JSON.stringify(arg, null, 2), "utf-8")
})
ipcMain.on(CHANNEL_START_HTTP_SERVER, (event: any, arg: any) => {
startExpress(configUtil.getConfig().port)
})
ipcMain.on(CHANNEL_UPDATE_GROUPS, (event: any, arg: Group[]) => {
for (const group of arg) {
let existGroup = groups.find(g => g.uid == group.uid)
if (existGroup) {
if (!existGroup.members) {
existGroup.members = []
}
existGroup.name = group.name
for (const member of group.members || []) {
let existMember = existGroup.members?.find(m => m.uin == member.uin)
if (existMember) {
existMember.nick = member.nick
existMember.cardName = member.cardName
} else {
existGroup.members?.push(member)
}
}
} else {
groups.push(group)
}
}
groups.length = 0
groups.push(...arg)
})
ipcMain.on(CHANNEL_UPDATE_FRIENDS, (event: any, arg: User[]) => {
friends.length = 0
friends.push(...arg)
})
ipcMain.on(CHANNEL_POST_ONEBOT_DATA, (event: any, arg: any) => {
try {
fetch(configUtil.getConfig().host, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(arg)
}).then((res: any) => {
log("新消息事件上传");
}, (err: any) => {
log("新消息事件上传失败:" + err + JSON.stringify(arg));
});
} catch (e: any) {
log(e.toString())
}
})
ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
log(arg)
})
}
// 创建窗口时触发
function onBrowserWindowCreated(window: any, plugin: any) {
}
// 这两个函数都是可选的
export {
onLoad, onBrowserWindowCreated
}

8
src/main/utils.ts Normal file
View File

@ -0,0 +1,8 @@
const fs = require('fs');
export function log(msg: any) {
let currentDateTime = new Date().toLocaleString();
fs.appendFile("./llonebot.log", currentDateTime + ":" + msg + "\n", (err: any) => {
})
}

View File

@ -1,9 +1,13 @@
// Electron 主进程 与 渲染进程 交互的桥梁
import {Config, Group, PostDataSendMsg, User} from "./types";
// type Group = import( "./types").Group;
// type PostDataSendMsg = import( "./types").PostDataSendMsg;
// type User = import( "./types").User;
import {Config, Group, PostDataSendMsg, User} from "./common/types";
import {
CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_POST_ONEBOT_DATA,
CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG,
CHANNEL_SET_CONFIG,
CHANNEL_START_HTTP_SERVER, CHANNEL_UPDATE_FRIENDS, CHANNEL_UPDATE_GROUPS
} from "./common/IPCChannel";
const {contextBridge} = require("electron");
const {ipcRenderer} = require('electron');
@ -12,35 +16,35 @@ const {ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld("llonebot", {
postData: (data: any) => {
ipcRenderer.send("postOnebotData", data);
ipcRenderer.send(CHANNEL_POST_ONEBOT_DATA, data);
},
updateGroups: (groups: Group[]) => {
ipcRenderer.send("updateGroups", groups);
ipcRenderer.send(CHANNEL_UPDATE_GROUPS, groups);
},
updateFriends: (friends: User[]) => {
ipcRenderer.send("updateFriends", friends);
ipcRenderer.send(CHANNEL_UPDATE_FRIENDS, friends);
},
listenSendMessage: (handle: (jsonData: PostDataSendMsg) => void) => {
ipcRenderer.on("llonebot_sendMsg", (event: any, args: PostDataSendMsg) => {
ipcRenderer.on(CHANNEL_SEND_MSG, (event: any, args: PostDataSendMsg) => {
handle(args)
})
},
listenRecallMessage: (handle: (jsonData: {message_id: string}) => void) => {
ipcRenderer.on("llonebot_recallMsg", (event: any, args: {message_id: string}) => {
ipcRenderer.on(CHANNEL_RECALL_MSG, (event: any, args: {message_id: string}) => {
handle(args)
})
},
startExpress: () => {
ipcRenderer.send("startExpress");
ipcRenderer.send(CHANNEL_START_HTTP_SERVER);
},
log: (data: any) => {
ipcRenderer.send("log", data);
ipcRenderer.send(CHANNEL_LOG, data);
},
setConfig: (config: Config)=>{
ipcRenderer.send("llonebot_setConfig", config);
ipcRenderer.send(CHANNEL_SET_CONFIG, config);
},
getConfig: async () => {
return ipcRenderer.invoke("llonebot_getConfig");
return ipcRenderer.invoke(CHANNEL_GET_CONFIG);
}
// startExpress,
});

View File

@ -2,10 +2,7 @@
// import express from "express";
// const { ipcRenderer } = require('electron');
import {AtType, Group, MessageElement, Peer, PostDataSendMsg, User} from "./types";
const host = "http://localhost:5000"
import {AtType, Group, MessageElement, Peer, PostDataSendMsg, User} from "./common/types";
let self_qq: string = ""
let groups: Group[] = []
@ -85,8 +82,8 @@ async function getGroupMember(group_qq: string, member_uid: string) {
}
async function forwardMessage(message: MessageElement) {
try {
async function handleNewMessage(messages: MessageElement[]) {
for (let message of messages) {
let onebot_message_data: any = {
self: {
platform: "qq",
@ -118,42 +115,33 @@ async function forwardMessage(message: MessageElement) {
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
message_data["data"]["file"] = 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
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
}
onebot_message_data.message.push(message_data)
} 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
message_data["data"]["file"] = 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);
} catch (e) {
console.log("上传消息事件失败", e)
}
}
async function handleNewMessage(messages: MessageElement[]) {
for (let message of messages) {
console.log("new message raw", message)
forwardMessage(message).then();
}
}

View File

@ -12,6 +12,6 @@
"include": ["src/*"],
"exclude": [
"node_modules",
"src/types.ts"
"src/common/types.ts"
]
}

View File

@ -2,7 +2,7 @@ const baseConfig = require('./webpack.base.config.js')
baseConfig.target = 'electron-main'
baseConfig.entry = {
main: './src/main.ts',
main: './src/main/main.ts',
// preload: './src/preload.ts',
}
baseConfig.output.libraryTarget = 'commonjs2'