LLOneBot/src/main/main.ts
2024-03-07 10:43:17 +08:00

433 lines
18 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.

// 运行在 Electron 主进程 下的插件入口
import {BrowserWindow, dialog, ipcMain} from 'electron';
import * as fs from 'node:fs';
import {Config} from "../common/types";
import {
CHANNEL_ERROR,
CHANNEL_GET_CONFIG,
CHANNEL_LOG,
CHANNEL_SELECT_FILE,
CHANNEL_SET_CONFIG,
} from "../common/channels";
import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
import {checkFfmpeg, DATA_DIR, getConfigUtil, log} from "../common/utils";
import {
friendRequests, getFriend,
getGroup,
getGroupMember,
groupNotifies,
llonebotError, refreshGroupMembers,
selfInfo
} from "../common/data";
import {hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook";
import {OB11Constructor} from "../onebot11/constructor";
import {NTQQApi} from "../ntqqapi/ntcall";
import {
ChatType,
FriendRequestNotify,
GroupMember,
GroupNotifies,
GroupNotifyTypes,
RawMessage
} from "../ntqqapi/types";
import {ob11HTTPServer} from "../onebot11/server/http";
import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent";
import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent";
import {postOB11Event} from "../onebot11/server/postOB11Event";
import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket";
import {OB11GroupAdminNoticeEvent} from "../onebot11/event/notice/OB11GroupAdminNoticeEvent";
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupRequestEvent} from "../onebot11/event/request/OB11GroupRequest";
import {OB11FriendRequestEvent} from "../onebot11/event/request/OB11FriendRequest";
import * as path from "node:path";
import {dbUtil} from "../common/db";
let running = false;
// 加载插件时触发
function onLoad() {
log("llonebot main onLoad");
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => {
const selectPath = new Promise<string>((resolve, reject) => {
dialog
.showOpenDialog({
title: "请选择ffmpeg",
properties: ["openFile"],
buttonLabel: "确定",
})
.then((result) => {
log("选择文件", result);
if (!result.canceled) {
const _selectPath = path.join(result.filePaths[0]);
resolve(_selectPath);
// let config = getConfigUtil().getConfig()
// config.ffmpeg = path.join(result.filePaths[0]);
// getConfigUtil().setConfig(config);
}
resolve("")
})
.catch((err) => {
reject(err);
});
})
try {
return await selectPath;
} catch (e) {
log("选择文件出错", e)
return ""
}
})
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, {recursive: true});
}
ipcMain.handle(CHANNEL_ERROR, (event, arg) => {
return llonebotError;
})
ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => {
const config = getConfigUtil().getConfig()
return config;
})
ipcMain.on(CHANNEL_SET_CONFIG, (event, arg: Config) => {
let oldConfig = getConfigUtil().getConfig();
getConfigUtil().setConfig(arg)
if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) {
ob11HTTPServer.restart(arg.ob11.httpPort);
}
// 判断是否启用或关闭HTTP服务
if (!arg.ob11.enableHttp) {
ob11HTTPServer.stop();
} else {
ob11HTTPServer.start(arg.ob11.httpPort);
}
// 正向ws端口变化重启服务
if (arg.ob11.wsPort != oldConfig.ob11.wsPort) {
ob11WebsocketServer.restart(arg.ob11.wsPort);
}
// 判断是否启用或关闭正向ws
if (arg.ob11.enableWs != oldConfig.ob11.enableWs) {
if (arg.ob11.enableWs) {
ob11WebsocketServer.start(arg.ob11.wsPort);
} else {
ob11WebsocketServer.stop();
}
}
// 判断是否启用或关闭反向ws
if (arg.ob11.enableWsReverse != oldConfig.ob11.enableWsReverse) {
if (arg.ob11.enableWsReverse) {
ob11ReverseWebsockets.start();
} else {
ob11ReverseWebsockets.stop();
}
}
if (arg.ob11.enableWsReverse) {
// 判断反向ws地址有变化
if (arg.ob11.wsHosts.length != oldConfig.ob11.wsHosts.length) {
ob11ReverseWebsockets.restart();
} else {
for (const newHost of arg.ob11.wsHosts) {
if (!oldConfig.ob11.wsHosts.includes(newHost)) {
ob11ReverseWebsockets.restart();
break;
}
}
}
}
// 检查ffmpeg
if (arg.ffmpeg) {
checkFfmpeg(arg.ffmpeg).then(success => {
if (success) {
llonebotError.ffmpegError = ''
}
})
}
})
ipcMain.on(CHANNEL_LOG, (event, arg) => {
log(arg);
})
async function postReceiveMsg(msgList: RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig();
for (let message of msgList) {
// log("收到新消息", message)
message.msgShortId = await dbUtil.addMsg(message)
OB11Constructor.message(message).then((msg) => {
if (debug) {
msg.raw = message;
}
const isSelfMsg = msg.user_id.toString() == selfInfo.uin
if (isSelfMsg && !reportSelfMessage) {
return
}
postOB11Event(msg);
// log("post msg", msg)
}).catch(e => log("constructMessage error: ", e.stack.toString()));
}
}
async function startReceiveHook() {
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, async (payload) => {
try {
await postReceiveMsg(payload.msgList);
} catch (e) {
log("report message error: ", e.stack.toString());
}
})
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
for (const message of payload.msgList) {
// log("message update", message.sendStatus, message)
if (message.recallTime != "0") {
// 撤回消息上报
const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
if (!oriMessage) {
continue
}
oriMessage.recallTime = message.recallTime
dbUtil.updateMsg(oriMessage).then();
if (message.chatType == ChatType.friend) {
const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message.senderUin), oriMessage.msgShortId);
postOB11Event(friendRecallEvent);
} else if (message.chatType == ChatType.group) {
let operatorId = message.senderUin
for (const element of message.elements) {
const operatorUid = element.grayTipElement?.revokeElement.operatorUid
const operator = await getGroupMember(message.peerUin, null, operatorUid)
operatorId = operator.uin
}
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
parseInt(message.peerUin),
parseInt(message.senderUin),
parseInt(operatorId),
oriMessage.msgShortId
)
postOB11Event(groupRecallEvent);
}
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
continue
}
dbUtil.addMsg(message).then();
}
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, async (payload) => {
const {reportSelfMessage} = getConfigUtil().getConfig();
if (!reportSelfMessage) {
return
}
// log("reportSelfMessage", payload)
try {
await postReceiveMsg([payload.msgRecord]);
} catch (e) {
log("report self message error: ", e.stack.toString());
}
})
registerReceiveHook<{
"doubt": boolean,
"oldestUnreadSeq": string,
"unreadCount": number
}>(ReceiveCmd.UNREAD_GROUP_NOTIFY, async (payload) => {
if (payload.unreadCount) {
// log("开始获取群通知详情")
let notify: GroupNotifies;
try {
notify = await NTQQApi.getGroupNotifies();
} catch (e) {
// log("获取群通知详情失败", e);
return
}
const notifies = notify.notifies.slice(0, payload.unreadCount)
// log("获取群通知详情完成", notifies, payload);
for (const notify of notifies) {
try {
notify.time = Date.now();
const notifyTime = parseInt(notify.seq) / 1000
// log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
// if (notifyTime < startTime) {
// continue;
// }
let existNotify = groupNotifies[notify.seq];
if (existNotify) {
if (Date.now() - existNotify.time < 3000) {
continue
}
}
log("收到群通知", notify);
groupNotifies[notify.seq] = notify;
// let member2: GroupMember;
// if (notify.user2.uid) {
// member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
// }
if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET].includes(notify.type)) {
const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
log("有管理员变动通知");
refreshGroupMembers(notify.group.groupCode).then()
let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent()
groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode);
log("开始获取变动的管理员")
if (member1) {
log("变动管理员获取成功")
groupAdminNoticeEvent.user_id = parseInt(member1.uin);
groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? "unset" : "set";
postOB11Event(groupAdminNoticeEvent, true);
} else {
log("获取群通知的成员信息失败", notify, getGroup(notify.group.groupCode));
}
} else if (notify.type == GroupNotifyTypes.MEMBER_EXIT) {
// log("有成员退出通知");
// const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
// let groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(notify.group.groupCode), parseInt(member1.uin))
// postEvent(groupDecreaseEvent, true);
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
log("有加群请求");
let groupRequestEvent = new OB11GroupRequestEvent();
groupRequestEvent.group_id = parseInt(notify.group.groupCode);
let requestQQ = ""
try {
requestQQ = (await NTQQApi.getUserDetailInfo(notify.user1.uid)).uin;
} catch (e) {
log("获取加群人QQ号失败", e)
}
groupRequestEvent.user_id = parseInt(requestQQ) || 0;
groupRequestEvent.sub_type = "add"
groupRequestEvent.comment = notify.postscript;
groupRequestEvent.flag = notify.seq;
postOB11Event(groupRequestEvent);
} else if (notify.type == GroupNotifyTypes.INVITE_ME) {
log("收到邀请我加群通知")
let groupInviteEvent = new OB11GroupRequestEvent();
groupInviteEvent.group_id = parseInt(notify.group.groupCode);
let user_id = (await getFriend("", notify.user2.uid))?.uin
if (!user_id){
user_id = (await NTQQApi.getUserDetailInfo(notify.user2.uid))?.uin
}
groupInviteEvent.user_id = parseInt(user_id);
groupInviteEvent.sub_type = "invite";
groupInviteEvent.flag = notify.seq;
postOB11Event(groupInviteEvent);
}
} catch (e) {
log("解析群通知失败", e.stack.toString());
}
}
} else if (payload.doubt) {
// 可能有群管理员变动
}
})
registerReceiveHook<FriendRequestNotify>(ReceiveCmd.FRIEND_REQUEST, async (payload) => {
for (const req of payload.data.buddyReqs) {
if (req.isUnread && !friendRequests[req.sourceId] && (parseInt(req.reqTime) > startTime / 1000)) {
friendRequests[req.sourceId] = req;
log("有新的好友请求", req);
let friendRequestEvent = new OB11FriendRequestEvent();
try {
let requester = await NTQQApi.getUserDetailInfo(req.friendUid)
friendRequestEvent.user_id = parseInt(requester.uin);
} catch (e) {
log("获取加好友者QQ号失败", e);
}
friendRequestEvent.flag = req.sourceId.toString();
friendRequestEvent.comment = req.extWords;
postOB11Event(friendRequestEvent);
}
}
})
}
let startTime = 0;
async function start() {
startTime = Date.now();
startReceiveHook().then();
NTQQApi.getGroups(true).then()
const config = getConfigUtil().getConfig()
// 检查ffmpeg
checkFfmpeg(config.ffmpeg).then(exist => {
if (!exist) {
llonebotError.ffmpegError = `没有找到ffmpeg,音频只能发送wav和silk`
}
})
if (config.ob11.enableHttp) {
try {
ob11HTTPServer.start(config.ob11.httpPort)
} catch (e) {
log("http server start failed", e);
}
}
if (config.ob11.enableWs) {
ob11WebsocketServer.start(config.ob11.wsPort);
}
if (config.ob11.enableWsReverse) {
ob11ReverseWebsockets.start();
}
log("LLOneBot start")
}
let getSelfNickCount = 0;
const init = async () => {
try {
log("start get self info")
const _ = await NTQQApi.getSelfInfo();
log("get self info api result:", _);
Object.assign(selfInfo, _);
selfInfo.nick = selfInfo.uin;
} catch (e) {
log("retry get self info", e);
}
log("self info", selfInfo);
if (selfInfo.uin) {
try {
const userInfo = (await NTQQApi.getUserDetailInfo(selfInfo.uid));
log("self info", userInfo);
if (userInfo) {
selfInfo.nick = userInfo.nick;
} else {
getSelfNickCount++;
if (getSelfNickCount < 10) {
return setTimeout(init, 1000);
}
}
} catch (e) {
log("get self nickname failed", e.toString());
return setTimeout(init, 1000);
}
start().then();
} else {
setTimeout(init, 1000)
}
}
setTimeout(init, 1000);
}
// 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) {
try {
hookNTQQApiCall(window);
hookNTQQApiReceive(window);
} catch (e) {
log("LLOneBot hook error: ", e.toString())
}
}
try {
onLoad();
} catch (e: any) {
console.log(e.toString())
}
// 这两个函数都是可选的
export {
onBrowserWindowCreated
}