From c1dd309b2106deaef106e105043a66c847b6ab55 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Tue, 20 Feb 2024 03:25:16 +0800 Subject: [PATCH] refactor: base server & setting ui --- package.json | 3 +- src/common/config.ts | 56 +++-- src/common/data.ts | 1 - src/common/types.ts | 7 +- src/common/utils.ts | 19 ++ src/main/main.ts | 34 ++-- .../{actions => action}/BaseAction.ts | 0 .../{actions => action}/CanSendImage.ts | 0 .../{actions => action}/CanSendRecord.ts | 0 src/onebot11/{actions => action}/DeleteMsg.ts | 0 .../{actions => action}/GetFriendList.ts | 0 .../{actions => action}/GetGroupInfo.ts | 0 .../{actions => action}/GetGroupList.ts | 0 .../{actions => action}/GetGroupMemberInfo.ts | 0 .../{actions => action}/GetGroupMemberList.ts | 0 .../{actions => action}/GetLoginInfo.ts | 0 src/onebot11/{actions => action}/GetMsg.ts | 0 src/onebot11/{actions => action}/GetStatus.ts | 0 .../{actions => action}/GetVersionInfo.ts | 0 .../{actions => action}/SendGroupMsg.ts | 0 src/onebot11/{actions => action}/SendMsg.ts | 35 ++++ .../{actions => action}/SendPrivateMsg.ts | 0 src/onebot11/{actions => action}/index.ts | 0 src/onebot11/{actions => action}/types.ts | 0 src/onebot11/{actions => action}/utils.ts | 0 .../notice/OB11GroupRecallNoticeEvent.ts | 1 - src/onebot11/server.ts | 191 +++--------------- src/onebot11/server/http.ts | 26 +++ src/renderer.ts | 164 ++++++++------- src/server/base.ts | 6 + src/server/http.ts | 106 ++++++++++ 31 files changed, 368 insertions(+), 281 deletions(-) rename src/onebot11/{actions => action}/BaseAction.ts (100%) rename src/onebot11/{actions => action}/CanSendImage.ts (100%) rename src/onebot11/{actions => action}/CanSendRecord.ts (100%) rename src/onebot11/{actions => action}/DeleteMsg.ts (100%) rename src/onebot11/{actions => action}/GetFriendList.ts (100%) rename src/onebot11/{actions => action}/GetGroupInfo.ts (100%) rename src/onebot11/{actions => action}/GetGroupList.ts (100%) rename src/onebot11/{actions => action}/GetGroupMemberInfo.ts (100%) rename src/onebot11/{actions => action}/GetGroupMemberList.ts (100%) rename src/onebot11/{actions => action}/GetLoginInfo.ts (100%) rename src/onebot11/{actions => action}/GetMsg.ts (100%) rename src/onebot11/{actions => action}/GetStatus.ts (100%) rename src/onebot11/{actions => action}/GetVersionInfo.ts (100%) rename src/onebot11/{actions => action}/SendGroupMsg.ts (100%) rename src/onebot11/{actions => action}/SendMsg.ts (84%) rename src/onebot11/{actions => action}/SendPrivateMsg.ts (100%) rename src/onebot11/{actions => action}/index.ts (100%) rename src/onebot11/{actions => action}/types.ts (100%) rename src/onebot11/{actions => action}/utils.ts (100%) create mode 100644 src/onebot11/server/http.ts create mode 100644 src/server/base.ts create mode 100644 src/server/http.ts diff --git a/package.json b/package.json index 10a0c76..667e78e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "dist/main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "postinstall": "set ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save", + "postinstall": "ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save", "build": "npm run build-main && npm run build-preload && npm run build-renderer", "build-main": "webpack --config webpack.main.config.js", "build-preload": "webpack --config webpack.preload.config.js", @@ -19,7 +19,6 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", - "express-ws": "^5.0.2", "json-bigint": "^1.0.0", "uuid": "^9.0.1", "ws": "^8.16.0" diff --git a/src/common/config.ts b/src/common/config.ts index 762a04b..12050b7 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -1,4 +1,5 @@ -import {Config} from "./types"; +import {Config, OB11Config} from "./types"; +import {mergeNewProperties} from "./utils"; const fs = require("fs"); @@ -8,13 +9,25 @@ export class ConfigUtil { constructor(configPath: string) { this.configPath = configPath; } - - getConfig(): Config { - let defaultConfig: Config = { + getConfig(){ + try { + return this._getConfig() + }catch (e) { + console.log("获取配置文件出错", e) + } + } + _getConfig(): Config { + let ob11Default: OB11Config = { httpPort: 3000, httpHosts: [], wsPort: 3001, wsHosts: [], + enableWs: true, + enableWsReverse: true + } + let defaultConfig: Config = { + ob11: ob11Default, + heartInterval: 5000, token: "", enableBase64: false, debug: false, @@ -28,28 +41,29 @@ export class ConfigUtil { let jsonData: Config = defaultConfig; try { jsonData = JSON.parse(data) + } catch (e) { } - catch (e) {} - if (!jsonData.httpHosts) { - jsonData.httpHosts = [] - } - if (!jsonData.wsHosts) { - jsonData.wsHosts = [] - } - if (!jsonData.wsPort) { - jsonData.wsPort = 3001 - } - if (!jsonData.httpPort) { - jsonData.httpPort = 3000 - } - if (!jsonData.token) { - jsonData.token = "" - } + mergeNewProperties(defaultConfig, jsonData); + this.checkOldConfig(jsonData.ob11, jsonData, "httpPort", "port"); + this.checkOldConfig(jsonData.ob11, jsonData, "httpHosts", "hosts"); + this.checkOldConfig(jsonData.ob11, jsonData, "wsPort", "wsPort"); + console.log("get config", jsonData); return jsonData; } } - + setConfig(config: Config) { fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8") } + + private checkOldConfig(currentConfig: Config | OB11Config, + oldConfig: Config | OB11Config, + currentKey: string, oldKey: string) { + // 迁移旧的配置到新配置,避免用户重新填写配置 + const oldValue = oldConfig[oldKey]; + if (oldValue) { + currentConfig[currentKey] = oldValue; + delete oldConfig[oldKey]; + } + } } diff --git a/src/common/data.ts b/src/common/data.ts index ce94b06..e070a41 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -88,4 +88,3 @@ export function getStrangerByUin(uin: string) { } export const version = "v3.4.0" -export const heartInterval = 15000 // 毫秒 \ No newline at end of file diff --git a/src/common/types.ts b/src/common/types.ts index abc2b88..1952099 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,4 +1,4 @@ -export interface Config { +export interface OB11Config { httpPort: number httpHosts: string[] wsPort: number @@ -7,7 +7,12 @@ export interface Config { enableHttpPost?: boolean enableWs?: boolean enableWsReverse?: boolean +} + +export interface Config { + ob11: OB11Config token?: string + heartInterval?: number // ms enableBase64?: boolean debug?: boolean reportSelfMessage?: boolean diff --git a/src/common/utils.ts b/src/common/utils.ts index 2a1f67d..43c77fe 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -97,3 +97,22 @@ export async function file2base64(path: string){ } return result; } + + +// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 +export function mergeNewProperties(newObj: any, oldObj: any) { + Object.keys(newObj).forEach(key => { + // 如果老对象不存在当前属性,则直接复制 + if (!oldObj.hasOwnProperty(key)) { + oldObj[key] = newObj[key]; + } else { + // 如果老对象和新对象的当前属性都是对象,则递归合并 + if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') { + mergeNewProperties(newObj[key], oldObj[key]); + } else if(typeof oldObj[key] === 'object' || typeof newObj[key] === 'object'){ + // 属性冲突,有一方不是对象,直接覆盖 + oldObj[key] = newObj[key]; + } + } + }); +} \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 009b7aa..ad8be04 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -3,7 +3,7 @@ import {BrowserWindow, ipcMain} from 'electron'; import {Config} from "../common/types"; -import {postMsg, setToken, startHTTPServer, initWebsocket} from "../onebot11/server"; +import {postMsg, initWebsocket} from "../onebot11/server"; import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "../common/channels"; import {CONFIG_DIR, getConfigUtil, log} from "../common/utils"; import {addHistoryMsg, getGroupMember, msgHistory, selfInfo} from "../common/data"; @@ -13,6 +13,7 @@ import {NTQQApi} from "../ntqqapi/ntcall"; import {ChatType, RawMessage} from "../ntqqapi/types"; import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent"; import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent"; +import {ob11HTTPServer} from "../onebot11/server/http"; const fs = require('fs'); @@ -29,19 +30,26 @@ function onLoad() { fs.mkdirSync(CONFIG_DIR, {recursive: true}); } ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => { - return getConfigUtil().getConfig(); + try { + return getConfigUtil().getConfig(); + }catch (e) { + console.log("获取配置文件出错", e) + } }) ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => { let oldConfig = getConfigUtil().getConfig(); getConfigUtil().setConfig(arg) - if (arg.httpPort != oldConfig.httpPort) { - startHTTPServer(arg.httpPort) + if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) { + ob11HTTPServer.restart(arg.ob11.httpPort); } - if (arg.wsPort != oldConfig.wsPort) { - initWebsocket(arg.wsPort) + if (!arg.ob11.enableHttp){ + ob11HTTPServer.stop() } - if (arg.token != oldConfig.token) { - setToken(arg.token); + else{ + ob11HTTPServer.start(arg.ob11.httpPort); + } + if (arg.ob11.wsPort != oldConfig.ob11.wsPort) { + initWebsocket(arg.ob11.wsPort) } }) @@ -125,11 +133,13 @@ function onLoad() { } }) NTQQApi.getGroups(true).then() - const config = getConfigUtil().getConfig() - startHTTPServer(config.httpPort) - initWebsocket(config.wsPort); - setToken(config.token) + try { + ob11HTTPServer.start(config.ob11.httpPort) + initWebsocket(config.ob11.wsPort); + }catch (e) { + console.log("start failed", e) + } log("LLOneBot start") } diff --git a/src/onebot11/actions/BaseAction.ts b/src/onebot11/action/BaseAction.ts similarity index 100% rename from src/onebot11/actions/BaseAction.ts rename to src/onebot11/action/BaseAction.ts diff --git a/src/onebot11/actions/CanSendImage.ts b/src/onebot11/action/CanSendImage.ts similarity index 100% rename from src/onebot11/actions/CanSendImage.ts rename to src/onebot11/action/CanSendImage.ts diff --git a/src/onebot11/actions/CanSendRecord.ts b/src/onebot11/action/CanSendRecord.ts similarity index 100% rename from src/onebot11/actions/CanSendRecord.ts rename to src/onebot11/action/CanSendRecord.ts diff --git a/src/onebot11/actions/DeleteMsg.ts b/src/onebot11/action/DeleteMsg.ts similarity index 100% rename from src/onebot11/actions/DeleteMsg.ts rename to src/onebot11/action/DeleteMsg.ts diff --git a/src/onebot11/actions/GetFriendList.ts b/src/onebot11/action/GetFriendList.ts similarity index 100% rename from src/onebot11/actions/GetFriendList.ts rename to src/onebot11/action/GetFriendList.ts diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/action/GetGroupInfo.ts similarity index 100% rename from src/onebot11/actions/GetGroupInfo.ts rename to src/onebot11/action/GetGroupInfo.ts diff --git a/src/onebot11/actions/GetGroupList.ts b/src/onebot11/action/GetGroupList.ts similarity index 100% rename from src/onebot11/actions/GetGroupList.ts rename to src/onebot11/action/GetGroupList.ts diff --git a/src/onebot11/actions/GetGroupMemberInfo.ts b/src/onebot11/action/GetGroupMemberInfo.ts similarity index 100% rename from src/onebot11/actions/GetGroupMemberInfo.ts rename to src/onebot11/action/GetGroupMemberInfo.ts diff --git a/src/onebot11/actions/GetGroupMemberList.ts b/src/onebot11/action/GetGroupMemberList.ts similarity index 100% rename from src/onebot11/actions/GetGroupMemberList.ts rename to src/onebot11/action/GetGroupMemberList.ts diff --git a/src/onebot11/actions/GetLoginInfo.ts b/src/onebot11/action/GetLoginInfo.ts similarity index 100% rename from src/onebot11/actions/GetLoginInfo.ts rename to src/onebot11/action/GetLoginInfo.ts diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/action/GetMsg.ts similarity index 100% rename from src/onebot11/actions/GetMsg.ts rename to src/onebot11/action/GetMsg.ts diff --git a/src/onebot11/actions/GetStatus.ts b/src/onebot11/action/GetStatus.ts similarity index 100% rename from src/onebot11/actions/GetStatus.ts rename to src/onebot11/action/GetStatus.ts diff --git a/src/onebot11/actions/GetVersionInfo.ts b/src/onebot11/action/GetVersionInfo.ts similarity index 100% rename from src/onebot11/actions/GetVersionInfo.ts rename to src/onebot11/action/GetVersionInfo.ts diff --git a/src/onebot11/actions/SendGroupMsg.ts b/src/onebot11/action/SendGroupMsg.ts similarity index 100% rename from src/onebot11/actions/SendGroupMsg.ts rename to src/onebot11/action/SendGroupMsg.ts diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/action/SendMsg.ts similarity index 84% rename from src/onebot11/actions/SendMsg.ts rename to src/onebot11/action/SendMsg.ts index cc84d23..e96ab0a 100644 --- a/src/onebot11/actions/SendMsg.ts +++ b/src/onebot11/action/SendMsg.ts @@ -9,6 +9,41 @@ import BaseAction from "./BaseAction"; import {ActionName} from "./types"; import * as fs from "fs"; +function checkSendMessage(sendMsgList: OB11MessageData[]) { + function checkUri(uri: string): boolean { + const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/; + return pattern.test(uri); + } + + for (let msg of sendMsgList) { + if (msg["type"] && msg["data"]) { + let type = msg["type"]; + let data = msg["data"]; + if (type === "text" && !data["text"]) { + return 400; + } else if (["image", "voice", "record"].includes(type)) { + if (!data["file"]) { + return 400; + } else { + if (checkUri(data["file"])) { + return 200; + } else { + return 400; + } + } + + } else if (type === "at" && !data["qq"]) { + return 400; + } else if (type === "reply" && !data["id"]) { + return 400; + } + } else { + return 400 + } + } + return 200; +} + export interface ReturnDataType { message_id: number } diff --git a/src/onebot11/actions/SendPrivateMsg.ts b/src/onebot11/action/SendPrivateMsg.ts similarity index 100% rename from src/onebot11/actions/SendPrivateMsg.ts rename to src/onebot11/action/SendPrivateMsg.ts diff --git a/src/onebot11/actions/index.ts b/src/onebot11/action/index.ts similarity index 100% rename from src/onebot11/actions/index.ts rename to src/onebot11/action/index.ts diff --git a/src/onebot11/actions/types.ts b/src/onebot11/action/types.ts similarity index 100% rename from src/onebot11/actions/types.ts rename to src/onebot11/action/types.ts diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/action/utils.ts similarity index 100% rename from src/onebot11/actions/utils.ts rename to src/onebot11/action/utils.ts diff --git a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts index 83c31d5..11c180a 100644 --- a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts +++ b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts @@ -7,7 +7,6 @@ export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent { constructor(groupId: number, userId: number, operatorId: number, messageId: number) { super(); - this.group_id = groupId; this.user_id = userId; this.operator_id = operatorId; diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts index b2a2a7c..df87476 100644 --- a/src/onebot11/server.ts +++ b/src/onebot11/server.ts @@ -1,134 +1,24 @@ -import * as http from "http"; import * as websocket from "ws"; import urlParse from "url"; -import express, {Request, Response} from "express"; import {getConfigUtil, log} from "../common/utils"; -import {heartInterval, selfInfo} from "../common/data"; -import {OB11Message, OB11MessageData, OB11Return} from './types'; -import {actionHandlers, actionMap} from "./actions"; -import {OB11Response, OB11WebsocketResponse} from "./actions/utils"; +import {selfInfo} from "../common/data"; +import {OB11Message} from './types'; +import {actionMap} from "./action"; +import {OB11WebsocketResponse} from "./action/utils"; import {callEvent, registerEventSender, unregisterEventSender} from "./event/manager"; import {ReconnectingWebsocket} from "./ReconnectingWebsocket"; -import {ActionName} from "./actions/types"; +import {ActionName} from "./action/types"; import {OB11BaseMetaEvent} from "./event/meta/OB11BaseMetaEvent"; import {OB11BaseNoticeEvent} from "./event/notice/OB11BaseNoticeEvent"; -import BaseAction from "./actions/BaseAction"; +import BaseAction from "./action/BaseAction"; import {LifeCycleSubType, OB11LifeCycleEvent} from "./event/meta/OB11LifeCycleEvent"; import {OB11HeartbeatEvent} from "./event/meta/OB11HeartbeatEvent"; -let accessToken = ""; let heartbeatRunning = false; - -// @SiberianHusky 2021-08-15 - -function checkSendMessage(sendMsgList: OB11MessageData[]) { - function checkUri(uri: string): boolean { - const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/; - return pattern.test(uri); - } - - for (let msg of sendMsgList) { - if (msg["type"] && msg["data"]) { - let type = msg["type"]; - let data = msg["data"]; - if (type === "text" && !data["text"]) { - return 400; - } else if (["image", "voice", "record"].includes(type)) { - if (!data["file"]) { - return 400; - } else { - if (checkUri(data["file"])) { - return 200; - } else { - return 400; - } - } - - } else if (type === "at" && !data["qq"]) { - return 400; - } else if (type === "reply" && !data["id"]) { - return 400; - } - } else { - return 400 - } - } - return 200; -} - -// ==end== - -const JSONbig = require('json-bigint')({storeAsString: true}); - -const expressAPP = express(); -expressAPP.use(express.urlencoded({extended: true, limit: "500mb"})); - -let httpServer: http.Server = null; - let websocketServer = null; -expressAPP.use((req, res, next) => { - let data = ''; - req.on('data', chunk => { - data += chunk.toString(); - }); - req.on('end', () => { - if (data) { - try { - // log("receive raw", data) - req.body = JSONbig.parse(data); - } catch (e) { - return next(e); - } - } - next(); - }); -}); - -const expressAuthorize = (req: Request, res: Response, next: () => void) => { - let token = "" - const authHeader = req.get("authorization") - if (authHeader) { - token = authHeader.split("Bearer ").pop() - log("receive http header token", token) - } else if (req.query.access_token) { - if (Array.isArray(req.query.access_token)) { - token = req.query.access_token[0].toString(); - } else { - token = req.query.access_token.toString(); - } - log("receive http url token", token) - } - - if (accessToken) { - if (token != accessToken) { - return res.status(403).send(JSON.stringify({message: 'token verify failed!'})); - } - } - next(); - -}; - -export function setToken(token: string) { - accessToken = token -} - -export function startHTTPServer(port: number) { - if (httpServer) { - httpServer.close(); - } - expressAPP.get('/', (req: Request, res: Response) => { - res.send('LLOneBot已启动'); - }) - - if (getConfigUtil().getConfig().enableHttp) { - httpServer = expressAPP.listen(port, "0.0.0.0", () => { - console.log(`llonebot http service started 0.0.0.0:${port}`); - }); - } -} - export function initWebsocket(port: number) { + const {heartInterval, ob11: {enableWs}, token} = getConfigUtil().getConfig() if (!heartbeatRunning) { setInterval(() => { callEvent(new OB11HeartbeatEvent(true, true, heartInterval)); @@ -136,8 +26,7 @@ export function initWebsocket(port: number) { heartbeatRunning = true; } - - if (getConfigUtil().getConfig().enableWs) { + if (enableWs) { if (websocketServer) { websocketServer.close((err) => { log("ws server close failed!", err) @@ -150,28 +39,26 @@ export function initWebsocket(port: number) { websocketServer.on("connection", (ws, req) => { const url = req.url.split("?").shift(); log("receive ws connect", url) - let token: string = "" + let clientToken: string = "" const authHeader = req.headers['authorization']; if (authHeader) { - token = authHeader.split("Bearer ").pop() - log("receive ws header token", token); + clientToken = authHeader.split("Bearer ").pop() + log("receive ws header token", clientToken); } else { const parsedUrl = urlParse.parse(req.url, true); const urlToken = parsedUrl.query.access_token; if (urlToken) { if (Array.isArray(urlToken)) { - token = urlToken[0] + clientToken = urlToken[0] } else { - token = urlToken + clientToken = urlToken } - log("receive ws url token", token); + log("receive ws url token", clientToken); } } - if (accessToken) { - if (token != accessToken) { - ws.send(JSON.stringify(OB11WebsocketResponse.res(null, "failed", 1403, "token验证失败"))) - return ws.close() - } + if (token && clientToken != token) { + ws.send(JSON.stringify(OB11WebsocketResponse.res(null, "failed", 1403, "token验证失败"))) + return ws.close() } if (url == "/api" || url == "/api/" || url == "/") { @@ -204,7 +91,7 @@ export function initWebsocket(port: number) { try { wsReply(ws, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) - } catch (e){ + } catch (e) { log("发送生命周期失败", e) } @@ -218,11 +105,12 @@ export function initWebsocket(port: number) { initReverseWebsocket(); } + function initReverseWebsocket() { const config = getConfigUtil().getConfig(); - if (config.enableWsReverse) { + if (config.ob11.enableWsReverse) { console.log("Prepare to connect all reverse websockets..."); - for (const url of config.wsHosts) { + for (const url of config.ob11.wsHosts) { new Promise(() => { try { let wsClient = new ReconnectingWebsocket(url); @@ -239,7 +127,7 @@ function initReverseWebsocket() { wsClient.onmessage = async function (msg) { let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}} let echo = "" - log("收到正向Websocket消息", msg.toString()) + log("收到反向Websocket消息", msg.toString()) try { receiveData = JSON.parse(msg.toString()) echo = receiveData.echo @@ -257,8 +145,7 @@ function initReverseWebsocket() { wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo)) } } - } - catch (e) { + } catch (e) { log(e.stack); } }).then(); @@ -292,7 +179,7 @@ export function postMsg(msg: PostMsgType) { return } } - for (const host of config.httpHosts) { + for (const host of config.ob11.httpHosts) { fetch(host, { method: "POST", headers: { @@ -310,33 +197,3 @@ export function postMsg(msg: PostMsgType) { log("新消息事件ws上报", msg); callEvent(msg); } - - -function registerRouter(action: string, handle: (payload: any) => Promise) { - let url = action.toString() - if (!action.startsWith("/")) { - url = "/" + action - } - - async function _handle(res: Response, payload: any) { - log("receive post data", url, payload) - try { - const result = await handle(payload) - res.send(result) - } catch (e) { - log(e.stack); - res.send(OB11Response.error(e.stack.toString(), 200)) - } - } - - expressAPP.post(url, expressAuthorize, (req: Request, res: Response) => { - _handle(res, req.body || {}).then() - }); - expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => { - _handle(res, req.query as any || {}).then() - }); -} - -for (const action of actionHandlers) { - registerRouter(action.actionName, (payload) => action.handle(payload)) -} \ No newline at end of file diff --git a/src/onebot11/server/http.ts b/src/onebot11/server/http.ts new file mode 100644 index 0000000..b7408a2 --- /dev/null +++ b/src/onebot11/server/http.ts @@ -0,0 +1,26 @@ +import {Response} from "express"; +import {getConfigUtil} from "../../common/utils"; +import {OB11Response} from "../action/utils"; +import {HttpServerBase} from "../../server/http"; +import {actionHandlers} from "../action"; + +class OB11HTTPServer extends HttpServerBase { + name = "OneBot V11 server" + handleFailed(res: Response, payload: any, e: any) { + res.send(OB11Response.error(e.stack.toString(), 200)) + } + + protected listen(port: number) { + if (getConfigUtil().getConfig().ob11.enableHttp) { + super.listen(port); + } + } +} + +export const ob11HTTPServer = new OB11HTTPServer(); + +for (const action of actionHandlers) { + for(const method of ["post", "get"]){ + ob11HTTPServer.registerRouter(method, action.actionName, (res, payload) => action.handle(payload)) + } +} diff --git a/src/renderer.ts b/src/renderer.ts index e673b44..aa70f15 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -5,40 +5,42 @@ async function onSettingWindowCreated(view: Element) { window.llonebot.log("setting window created"); let config = await window.llonebot.getConfig() + const httpClass = "http"; + const httpPostClass = "http-post"; + const wsClass = "ws"; + const reverseWSClass = "reverse-ws"; function createHttpHostEleStr(host: string) { let eleStr = ` - -

事件上报地址(http)

+ +

HTTP事件上报地址(http)

- ` return eleStr } function createWsHostEleStr(host: string) { let eleStr = ` - +

事件上报地址(反向websocket)

- ` return eleStr } let httpHostsEleStr = "" - for (const host of config.httpHosts) { + for (const host of config.ob11.httpHosts) { httpHostsEleStr += createHttpHostEleStr(host); } let wsHostsEleStr = "" - for (const host of config.wsHosts) { + for (const host of config.ob11.wsHosts) { wsHostsEleStr += createWsHostEleStr(host); } @@ -47,63 +49,63 @@ async function onSettingWindowCreated(view: Element) { - + +
+
启用HTTP服务
+
+ +
+ HTTP监听端口 - + -
- + +
+
启用HTTP事件上报
+
+ +
+
+
+ +
+
+ ${httpHostsEleStr} +
-
- ${httpHostsEleStr} -
- - + +
+
启用正向Websocket协议
+
+ +
+ 正向Websocket监听端口 - + + + +
+
启用反向Websocket协议
+
+ +
+
+
+ +
+
+ ${wsHostsEleStr} +
+
Access Token -
- -
-
- ${wsHostsEleStr} -
- -
-
启用HTTP支持
-
修改后须重启QQ生效
-
- -
- -
-
启用HTTP POST支持
-
修改后须重启QQ生效
-
- -
- -
-
启用正向Websocket支持
-
修改后须重启QQ生效
-
- -
- -
-
启用反向Websocket支持
-
修改后须重启QQ生效
-
- -
上报文件进行base64编码
@@ -128,7 +130,7 @@ async function onSettingWindowCreated(view: Element) {
日志
-
日志目录:${window.LiteLoader.plugins["LLOneBot"].path.data}
+
目录:${window.LiteLoader.plugins["LLOneBot"].path.data}
@@ -160,8 +162,7 @@ async function onSettingWindowCreated(view: Element) { let addressDoc = parser.parseFromString(createWsHostEleStr(initValue), "text/html"); addressEle = addressDoc.querySelector("setting-item") hostItemsEle = document.getElementById("wsHostItems"); - } - else { + } else { let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html"); addressEle = addressDoc.querySelector("setting-item") hostItemsEle = document.getElementById("httpHostItems"); @@ -174,24 +175,38 @@ async function onSettingWindowCreated(view: Element) { doc.getElementById("addHttpHost").addEventListener("click", () => addHostEle("http")) doc.getElementById("addWsHost").addEventListener("click", () => addHostEle("ws")) - function switchClick(eleId: string, configKey: string) { + function switchClick(eleId: string, configKey: string, _config=null) { + if (!_config){ + _config = config + } doc.getElementById(eleId)?.addEventListener("click", (e) => { const switchEle = e.target as HTMLInputElement - if (config[configKey]) { - config[configKey] = false + if (_config[configKey]) { + _config[configKey] = false switchEle.removeAttribute("is-active") } else { - config[configKey] = true + _config[configKey] = true switchEle.setAttribute("is-active", "") } + // 妈蛋,手动操作DOM越写越麻烦,要不用vue算了 + const keyClassMap = { + "enableHttp": httpClass, + "enableHttpPost": httpPostClass, + "enableWs": wsClass, + "enableWsReverse": reverseWSClass, + } + for (let e of document.getElementsByClassName(keyClassMap[configKey])) { + e["style"].display = _config[configKey] ? "" : "none" + } + window.llonebot.setConfig(config) }) } - switchClick("http", "enableHttp"); - switchClick("httpPost", "enableHttpPost"); - switchClick("websocket", "enableWs"); - switchClick("websocketReverse", "enableWsReverse"); + switchClick("http", "enableHttp", config.ob11); + switchClick("httpPost", "enableHttpPost", config.ob11); + switchClick("websocket", "enableWs", config.ob11); + switchClick("websocketReverse", "enableWsReverse", config.ob11); switchClick("debug", "debug"); switchClick("switchBase64", "enableBase64"); switchClick("reportSelfMessage", "reportSelfMessage"); @@ -210,27 +225,24 @@ async function onSettingWindowCreated(view: Element) { let httpHosts: string[] = []; for (const hostEle of httpHostEles) { - if (hostEle.value) { - httpHosts.push(hostEle.value); - } + const value = hostEle.value.trim(); + value && httpHosts.push(value); } - const wsPort = wsPortEle.value - const token = tokenEle.value + const wsPort = wsPortEle.value; + const token = tokenEle.value.trim(); let wsHosts: string[] = []; for (const hostEle of wsHostEles) { - if (hostEle.value) { - wsHosts.push(hostEle.value); - } + const value = hostEle.value.trim(); + value && wsHosts.push(value); } - - config.httpPort = parseInt(httpPort); - config.httpHosts = httpHosts; - config.wsPort = parseInt(wsPort); - config.wsHosts = wsHosts; - config.token = token.trim(); + config.ob11.httpPort = parseInt(httpPort); + config.ob11.httpHosts = httpHosts; + config.ob11.wsPort = parseInt(wsPort); + config.ob11.wsHosts = wsHosts; + config.token = token; window.llonebot.setConfig(config); alert("保存成功"); }) diff --git a/src/server/base.ts b/src/server/base.ts new file mode 100644 index 0000000..7291840 --- /dev/null +++ b/src/server/base.ts @@ -0,0 +1,6 @@ + + +export abstract class ServerBase{ + abstract start: () => void + abstract restart: ()=>void +} \ No newline at end of file diff --git a/src/server/http.ts b/src/server/http.ts new file mode 100644 index 0000000..6163140 --- /dev/null +++ b/src/server/http.ts @@ -0,0 +1,106 @@ +import express, {Express, Request, Response} from "express"; +import {getConfigUtil, log} from "../common/utils"; +import http from "http"; + +const JSONbig = require('json-bigint')({storeAsString: true}); + +type RegisterHandler = (res: Response, payload: any) => Promise + +export abstract class HttpServerBase { + name: string = "LLOneBot"; + private readonly expressAPP: Express; + private server: http.Server = null; + + constructor() { + this.expressAPP = express(); + this.expressAPP.use(express.urlencoded({extended: true, limit: "500mb"})); + this.expressAPP.use((req, res, next) => { + let data = ''; + req.on('data', chunk => { + data += chunk.toString(); + }); + req.on('end', () => { + if (data) { + try { + // log("receive raw", data) + req.body = JSONbig.parse(data); + } catch (e) { + return next(e); + } + } + next(); + }); + }); + } + + authorize(req: Request, res: Response, next: () => void) { + let serverToken = getConfigUtil().getConfig().token; + let clientToken = "" + const authHeader = req.get("authorization") + if (authHeader) { + clientToken = authHeader.split("Bearer ").pop() + log("receive http header token", clientToken) + } else if (req.query.access_token) { + if (Array.isArray(req.query.access_token)) { + clientToken = req.query.access_token[0].toString(); + } else { + clientToken = req.query.access_token.toString(); + } + log("receive http url token", clientToken) + } + + if (serverToken && clientToken != serverToken) { + return res.status(403).send(JSON.stringify({message: 'token verify failed!'})); + } + next(); + }; + + start(port: number) { + this.expressAPP.get('/', (req: Request, res: Response) => { + res.send(`${this.name}已启动`); + }) + this.listen(port); + } + + stop() { + if (this.server){ + this.server.close() + this.server = null; + } + } + + restart(port: number){ + this.stop() + this.start(port) + } + + abstract handleFailed(res: Response, payload: any, err: any): void + + registerRouter(method: string, url: string, handler: RegisterHandler) { + if (!url.startsWith("/")) { + url = "/" + url + } + const methodFunc = this.expressAPP[method] + if (!methodFunc){ + const err = `${this.name} register router failed,${method} not exist`; + log(err); + throw err; + } + this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => { + const payload = req.body || req.query || {} + try{ + res.send(await handler(res, payload)) + }catch (e) { + this.handleFailed(res, payload, e.stack.toString()) + } + }); + } + + protected listen(port: number) { + this.server = this.expressAPP.listen(port, "0.0.0.0", () => { + const info = `${this.name} started 0.0.0.0:${port}` + console.log(info); + log(info); + }); + } +} \ No newline at end of file