diff --git a/src/common/server/http.ts b/src/common/server/http.ts new file mode 100644 index 00000000..1a57d55e --- /dev/null +++ b/src/common/server/http.ts @@ -0,0 +1,133 @@ +import express, { Express, Request, Response } from 'express'; +import cors from 'cors'; +import http from 'http'; +import { LogWrapper } from '../utils/log'; +type RegisterHandler = (res: Response, payload: any) => Promise + +export abstract class HttpServerBase { + name: string = 'NapCatQQ'; + private readonly expressAPP: Express; + private _server: http.Server | null = null; + logger: LogWrapper; + serverToken: string | undefined = undefined; + + public get server(): http.Server | null { + return this._server; + } + + private set server(value: http.Server | null) { + this._server = value; + } + + constructor(logger: LogWrapper, token: string) { + this.serverToken = token; + this.logger = logger; + this.expressAPP = express(); + this.expressAPP.use(cors()); + this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' })); + this.expressAPP.use((req, res, next) => { + // 兼容处理没有带content-type的请求 + // log("req.headers['content-type']", req.headers['content-type']) + req.headers['content-type'] = 'application/json'; + const originalJson = express.json({ limit: '5000mb' }); + // 调用原始的express.json()处理器 + originalJson(req, res, (err) => { + if (err) { + this.logger.logError('Error parsing JSON:', err); + return res.status(400).send('Invalid JSON'); + } + next(); + }); + }); + } + + authorize(req: Request, res: Response, next: () => void) { + let clientToken = ''; + const authHeader = req.get('authorization'); + if (authHeader) { + clientToken = authHeader.split('Bearer ').pop() || ''; + //logDebug('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(); + } + //logDebug('receive http url token', clientToken); + } + + if (this.serverToken && clientToken != this.serverToken) { + return res.status(403).send(JSON.stringify({ message: 'token verify failed!' })); + } + next(); + } + + start(port: number, host: string) { + try { + this.expressAPP.get('/', (req: Request, res: Response) => { + res.send(`${this.name}已启动`); + }); + this.listen(port, host); + } catch (e: any) { + this.logger.logError('HTTP服务启动失败', e.toString()); + // httpServerError = "HTTP服务启动失败, " + e.toString() + } + } + + stop() { + // httpServerError = "" + if (this.server) { + this.server.close(); + this.server = null; + } + } + + restart(port: number, host: string) { + this.stop(); + this.start(port, host); + } + + abstract handleFailed(res: Response, payload: any, err: Error): void + + registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) { + if (!url.startsWith('/')) { + url = '/' + url; + } + + // @ts-expect-error wait fix + if (!this.expressAPP[method]) { + const err = `${this.name} register router failed,${method} not exist`; + this.logger.logError(err); + throw err; + } + // @ts-expect-error wait fix + this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => { + let payload = req.body; + if (method == 'get') { + payload = req.query; + } else if (req.query) { + payload = { ...req.query, ...req.body }; + } + this.logger.logDebug('收到http请求', url, payload); + try { + res.send(await handler(res, payload)); + } catch (e: any) { + this.handleFailed(res, payload, e); + } + }); + } + + protected listen(port: number, host: string = '0.0.0.0') { + host = host || '0.0.0.0'; + try { + this.server = this.expressAPP.listen(port, host, () => { + const info = `${this.name} started ${host}:${port}`; + this.logger.log(info); + }).on('error', (err) => { + this.logger.logError('HTTP服务启动失败', err.toString()); + }); + } catch (e: any) { + this.logger.logError('HTTP服务启动失败, 请检查监听的ip地址和端口', e.stack.toString()); + } + } +} diff --git a/src/common/server/websocket.ts b/src/common/server/websocket.ts new file mode 100644 index 00000000..161ad09a --- /dev/null +++ b/src/common/server/websocket.ts @@ -0,0 +1,129 @@ +import { WebSocket, WebSocketServer } from 'ws'; +import http from 'http'; +import urlParse from 'url'; +import { IncomingMessage } from 'node:http'; +import { LogWrapper } from '../utils/log'; + +class WebsocketClientBase { + private wsClient: WebSocket | undefined; + + constructor() { + } + + send(msg: string) { + if (this.wsClient && this.wsClient.readyState == WebSocket.OPEN) { + this.wsClient.send(msg); + } + } + + onMessage(msg: string) { + + } +} + +export class WebsocketServerBase { + private ws: WebSocketServer | null = null; + public token: string = ''; + logger: LogWrapper; + + constructor(logger:LogWrapper) { + this.logger = logger; + } + + start(port: number | http.Server, host: string = '') { + if (port instanceof http.Server) { + try { + const wss = new WebSocketServer({ + noServer: true, + maxPayload: 1024 * 1024 * 1024 + }).on('error', () => { + }); + this.ws = wss; + port.on('upgrade', function upgrade(request, socket, head) { + wss.handleUpgrade(request, socket, head, function done(ws) { + wss.emit('connection', ws, request); + }); + }); + this.logger.log('ws服务启动成功, 绑定到HTTP服务'); + } catch (e: any) { + throw Error('ws服务启动失败, 可能是绑定的HTTP服务异常' + e.toString()); + } + } else { + try { + this.ws = new WebSocketServer({ + port, + host: '', + maxPayload: 1024 * 1024 * 1024 + }).on('error', () => { + }); + this.logger.log(`ws服务启动成功, ${host}:${port}`); + } catch (e: any) { + throw Error('ws服务启动失败, 请检查监听的ip和端口' + e.toString()); + } + } + this.ws.on('connection', (wsClient, req) => { + const url: string = req.url!.split('?').shift() || '/'; + this.authorize(wsClient, req); + this.onConnect(wsClient, url, req); + wsClient.on('message', async (msg) => { + this.onMessage(wsClient, url, msg.toString()); + }); + }); + } + + stop() { + if (this.ws) { + this.ws.close((err) => { + if (err) this.logger.log('ws server close failed!', err); + }); + this.ws = null; + } + } + + restart(port: number) { + this.stop(); + this.start(port); + } + + authorize(wsClient: WebSocket, req: IncomingMessage) { + const url = req.url!.split('?').shift(); + this.logger.log('ws connect', url); + let clientToken: string = ''; + const authHeader = req.headers['authorization']; + if (authHeader) { + clientToken = authHeader.split('Bearer ').pop() || ''; + this.logger.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)) { + clientToken = urlToken[0]; + } else { + clientToken = urlToken; + } + this.logger.log('receive ws url token', clientToken); + } + } + if (this.token && clientToken != this.token) { + this.authorizeFailed(wsClient); + return wsClient.close(); + } + } + + authorizeFailed(wsClient: WebSocket) { + + } + + onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { + + } + + onMessage(wsClient: WebSocket, url: string, msg: string) { + + } + + sendHeart() { + + } +} diff --git a/src/onebot/action/go-cqhttp/QuickAction.ts b/src/onebot/action/go-cqhttp/QuickAction.ts index 33204175..5dc5df00 100644 --- a/src/onebot/action/go-cqhttp/QuickAction.ts +++ b/src/onebot/action/go-cqhttp/QuickAction.ts @@ -1,6 +1,6 @@ import BaseAction from '../BaseAction'; import { ActionName } from '../types'; -import { QuickAction, QuickActionEvent, handleQuickOperation } from '@/onebot11/server/postOB11Event'; +import { QuickAction, QuickActionEvent, handleQuickOperation } from '@/onebot/server/postOB11Event'; interface Payload{ context: QuickActionEvent,