refactor: base server & setting ui

This commit is contained in:
linyuchen 2024-02-20 03:25:16 +08:00
parent 5094ba724a
commit c1dd309b21
31 changed files with 368 additions and 281 deletions

View File

@ -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"

View File

@ -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];
}
}
}

View File

@ -88,4 +88,3 @@ export function getStrangerByUin(uin: string) {
}
export const version = "v3.4.0"
export const heartInterval = 15000 // 毫秒

View File

@ -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

View File

@ -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];
}
}
});
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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;

View File

@ -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<any>) {
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))
}

View File

@ -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))
}
}

View File

@ -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 = `
<setting-item data-direction="row" class="hostItem vertical-list-item">
<h2>(http)</h2>
<setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}">
<h2>HTTP事件上报地(http)</h2>
<input class="httpHost input-text" type="text" value="${host}"
style="width:60%;padding: 5px"
placeholder="如果localhost上报失败试试局域网ip"/>
</setting-item>
`
return eleStr
}
function createWsHostEleStr(host: string) {
let eleStr = `
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="hostItem vertical-list-item ${reverseWSClass}">
<h2>(websocket)</h2>
<input class="wsHost input-text" type="text" value="${host}"
style="width:60%;padding: 5px"
placeholder="如果localhost上报失败试试局域网ip"/>
</setting-item>
`
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) {
<setting-section>
<setting-panel>
<setting-list class="wrap">
<setting-item class="vertical-list-item" data-direction="row">
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>HTTP服务</div>
</div>
<setting-switch id="http" ${config.ob11.enableHttp ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item class="vertical-list-item ${httpClass}" data-direction="row" style="display: ${config.ob11.enableHttp ? '' : 'none'}">
<setting-text>HTTP监听端口</setting-text>
<input id="httpPort" type="number" value="${config.httpPort}"/>
<input id="httpPort" type="number" value="${config.ob11.httpPort}"/>
</setting-item>
<div>
<button id="addHttpHost" class="q-button">HTTP POST上报地址</button>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>HTTP事件上报</div>
</div>
<setting-switch id="httpPost" ${config.ob11.enableHttpPost ? "is-active" : ""}></setting-switch>
</setting-item>
<div class="${httpPostClass}" style="display: ${config.ob11.enableHttpPost ? '' : 'none'}">
<div >
<button id="addHttpHost" class="q-button">HTTP POST上报地址</button>
</div>
<div id="httpHostItems">
${httpHostsEleStr}
</div>
</div>
<div id="httpHostItems">
${httpHostsEleStr}
</div>
<setting-item class="vertical-list-item" data-direction="row">
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>Websocket协议</div>
</div>
<setting-switch id="websocket" ${config.ob11.enableWs ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item class="vertical-list-item ${wsClass}" data-direction="row" style="display: ${config.ob11.enableWs ? '' : 'none'}">
<setting-text>Websocket监听端口</setting-text>
<input id="wsPort" type="number" value="${config.wsPort}"/>
<input id="wsPort" type="number" value="${config.ob11.wsPort}"/>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>Websocket协议</div>
</div>
<setting-switch id="websocketReverse" ${config.ob11.enableWsReverse ? "is-active" : ""}></setting-switch>
</setting-item>
<div class="${reverseWSClass}" style="display: ${config.ob11.enableWsReverse ? '' : 'none'}">
<div>
<button id="addWsHost" class="q-button">Websocket上报地址</button>
</div>
<div id="wsHostItems">
${wsHostsEleStr}
</div>
</div>
<setting-item class="vertical-list-item" data-direction="row">
<setting-text>Access Token</setting-text>
<input id="token" type="text" placeholder="可为空" value="${config.token}"/>
</setting-item>
<div>
<button id="addWsHost" class="q-button">Websocket上报地址</button>
</div>
<div id="wsHostItems">
${wsHostsEleStr}
</div>
<button id="save" class="q-button"></button>
</setting-list>
</setting-panel>
<setting-panel>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>HTTP支持</div>
<div class="tips">QQ生效</div>
</div>
<setting-switch id="http" ${config.enableHttp ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>HTTP POST支持</div>
<div class="tips">QQ生效</div>
</div>
<setting-switch id="httpPost" ${config.enableHttpPost ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>Websocket支持</div>
<div class="tips">QQ生效</div>
</div>
<setting-switch id="websocket" ${config.enableWs ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>Websocket支持</div>
<div class="tips">QQ生效</div>
</div>
<setting-switch id="websocketReverse" ${config.enableWsReverse ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div>base64编码</div>
@ -128,7 +130,7 @@ async function onSettingWindowCreated(view: Element) {
<setting-item data-direction="row" class="hostItem vertical-list-item">
<div>
<div></div>
<div class="tips">日志目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
<div class="tips">目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
</div>
<setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch>
</setting-item>
@ -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("保存成功");
})

6
src/server/base.ts Normal file
View File

@ -0,0 +1,6 @@
export abstract class ServerBase{
abstract start: () => void
abstract restart: ()=>void
}

106
src/server/http.ts Normal file
View File

@ -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<any>
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);
});
}
}