mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Merge branch 'v3.4.0' into dev
# Conflicts: # src/common/utils.ts # src/global.d.ts # src/main/ipcsend.ts # src/main/main.ts # src/ntqqapi/hook.ts # src/onebot11/action/SendMsg.ts # src/onebot11/action/TestForwdMsg.ts # src/onebot11/action/types.ts # src/onebot11/server.ts # src/preload.ts
This commit is contained in:
commit
ed48a76c33
@ -22,7 +22,7 @@ LiteLoaderQQNT的OneBot11协议插件
|
|||||||
- [x] http调用api
|
- [x] http调用api
|
||||||
- [x] http事件上报
|
- [x] http事件上报
|
||||||
- [x] 正向websocket
|
- [x] 正向websocket
|
||||||
- [ ] 反向websocket
|
- [x] 反向websocket
|
||||||
|
|
||||||
主要功能:
|
主要功能:
|
||||||
- [x] 发送好友消息
|
- [x] 发送好友消息
|
||||||
@ -108,7 +108,7 @@ LiteLoaderQQNT的OneBot11协议插件
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
|
- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
|
||||||
- [x] 支持正向websocket
|
- [x] 支持正、反向websocket(感谢@disymayufei的PR)
|
||||||
- [ ] 转发消息记录
|
- [ ] 转发消息记录
|
||||||
- [ ] 好友点赞api
|
- [ ] 好友点赞api
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"name": "LLOneBot",
|
"name": "LLOneBot",
|
||||||
"slug": "LLOneBot",
|
"slug": "LLOneBot",
|
||||||
"description": "LiteLoaderQQNT的OneBotApi",
|
"description": "LiteLoaderQQNT的OneBotApi",
|
||||||
"version": "3.3.1",
|
"version": "3.4.0",
|
||||||
"thumbnail": "./icon.png",
|
"thumbnail": "./icon.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
38
package-lock.json
generated
38
package-lock.json
generated
@ -11,8 +11,10 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-ws": "^5.0.2",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1",
|
||||||
|
"ws": "^8.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.23.2",
|
"@babel/preset-env": "^7.23.2",
|
||||||
@ -3134,6 +3136,40 @@
|
|||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-ws": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/express-ws/-/express-ws-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^7.4.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.0.0 || ^5.0.0-alpha.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-ws/node_modules/ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"postinstall": "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": "npm run build-main && npm run build-preload && npm run build-renderer",
|
||||||
"build-main": "webpack --config webpack.main.config.js",
|
"build-main": "webpack --config webpack.main.config.js",
|
||||||
"build-preload": "webpack --config webpack.preload.config.js",
|
"build-preload": "webpack --config webpack.preload.config.js",
|
||||||
@ -20,7 +20,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1",
|
||||||
|
"ws": "^8.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.23.2",
|
"@babel/preset-env": "^7.23.2",
|
||||||
|
@ -1,50 +1,79 @@
|
|||||||
import { Config } from "./types";
|
import fs from "fs";
|
||||||
|
import {Config, OB11Config} from "./types";
|
||||||
const fs = require("fs")
|
import {mergeNewProperties} from "./utils";
|
||||||
|
|
||||||
export class ConfigUtil {
|
export class ConfigUtil {
|
||||||
configPath: string;
|
private readonly configPath: string;
|
||||||
|
private config: Config | null = null;
|
||||||
|
|
||||||
constructor(configPath: string) {
|
constructor(configPath: string) {
|
||||||
this.configPath = configPath;
|
this.configPath = configPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig(): Config {
|
getConfig(cache=true) {
|
||||||
let defaultConfig: Config = {
|
if (this.config && cache) {
|
||||||
port: 3000,
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.reloadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadConfig(): Config {
|
||||||
|
let ob11Default: OB11Config = {
|
||||||
|
httpPort: 3000,
|
||||||
|
httpHosts: [],
|
||||||
wsPort: 3001,
|
wsPort: 3001,
|
||||||
hosts: [],
|
wsHosts: [],
|
||||||
|
enableHttp: true,
|
||||||
|
enableHttpPost: true,
|
||||||
|
enableWs: true,
|
||||||
|
enableWsReverse: false
|
||||||
|
}
|
||||||
|
let defaultConfig: Config = {
|
||||||
|
ob11: ob11Default,
|
||||||
|
heartInterval: 5000,
|
||||||
token: "",
|
token: "",
|
||||||
enableBase64: false,
|
enableBase64: false,
|
||||||
debug: false,
|
debug: false,
|
||||||
log: false,
|
log: false,
|
||||||
reportSelfMessage: false
|
reportSelfMessage: false
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!fs.existsSync(this.configPath)) {
|
if (!fs.existsSync(this.configPath)) {
|
||||||
return defaultConfig
|
this.config = defaultConfig;
|
||||||
|
return this.config;
|
||||||
} else {
|
} else {
|
||||||
const data = fs.readFileSync(this.configPath, "utf-8");
|
const data = fs.readFileSync(this.configPath, "utf-8");
|
||||||
let jsonData: Config = defaultConfig;
|
let jsonData: Config = defaultConfig;
|
||||||
try {
|
try {
|
||||||
jsonData = JSON.parse(data)
|
jsonData = JSON.parse(data)
|
||||||
|
} catch (e) {
|
||||||
|
this.config = defaultConfig;
|
||||||
|
return this.config;
|
||||||
}
|
}
|
||||||
catch (e){
|
mergeNewProperties(defaultConfig, jsonData);
|
||||||
|
this.checkOldConfig(jsonData.ob11, jsonData, "httpPort", "port");
|
||||||
}
|
this.checkOldConfig(jsonData.ob11, jsonData, "httpHosts", "hosts");
|
||||||
if (!jsonData.hosts) {
|
this.checkOldConfig(jsonData.ob11, jsonData, "wsPort", "wsPort");
|
||||||
jsonData.hosts = []
|
// console.log("get config", jsonData);
|
||||||
}
|
this.config = jsonData;
|
||||||
if (!jsonData.wsPort){
|
return this.config;
|
||||||
jsonData.wsPort = 3001
|
|
||||||
}
|
|
||||||
if (!jsonData.token){
|
|
||||||
jsonData.token = ""
|
|
||||||
}
|
|
||||||
return jsonData;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig(config: Config) {
|
setConfig(config: Config) {
|
||||||
|
this.config = config;
|
||||||
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8")
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { NTQQApi } from '../ntqqapi/ntcall';
|
import {NTQQApi} from '../ntqqapi/ntcall';
|
||||||
import { Friend, Group, GroupMember, RawMessage, SelfInfo } from "../ntqqapi/types";
|
import {Friend, Group, GroupMember, RawMessage, SelfInfo} from "../ntqqapi/types";
|
||||||
import { log } from "./utils";
|
|
||||||
|
|
||||||
export let groups: Group[] = []
|
export let groups: Group[] = []
|
||||||
export let friends: Friend[] = []
|
export let friends: Friend[] = []
|
||||||
@ -88,5 +87,4 @@ export function getStrangerByUin(uin: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const version = "v3.3.1"
|
export const version = "v3.4.0"
|
||||||
export const heartInterval = 15000 // 毫秒
|
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
export interface Config {
|
export interface OB11Config {
|
||||||
port: number
|
httpPort: number
|
||||||
|
httpHosts: string[]
|
||||||
wsPort: number
|
wsPort: number
|
||||||
hosts: string[]
|
wsHosts: string[]
|
||||||
|
enableHttp?: boolean
|
||||||
|
enableHttpPost?: boolean
|
||||||
|
enableWs?: boolean
|
||||||
|
enableWsReverse?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
ob11: OB11Config
|
||||||
token?: string
|
token?: string
|
||||||
|
heartInterval?: number // ms
|
||||||
enableBase64?: boolean
|
enableBase64?: boolean
|
||||||
debug?: boolean
|
debug?: boolean
|
||||||
reportSelfMessage?: boolean
|
reportSelfMessage?: boolean
|
||||||
log?: boolean
|
log?: boolean
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import * as path from "path";
|
|||||||
import {selfInfo} from "./data";
|
import {selfInfo} from "./data";
|
||||||
import {ConfigUtil} from "./config";
|
import {ConfigUtil} from "./config";
|
||||||
import util from "util";
|
import util from "util";
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
||||||
@ -47,6 +48,10 @@ export function isGIF(path: string) {
|
|||||||
return buffer.toString() === 'GIF8'
|
return buffer.toString() === 'GIF8'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 定义一个异步函数来检查文件是否存在
|
// 定义一个异步函数来检查文件是否存在
|
||||||
export function checkFileReceived(path: string, timeout: number=3000): Promise<void> {
|
export function checkFileReceived(path: string, timeout: number=3000): Promise<void> {
|
||||||
@ -93,5 +98,21 @@ export async function file2base64(path: string){
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sleep = (ms: number): Promise<void> =>
|
|
||||||
new Promise((resolve) => setTimeout(resolve, ms))
|
// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@ -4,7 +4,7 @@ import {LLOneBot} from "./preload";
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
llonebot: LLOneBot;
|
llonebot: typeof llonebot;
|
||||||
LiteLoader: any;
|
LiteLoader: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,11 @@
|
|||||||
import {webContents} from 'electron';
|
import {webContents} from 'electron';
|
||||||
|
|
||||||
function sendIPCMsg(channel: string, data: any) {
|
|
||||||
|
function sendIPCMsg(channel: string, ...data: any) {
|
||||||
let contents = webContents.getAllWebContents();
|
let contents = webContents.getAllWebContents();
|
||||||
for (const content of contents) {
|
for (const content of contents) {
|
||||||
try {
|
try {
|
||||||
content.send(channel, data)
|
content.send(channel, ...data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("llonebot send ipc msg to render error:", e)
|
console.log("llonebot send ipc msg to render error:", e)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG, } from "../common/
|
|||||||
import { postMsg, setToken, startHTTPServer, startWSServer } from "../onebot11/server";
|
import { postMsg, setToken, startHTTPServer, startWSServer } from "../onebot11/server";
|
||||||
import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
|
import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
|
||||||
import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data";
|
import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data";
|
||||||
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
|
import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
|
||||||
import { OB11Constructor } from "../onebot11/constructor";
|
import { OB11Constructor } from "../onebot11/constructor";
|
||||||
import { NTQQApi } from "../ntqqapi/ntcall";
|
import { NTQQApi } from "../ntqqapi/ntcall";
|
||||||
import { ChatType, RawMessage } from "../ntqqapi/types";
|
import { ChatType, RawMessage } from "../ntqqapi/types";
|
||||||
@ -29,19 +29,22 @@ function onLoad() {
|
|||||||
ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
|
ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
|
||||||
let oldConfig = getConfigUtil().getConfig();
|
let oldConfig = getConfigUtil().getConfig();
|
||||||
getConfigUtil().setConfig(arg)
|
getConfigUtil().setConfig(arg)
|
||||||
if (arg.port != oldConfig.port) {
|
if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) {
|
||||||
startHTTPServer(arg.port)
|
ob11HTTPServer.restart(arg.ob11.httpPort);
|
||||||
}
|
}
|
||||||
if (arg.wsPort != oldConfig.wsPort) {
|
if (!arg.ob11.enableHttp){
|
||||||
startWSServer(arg.wsPort)
|
ob11HTTPServer.stop()
|
||||||
}
|
}
|
||||||
if (arg.token != oldConfig.token) {
|
else{
|
||||||
setToken(arg.token);
|
ob11HTTPServer.start(arg.ob11.httpPort);
|
||||||
|
}
|
||||||
|
if (arg.ob11.wsPort != oldConfig.ob11.wsPort) {
|
||||||
|
initWebsocket(arg.ob11.wsPort)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
|
ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
|
||||||
log(arg)
|
log(arg);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -51,7 +54,7 @@ function onLoad() {
|
|||||||
// log("收到新消息", message)
|
// log("收到新消息", message)
|
||||||
message.msgShortId = msgHistory[message.msgId]?.msgShortId
|
message.msgShortId = msgHistory[message.msgId]?.msgShortId
|
||||||
if (!message.msgShortId) {
|
if (!message.msgShortId) {
|
||||||
addHistoryMsg(message)
|
addHistoryMsg(message);
|
||||||
}
|
}
|
||||||
OB11Constructor.message(message).then((msg) => {
|
OB11Constructor.message(message).then((msg) => {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
@ -70,10 +73,9 @@ function onLoad() {
|
|||||||
async function start() {
|
async function start() {
|
||||||
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
|
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
|
||||||
try {
|
try {
|
||||||
// log("received msg length", payload.msgList.length);
|
|
||||||
postRawMsg(payload.msgList);
|
postRawMsg(payload.msgList);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("report message error: ", e.toString())
|
log("report message error: ", e.toString());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
|
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
|
||||||
@ -86,8 +88,8 @@ function onLoad() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (message.chatType == ChatType.friend) {
|
if (message.chatType == ChatType.friend) {
|
||||||
const friendRecallEvent = OB11Constructor.friendRecallEvent(message.senderUin, oriMessage.msgShortId)
|
const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message.senderUin), oriMessage.msgShortId);
|
||||||
postMsg(friendRecallEvent)
|
postMsg(friendRecallEvent);
|
||||||
} else if (message.chatType == ChatType.group) {
|
} else if (message.chatType == ChatType.group) {
|
||||||
let operatorId = message.senderUin
|
let operatorId = message.senderUin
|
||||||
for (const element of message.elements) {
|
for (const element of message.elements) {
|
||||||
@ -95,13 +97,14 @@ function onLoad() {
|
|||||||
const operator = await getGroupMember(message.peerUin, null, operatorUid)
|
const operator = await getGroupMember(message.peerUin, null, operatorUid)
|
||||||
operatorId = operator.uin
|
operatorId = operator.uin
|
||||||
}
|
}
|
||||||
const groupRecallEvent = OB11Constructor.groupRecallEvent(
|
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
|
||||||
message.peerUin,
|
parseInt(message.peerUin),
|
||||||
message.senderUin,
|
parseInt(message.senderUin),
|
||||||
operatorId,
|
parseInt(operatorId),
|
||||||
oriMessage.msgShortId
|
oriMessage.msgShortId
|
||||||
)
|
)
|
||||||
postMsg(groupRecallEvent)
|
|
||||||
|
postMsg(groupRecallEvent);
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -109,7 +112,7 @@ function onLoad() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => {
|
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => {
|
||||||
const {reportSelfMessage} = getConfigUtil().getConfig()
|
const {reportSelfMessage} = getConfigUtil().getConfig();
|
||||||
if (!reportSelfMessage) {
|
if (!reportSelfMessage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -117,45 +120,48 @@ function onLoad() {
|
|||||||
try {
|
try {
|
||||||
postRawMsg([payload.msgRecord]);
|
postRawMsg([payload.msgRecord]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("report self message error: ", e.toString())
|
log("report self message error: ", e.toString());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
NTQQApi.getGroups(true).then()
|
NTQQApi.getGroups(true).then()
|
||||||
const config = getConfigUtil().getConfig()
|
const config = getConfigUtil().getConfig()
|
||||||
startHTTPServer(config.port)
|
try {
|
||||||
startWSServer(config.wsPort)
|
ob11HTTPServer.start(config.ob11.httpPort)
|
||||||
setToken(config.token)
|
initWebsocket(config.ob11.wsPort);
|
||||||
|
}catch (e) {
|
||||||
|
console.log("start failed", e)
|
||||||
|
}
|
||||||
log("LLOneBot start")
|
log("LLOneBot start")
|
||||||
}
|
}
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
try {
|
try {
|
||||||
const _ = await NTQQApi.getSelfInfo()
|
const _ = await NTQQApi.getSelfInfo();
|
||||||
Object.assign(selfInfo, _)
|
Object.assign(selfInfo, _);
|
||||||
selfInfo.nick = selfInfo.uin
|
selfInfo.nick = selfInfo.uin;
|
||||||
log("get self simple info", _)
|
log("get self simple info", _);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("retry get self info")
|
log("retry get self info");
|
||||||
}
|
}
|
||||||
if (selfInfo.uin) {
|
if (selfInfo.uin) {
|
||||||
try {
|
try {
|
||||||
const userInfo = (await NTQQApi.getUserInfo(selfInfo.uid))
|
const userInfo = (await NTQQApi.getUserInfo(selfInfo.uid));
|
||||||
log("self info", userInfo);
|
log("self info", userInfo);
|
||||||
if (userInfo) {
|
if (userInfo) {
|
||||||
selfInfo.nick = userInfo.nick
|
selfInfo.nick = userInfo.nick;
|
||||||
} else {
|
} else {
|
||||||
return setTimeout(init, 1000)
|
return setTimeout(init, 1000);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("get self nickname failed", e.toString())
|
log("get self nickname failed", e.toString());
|
||||||
return setTimeout(init, 1000)
|
return setTimeout(init, 1000);
|
||||||
}
|
}
|
||||||
start().then();
|
start().then();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(init, 1000)
|
setTimeout(init, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(init, 1000)
|
setTimeout(init, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
|
AtType,
|
||||||
ElementType,
|
ElementType,
|
||||||
|
SendFaceElement,
|
||||||
SendPicElement,
|
SendPicElement,
|
||||||
SendPttElement,
|
SendPttElement,
|
||||||
SendReplyElement,
|
SendReplyElement,
|
||||||
SendTextElement,
|
SendTextElement
|
||||||
AtType,
|
|
||||||
SendFaceElement
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { NTQQApi } from "./ntcall";
|
import {NTQQApi} from "./ntcall";
|
||||||
|
|
||||||
|
|
||||||
export class SendMsgElementConstructor {
|
export class SendMsgElementConstructor {
|
||||||
|
@ -4,6 +4,15 @@ import { NTQQApi, NTQQApiClass, sendMessagePool } from "./ntcall";
|
|||||||
import { Group, RawMessage, User } from "./types";
|
import { Group, RawMessage, User } from "./types";
|
||||||
import { addHistoryMsg, friends, groups, msgHistory } from "../common/data";
|
import { addHistoryMsg, friends, groups, msgHistory } from "../common/data";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import {BrowserWindow} from 'electron';
|
||||||
|
import {log, sleep} from "../common/utils";
|
||||||
|
import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
|
||||||
|
import {Group, RawMessage, User} from "./types";
|
||||||
|
import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
|
||||||
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
|
||||||
|
import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
|
||||||
|
import {postMsg} from "../onebot11/server";
|
||||||
|
|
||||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||||
|
|
||||||
@ -110,25 +119,103 @@ export function removeReceiveHook(id: string) {
|
|||||||
receiveHooks.splice(index, 1);
|
receiveHooks.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateGroups(_groups: Group[]) {
|
async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
|
||||||
for (let group of _groups) {
|
for (let group of _groups) {
|
||||||
let existGroup = groups.find(g => g.groupCode == group.groupCode)
|
let existGroup = groups.find(g => g.groupCode == group.groupCode);
|
||||||
if (!existGroup) {
|
if (existGroup) {
|
||||||
NTQQApi.getGroupMembers(group.groupCode).then(members => {
|
Object.assign(existGroup, group);
|
||||||
if (members) {
|
}
|
||||||
group.members = members
|
else {
|
||||||
}
|
groups.push(group);
|
||||||
})
|
existGroup = group;
|
||||||
groups.push(group)
|
}
|
||||||
log("update group members", group.members)
|
|
||||||
} else {
|
if (needUpdate) {
|
||||||
Object.assign(existGroup, group)
|
const members = await NTQQApi.getGroupMembers(group.groupCode);
|
||||||
|
|
||||||
|
if (members) {
|
||||||
|
existGroup.members = members;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS, (payload) => updateGroups(payload.groupList).then())
|
async function processGroupEvent(payload) {
|
||||||
registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS_UNIX, (payload) => updateGroups(payload.groupList).then())
|
try {
|
||||||
|
const newGroupList = payload.groupList;
|
||||||
|
for (const group of newGroupList) {
|
||||||
|
let existGroup = groups.find(g => g.groupCode == group.groupCode);
|
||||||
|
if (existGroup) {
|
||||||
|
if (existGroup.memberCount > group.memberCount) {
|
||||||
|
const oldMembers = existGroup.members;
|
||||||
|
|
||||||
|
await sleep(200); // 如果请求QQ API的速度过快,通常无法正确拉取到最新的群信息,因此这里人为引入一个延时
|
||||||
|
const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
|
||||||
|
|
||||||
|
group.members = newMembers;
|
||||||
|
const newMembersSet = new Set<string>(); // 建立索引降低时间复杂度
|
||||||
|
|
||||||
|
for (const member of newMembers) {
|
||||||
|
newMembersSet.add(member.uin);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const member of oldMembers) {
|
||||||
|
if (!newMembersSet.has(member.uin)) {
|
||||||
|
postMsg(new OB11GroupDecreaseEvent(group.groupCode, parseInt(member.uin)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (existGroup.memberCount < group.memberCount) {
|
||||||
|
const oldMembers = existGroup.members;
|
||||||
|
const oldMembersSet = new Set<string>();
|
||||||
|
for (const member of oldMembers) {
|
||||||
|
oldMembersSet.add(member.uin);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(200);
|
||||||
|
const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
|
||||||
|
|
||||||
|
group.members = newMembers;
|
||||||
|
for (const member of newMembers) {
|
||||||
|
if (!oldMembersSet.has(member.uin)) {
|
||||||
|
postMsg(new OB11GroupIncreaseEvent(group.groupCode, parseInt(member.uin)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroups(newGroupList, false).then();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
updateGroups(payload.groupList).then();
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => {
|
||||||
|
if (payload.updateType != 2) {
|
||||||
|
updateGroups(payload.groupList).then();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (process.platform == "win32") {
|
||||||
|
processGroupEvent(payload).then();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => {
|
||||||
|
if (payload.updateType != 2) {
|
||||||
|
updateGroups(payload.groupList).then();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (process.platform != "win32") {
|
||||||
|
processGroupEvent(payload).then();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
registerReceiveHook<{
|
registerReceiveHook<{
|
||||||
data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
|
data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
|
||||||
}>(ReceiveCmd.FRIENDS, payload => {
|
}>(ReceiveCmd.FRIENDS, payload => {
|
||||||
@ -145,11 +232,6 @@ registerReceiveHook<{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// registerReceiveHook<any>(ReceiveCmd.USER_INFO, (payload)=>{
|
|
||||||
// log("user info", payload);
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
|
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
|
||||||
for (const message of payload.msgList) {
|
for (const message of payload.msgList) {
|
||||||
// log("收到新消息,push到历史记录", message)
|
// log("收到新消息,push到历史记录", message)
|
||||||
|
56
src/onebot11/ReconnectingWebsocket.ts
Normal file
56
src/onebot11/ReconnectingWebsocket.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {log} from "../common/utils";
|
||||||
|
|
||||||
|
const WebSocket = require("ws");
|
||||||
|
|
||||||
|
export class ReconnectingWebsocket {
|
||||||
|
private websocket;
|
||||||
|
private readonly url: string;
|
||||||
|
|
||||||
|
public constructor(url: string) {
|
||||||
|
this.url = url;
|
||||||
|
this.reconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
public onopen = function (){}
|
||||||
|
|
||||||
|
public onmessage = function (msg){}
|
||||||
|
|
||||||
|
public onclose = function () {}
|
||||||
|
|
||||||
|
public send(msg) {
|
||||||
|
if (this.websocket && this.websocket.readyState == WebSocket.OPEN) {
|
||||||
|
this.websocket.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private reconnect() {
|
||||||
|
this.websocket = new WebSocket(this.url, {
|
||||||
|
handshakeTimeout: 2000,
|
||||||
|
perMessageDeflate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Trying to connect to the websocket server: " + this.url);
|
||||||
|
|
||||||
|
const instance = this;
|
||||||
|
|
||||||
|
this.websocket.on("open", function open() {
|
||||||
|
console.log("Connected to the websocket server: " + instance.url);
|
||||||
|
instance.onopen();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.websocket.on("message", function message(data) {
|
||||||
|
instance.onmessage(data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.websocket.on("error", console.error);
|
||||||
|
|
||||||
|
this.websocket.on("close", function close() {
|
||||||
|
console.log("The websocket connection: " + instance.url + " closed, trying reconnecting...");
|
||||||
|
instance.onclose();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
instance.reconnect();
|
||||||
|
}, 3000); // TODO: 重连间隔在配置文件中实现
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
44
src/onebot11/action/BaseAction.ts
Normal file
44
src/onebot11/action/BaseAction.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import {ActionName, BaseCheckResult} from "./types"
|
||||||
|
import {OB11Response, OB11WebsocketResponse} from "./utils"
|
||||||
|
import {OB11Return, OB11WebsocketReturn} from "../types";
|
||||||
|
|
||||||
|
class BaseAction<PayloadType, ReturnDataType> {
|
||||||
|
actionName: ActionName
|
||||||
|
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> {
|
||||||
|
const result = await this.check(payload);
|
||||||
|
if (!result.valid) {
|
||||||
|
return OB11Response.error(result.message, 400);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resData = await this._handle(payload);
|
||||||
|
return OB11Response.ok(resData);
|
||||||
|
} catch (e) {
|
||||||
|
return OB11Response.error(e.toString(), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async websocketHandle(payload: PayloadType, echo: string): Promise<OB11WebsocketReturn<ReturnDataType | null>> {
|
||||||
|
const result = await this.check(payload)
|
||||||
|
if (!result.valid) {
|
||||||
|
return OB11WebsocketResponse.error(result.message, 1400)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const resData = await this._handle(payload)
|
||||||
|
return OB11WebsocketResponse.ok(resData, echo);
|
||||||
|
} catch (e) {
|
||||||
|
return OB11WebsocketResponse.error(e.toString(), 1200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _handle(payload: PayloadType): Promise<ReturnDataType> {
|
||||||
|
throw `pleas override ${this.actionName} _handle`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BaseAction
|
@ -1,4 +1,4 @@
|
|||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
import CanSendRecord from "./CanSendRecord";
|
import CanSendRecord from "./CanSendRecord";
|
||||||
|
|
||||||
interface ReturnType{
|
interface ReturnType{
|
@ -1,5 +1,5 @@
|
|||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
interface ReturnType{
|
interface ReturnType{
|
||||||
yes: boolean
|
yes: boolean
|
@ -1,7 +1,7 @@
|
|||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { NTQQApi } from "../../ntqqapi/ntcall";
|
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||||
import { getHistoryMsgByShortId, msgHistory } from "../../common/data";
|
import {getHistoryMsgByShortId} from "../../common/data";
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
message_id: number
|
message_id: number
|
@ -1,8 +1,8 @@
|
|||||||
import { OB11User } from '../types';
|
import {OB11User} from '../types';
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import { friends } from "../../common/data";
|
import {friends} from "../../common/data";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
class GetFriendList extends BaseAction<null, OB11User[]> {
|
class GetFriendList extends BaseAction<null, OB11User[]> {
|
@ -1,8 +1,8 @@
|
|||||||
import { OB11Group } from '../types';
|
import {OB11Group} from '../types';
|
||||||
import { getGroup } from "../../common/data";
|
import {getGroup} from "../../common/data";
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
interface PayloadType {
|
interface PayloadType {
|
||||||
group_id: number
|
group_id: number
|
@ -1,9 +1,8 @@
|
|||||||
import { OB11Group } from '../types';
|
import {OB11Group} from '../types';
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import { groups } from "../../common/data";
|
import {groups} from "../../common/data";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GetGroupList extends BaseAction<null, OB11Group[]> {
|
class GetGroupList extends BaseAction<null, OB11Group[]> {
|
@ -1,8 +1,8 @@
|
|||||||
import { OB11GroupMember } from '../types';
|
import {OB11GroupMember} from '../types';
|
||||||
import { getGroupMember } from "../../common/data";
|
import {getGroupMember} from "../../common/data";
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
export interface PayloadType {
|
export interface PayloadType {
|
@ -1,9 +1,9 @@
|
|||||||
import { OB11GroupMember } from '../types';
|
import {OB11GroupMember} from '../types';
|
||||||
import { getGroup } from "../../common/data";
|
import {getGroup} from "../../common/data";
|
||||||
import { NTQQApi } from "../../ntqqapi/ntcall";
|
import {NTQQApi} from "../../ntqqapi/ntcall";
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
export interface PayloadType {
|
export interface PayloadType {
|
||||||
group_id: number
|
group_id: number
|
@ -1,8 +1,8 @@
|
|||||||
import { OB11User } from '../types';
|
import {OB11User} from '../types';
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import { selfInfo } from "../../common/data";
|
import {selfInfo} from "../../common/data";
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
class GetLoginInfo extends BaseAction<null, OB11User> {
|
class GetLoginInfo extends BaseAction<null, OB11User> {
|
@ -1,9 +1,8 @@
|
|||||||
import { getHistoryMsgByShortId, msgHistory } from "../../common/data";
|
import {getHistoryMsgByShortId} from "../../common/data";
|
||||||
import { OB11Message } from '../types';
|
import {OB11Message} from '../types';
|
||||||
import { OB11Constructor } from "../constructor";
|
import {OB11Constructor} from "../constructor";
|
||||||
import { log } from "../../common/utils";
|
|
||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
export interface PayloadType {
|
export interface PayloadType {
|
@ -1,6 +1,6 @@
|
|||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import {OB11Status} from "../types";
|
import {OB11Status} from "../types";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
export default class GetStatus extends BaseAction<any, OB11Status> {
|
export default class GetStatus extends BaseAction<any, OB11Status> {
|
@ -1,7 +1,7 @@
|
|||||||
import BaseAction from "./BaseAction";
|
import BaseAction from "./BaseAction";
|
||||||
import { OB11Version } from "../types";
|
import {OB11Version} from "../types";
|
||||||
import {version} from "../../common/data";
|
import {version} from "../../common/data";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
export default class GetVersionInfo extends BaseAction<any, OB11Version>{
|
export default class GetVersionInfo extends BaseAction<any, OB11Version>{
|
||||||
actionName = ActionName.GetVersionInfo
|
actionName = ActionName.GetVersionInfo
|
@ -1,5 +1,5 @@
|
|||||||
import SendMsg from "./SendMsg";
|
import SendMsg from "./SendMsg";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
|
|
||||||
class SendGroupMsg extends SendMsg{
|
class SendGroupMsg extends SendMsg{
|
@ -10,6 +10,41 @@ import {ActionName, BaseCheckResult} from "./types";
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import {log, sleep} from "../../common/utils";
|
import {log, sleep} from "../../common/utils";
|
||||||
|
|
||||||
|
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 {
|
export interface ReturnDataType {
|
||||||
message_id: number
|
message_id: number
|
||||||
}
|
}
|
||||||
@ -233,8 +268,6 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}))
|
}))
|
||||||
return returnMsg
|
return returnMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SendMsg
|
export default SendMsg
|
@ -1,5 +1,5 @@
|
|||||||
import SendMsg from "./SendMsg";
|
import SendMsg from "./SendMsg";
|
||||||
import { ActionName } from "./types";
|
import {ActionName} from "./types";
|
||||||
|
|
||||||
class SendPrivateMsg extends SendMsg {
|
class SendPrivateMsg extends SendMsg {
|
||||||
actionName = ActionName.SendPrivateMsg
|
actionName = ActionName.SendPrivateMsg
|
@ -9,6 +9,7 @@ import SendGroupMsg from './SendGroupMsg'
|
|||||||
import SendPrivateMsg from './SendPrivateMsg'
|
import SendPrivateMsg from './SendPrivateMsg'
|
||||||
import SendMsg from './SendMsg'
|
import SendMsg from './SendMsg'
|
||||||
import DeleteMsg from "./DeleteMsg";
|
import DeleteMsg from "./DeleteMsg";
|
||||||
|
import BaseAction from "./BaseAction";
|
||||||
import GetVersionInfo from "./GetVersionInfo";
|
import GetVersionInfo from "./GetVersionInfo";
|
||||||
import CanSendRecord from "./CanSendRecord";
|
import CanSendRecord from "./CanSendRecord";
|
||||||
import CanSendImage from "./CanSendImage";
|
import CanSendImage from "./CanSendImage";
|
||||||
@ -27,4 +28,15 @@ export const actionHandlers = [
|
|||||||
new CanSendRecord(),
|
new CanSendRecord(),
|
||||||
new CanSendImage(),
|
new CanSendImage(),
|
||||||
new GetStatus()
|
new GetStatus()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function initActionMap() {
|
||||||
|
const actionMap = new Map<string, BaseAction<any, any>>();
|
||||||
|
for (const action of actionHandlers) {
|
||||||
|
actionMap.set(action.actionName, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionMap
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actionMap = initActionMap();
|
@ -1,5 +1,3 @@
|
|||||||
import GetVersionInfo from "./GetVersionInfo";
|
|
||||||
|
|
||||||
export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
|
export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
|
||||||
|
|
||||||
export interface ValidCheckResult {
|
export interface ValidCheckResult {
|
||||||
@ -13,7 +11,7 @@ export interface InvalidCheckResult {
|
|||||||
[k: string | number]: any
|
[k: string | number]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ActionName{
|
export enum ActionName {
|
||||||
TestForwardMsg = "test_forward_msg",
|
TestForwardMsg = "test_forward_msg",
|
||||||
GetLoginInfo = "get_login_info",
|
GetLoginInfo = "get_login_info",
|
||||||
GetFriendList = "get_friend_list",
|
GetFriendList = "get_friend_list",
|
36
src/onebot11/action/utils.ts
Normal file
36
src/onebot11/action/utils.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import {OB11Return, OB11WebsocketReturn} from '../types';
|
||||||
|
|
||||||
|
export class OB11Response {
|
||||||
|
static res<T>(data: T, status: string, retcode: number, message: string = ""): OB11Return<T> {
|
||||||
|
return {
|
||||||
|
status: status,
|
||||||
|
retcode: retcode,
|
||||||
|
data: data,
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static ok<T>(data: T) {
|
||||||
|
return OB11Response.res<T>(data, "ok", 0)
|
||||||
|
}
|
||||||
|
static error(err: string, retcode: number) {
|
||||||
|
return OB11Response.res(null, "failed", retcode, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OB11WebsocketResponse {
|
||||||
|
static res<T>(data: T, status: string, retcode: number, echo: string, message: string = ""): OB11WebsocketReturn<T> {
|
||||||
|
return {
|
||||||
|
status: status,
|
||||||
|
retcode: retcode,
|
||||||
|
data: data,
|
||||||
|
echo: echo,
|
||||||
|
message: message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static ok<T>(data: T, echo: string = "") {
|
||||||
|
return OB11WebsocketResponse.res<T>(data, "ok", 0, echo)
|
||||||
|
}
|
||||||
|
static error(err: string, retcode: number, echo: string = "") {
|
||||||
|
return OB11WebsocketResponse.res(null, "failed", retcode, echo, err)
|
||||||
|
}
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import {ActionName, BaseCheckResult} from "./types"
|
|
||||||
import { OB11Response } from "./utils"
|
|
||||||
import { OB11Return } from "../types";
|
|
||||||
|
|
||||||
class BaseAction<PayloadType, ReturnDataType> {
|
|
||||||
actionName: ActionName
|
|
||||||
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> {
|
|
||||||
const result = await this.check(payload)
|
|
||||||
if (!result.valid) {
|
|
||||||
return OB11Response.error(result.message)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const resData = await this._handle(payload)
|
|
||||||
return OB11Response.ok(resData)
|
|
||||||
}catch (e) {
|
|
||||||
return OB11Response.error(e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async _handle(payload: PayloadType): Promise<ReturnDataType> {
|
|
||||||
throw `pleas override ${this.actionName} _handle`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BaseAction
|
|
@ -1,34 +0,0 @@
|
|||||||
import { OB11Message } from '../types';
|
|
||||||
import BaseAction from "./BaseAction";
|
|
||||||
import { ActionName } from "./types";
|
|
||||||
import { NTQQApi, Peer } from "../../ntqqapi/ntcall";
|
|
||||||
import { ChatType } from "../../ntqqapi/types";
|
|
||||||
import { selfInfo } from "../../common/data";
|
|
||||||
import { SendMsgElementConstructor } from "../../ntqqapi/constructor";
|
|
||||||
import {sleep} from "../../common/utils";
|
|
||||||
|
|
||||||
|
|
||||||
export interface PayloadType {
|
|
||||||
message: string,
|
|
||||||
group_id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TestForwardMsg extends BaseAction<PayloadType, OB11Message> {
|
|
||||||
actionName = ActionName.TestForwardMsg
|
|
||||||
|
|
||||||
protected async _handle(payload: PayloadType) {
|
|
||||||
// log("history msg ids", Object.keys(msgHistory));
|
|
||||||
const selfPeer: Peer = {
|
|
||||||
chatType: ChatType.friend,
|
|
||||||
peerUid: selfInfo.uid
|
|
||||||
}
|
|
||||||
const sendMsg = await NTQQApi.sendMsg(selfPeer, [SendMsgElementConstructor.text(payload.message)])
|
|
||||||
const sendMsg2 = await NTQQApi.sendMsg(selfPeer, [SendMsgElementConstructor.text(payload.message)])
|
|
||||||
await NTQQApi.multiForwardMsg(
|
|
||||||
selfPeer,
|
|
||||||
{chatType: ChatType.group, peerUid: payload.group_id, guildId: ""},
|
|
||||||
[sendMsg.msgId, sendMsg2.msgId]
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { OB11Return } from '../types';
|
|
||||||
|
|
||||||
export class OB11Response {
|
|
||||||
static res<T>(data: T, status: number = 0, message: string = "", echo=""): OB11Return<T> {
|
|
||||||
return {
|
|
||||||
status: status,
|
|
||||||
retcode: status,
|
|
||||||
data: data,
|
|
||||||
message: message,
|
|
||||||
echo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static ok<T>(data: T) {
|
|
||||||
return OB11Response.res<T>(data)
|
|
||||||
}
|
|
||||||
static error(err: string, status=-1) {
|
|
||||||
return OB11Response.res(null, status, err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,11 @@
|
|||||||
import {
|
import {OB11Group, OB11GroupMember, OB11GroupMemberRole, OB11Message, OB11MessageDataType, OB11User} from "./types";
|
||||||
OB11MessageDataType,
|
import {AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User} from '../ntqqapi/types';
|
||||||
OB11GroupMemberRole,
|
import {getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data';
|
||||||
OB11Message,
|
import {file2base64, getConfigUtil, log} from "../common/utils";
|
||||||
OB11Group,
|
import {NTQQApi} from "../ntqqapi/ntcall";
|
||||||
OB11GroupMember,
|
|
||||||
OB11User
|
|
||||||
} from "./types";
|
|
||||||
import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
|
|
||||||
import { getFriend, getGroupMember, getHistoryMsgBySeq, heartInterval, msgHistory, selfInfo } from '../common/data';
|
|
||||||
import { file2base64, getConfigUtil, log } from "../common/utils";
|
|
||||||
import { NTQQApi } from "../ntqqapi/ntcall";
|
|
||||||
import {OB11EventConstructor} from "./events/constructor";
|
|
||||||
|
|
||||||
|
|
||||||
export class OB11Constructor extends OB11EventConstructor{
|
export class OB11Constructor {
|
||||||
static async message(msg: RawMessage): Promise<OB11Message> {
|
static async message(msg: RawMessage): Promise<OB11Message> {
|
||||||
|
|
||||||
const {enableBase64} = getConfigUtil().getConfig()
|
const {enableBase64} = getConfigUtil().getConfig()
|
||||||
|
15
src/onebot11/event/OB11BaseEvent.ts
Normal file
15
src/onebot11/event/OB11BaseEvent.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {selfInfo} from "../../common/data";
|
||||||
|
|
||||||
|
export enum EventType {
|
||||||
|
META = "meta_event",
|
||||||
|
REQUEST = "request",
|
||||||
|
NOTICE = "notice",
|
||||||
|
MESSAGE = "message"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class OB11BaseEvent {
|
||||||
|
time = new Date().getTime();
|
||||||
|
self_id = selfInfo.uin;
|
||||||
|
post_type: EventType;
|
||||||
|
}
|
24
src/onebot11/event/manager.ts
Normal file
24
src/onebot11/event/manager.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as websocket from "ws";
|
||||||
|
import {PostMsgType, wsReply} from "../server";
|
||||||
|
import {ReconnectingWebsocket} from "../ReconnectingWebsocket";
|
||||||
|
|
||||||
|
const websocketList = [];
|
||||||
|
|
||||||
|
export function registerEventSender(ws: websocket.WebSocket | ReconnectingWebsocket) {
|
||||||
|
websocketList.push(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregisterEventSender(ws: websocket.WebSocket | ReconnectingWebsocket) {
|
||||||
|
let index = websocketList.indexOf(ws);
|
||||||
|
if (index !== -1) {
|
||||||
|
websocketList.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function callEvent(event: PostMsgType) {
|
||||||
|
new Promise(() => {
|
||||||
|
for (const ws of websocketList) {
|
||||||
|
wsReply(ws, event);
|
||||||
|
}
|
||||||
|
}).then()
|
||||||
|
}
|
5
src/onebot11/event/message/OB11BaseMessageEvent.ts
Normal file
5
src/onebot11/event/message/OB11BaseMessageEvent.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import {EventType, OB11BaseEvent} from "../OB11BaseEvent";
|
||||||
|
|
||||||
|
export abstract class OB11BaseMessageEvent extends OB11BaseEvent {
|
||||||
|
post_type = EventType.MESSAGE;
|
||||||
|
}
|
6
src/onebot11/event/meta/OB11BaseMetaEvent.ts
Normal file
6
src/onebot11/event/meta/OB11BaseMetaEvent.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import {EventType, OB11BaseEvent} from "../OB11BaseEvent";
|
||||||
|
|
||||||
|
export abstract class OB11BaseMetaEvent extends OB11BaseEvent {
|
||||||
|
post_type = EventType.META;
|
||||||
|
meta_event_type: string;
|
||||||
|
}
|
21
src/onebot11/event/meta/OB11HeartbeatEvent.ts
Normal file
21
src/onebot11/event/meta/OB11HeartbeatEvent.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {OB11BaseMetaEvent} from "./OB11BaseMetaEvent";
|
||||||
|
|
||||||
|
interface HeartbeatStatus {
|
||||||
|
online: boolean | null,
|
||||||
|
good: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OB11HeartbeatEvent extends OB11BaseMetaEvent {
|
||||||
|
meta_event_type = "heartbeat";
|
||||||
|
status: HeartbeatStatus;
|
||||||
|
interval: number;
|
||||||
|
|
||||||
|
public constructor(isOnline: boolean | null, isGood: boolean, interval: number) {
|
||||||
|
super();
|
||||||
|
this.interval = interval;
|
||||||
|
this.status = {
|
||||||
|
online: isOnline,
|
||||||
|
good: isGood
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/onebot11/event/meta/OB11LifeCycleEvent.ts
Normal file
17
src/onebot11/event/meta/OB11LifeCycleEvent.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import {OB11BaseMetaEvent} from "./OB11BaseMetaEvent";
|
||||||
|
|
||||||
|
export enum LifeCycleSubType {
|
||||||
|
ENABLE = "enable",
|
||||||
|
DISABLE = "disable",
|
||||||
|
CONNECT = "connect"
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OB11LifeCycleEvent extends OB11BaseMetaEvent {
|
||||||
|
meta_event_type = "lifecycle";
|
||||||
|
sub_type: LifeCycleSubType;
|
||||||
|
|
||||||
|
public constructor(subType: LifeCycleSubType) {
|
||||||
|
super();
|
||||||
|
this.sub_type = subType;
|
||||||
|
}
|
||||||
|
}
|
5
src/onebot11/event/notice/OB11BaseNoticeEvent.ts
Normal file
5
src/onebot11/event/notice/OB11BaseNoticeEvent.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import {EventType, OB11BaseEvent} from "../OB11BaseEvent";
|
||||||
|
|
||||||
|
export abstract class OB11BaseNoticeEvent extends OB11BaseEvent {
|
||||||
|
post_type = EventType.NOTICE;
|
||||||
|
}
|
13
src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts
Normal file
13
src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
|
||||||
|
|
||||||
|
export class OB11FriendRecallNoticeEvent extends OB11BaseNoticeEvent {
|
||||||
|
notice_type = "friend_recall"
|
||||||
|
user_id: number
|
||||||
|
message_id: number
|
||||||
|
|
||||||
|
public constructor(userId: number, messageId: number) {
|
||||||
|
super();
|
||||||
|
this.user_id = userId;
|
||||||
|
this.message_id = messageId;
|
||||||
|
}
|
||||||
|
}
|
6
src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts
Normal file
6
src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
|
||||||
|
|
||||||
|
export class OB11GroupAdminNoticeEvent extends OB11BaseNoticeEvent {
|
||||||
|
notice_type = "group_admin"
|
||||||
|
sub_type: string // "set" | "unset"
|
||||||
|
}
|
14
src/onebot11/event/notice/OB11GroupDecreaseEvent.ts
Normal file
14
src/onebot11/event/notice/OB11GroupDecreaseEvent.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
|
||||||
|
|
||||||
|
export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent {
|
||||||
|
notice_type = "group_decrease";
|
||||||
|
sub_type = "leave"; // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
|
||||||
|
operate_id: number;
|
||||||
|
|
||||||
|
constructor(groupId: number, userId: number) {
|
||||||
|
super();
|
||||||
|
this.group_id = groupId;
|
||||||
|
this.operate_id = userId; // 实际上不应该这么实现,但是现在还没有办法识别用户是被踢出的,还是自己主动退出的
|
||||||
|
this.user_id = userId;
|
||||||
|
}
|
||||||
|
}
|
14
src/onebot11/event/notice/OB11GroupIncreaseEvent.ts
Normal file
14
src/onebot11/event/notice/OB11GroupIncreaseEvent.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
|
||||||
|
|
||||||
|
export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent {
|
||||||
|
notice_type = "group_increase";
|
||||||
|
sub_type = "approve"; // TODO: 实现其他几种子类型的识别 ("approve" | "invite")
|
||||||
|
operate_id: number;
|
||||||
|
|
||||||
|
constructor(groupId: number, userId: number) {
|
||||||
|
super();
|
||||||
|
this.group_id = groupId;
|
||||||
|
this.operate_id = userId; // 实际上不应该这么实现,但是现在还没有办法识别用户是被邀请的,还是主动加入的
|
||||||
|
this.user_id = userId;
|
||||||
|
}
|
||||||
|
}
|
6
src/onebot11/event/notice/OB11GroupNoticeEvent.ts
Normal file
6
src/onebot11/event/notice/OB11GroupNoticeEvent.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
|
||||||
|
|
||||||
|
export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent {
|
||||||
|
group_id: number;
|
||||||
|
user_id: number;
|
||||||
|
}
|
15
src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
Normal file
15
src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
|
||||||
|
|
||||||
|
export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent {
|
||||||
|
notice_type = "group_recall"
|
||||||
|
operator_id: number
|
||||||
|
message_id: number
|
||||||
|
|
||||||
|
constructor(groupId: number, userId: number, operatorId: number, messageId: number) {
|
||||||
|
super();
|
||||||
|
this.group_id = groupId;
|
||||||
|
this.user_id = userId;
|
||||||
|
this.operator_id = operatorId;
|
||||||
|
this.message_id = messageId;
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
import {
|
|
||||||
OB11EventBase,
|
|
||||||
OB11EventPostType, OB11FriendRecallNoticeEvent,
|
|
||||||
OB11GroupRecallNoticeEvent,
|
|
||||||
OB11HeartEvent,
|
|
||||||
OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent
|
|
||||||
} from "./types";
|
|
||||||
import { heartInterval, selfInfo } from "../../common/data";
|
|
||||||
|
|
||||||
function eventBase(post_type: OB11EventPostType): OB11EventBase {
|
|
||||||
return {
|
|
||||||
time: Math.floor(Date.now() / 1000),
|
|
||||||
self_id: parseInt(selfInfo.uin),
|
|
||||||
post_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OB11EventConstructor {
|
|
||||||
static lifeCycleEvent(): OB11LifeCycleEvent {
|
|
||||||
return {
|
|
||||||
...eventBase(OB11EventPostType.META) as OB11MetaEvent,
|
|
||||||
meta_event_type: "lifecycle",
|
|
||||||
sub_type: "connect"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static heartEvent(): OB11HeartEvent {
|
|
||||||
return {
|
|
||||||
...eventBase(OB11EventPostType.META) as OB11MetaEvent,
|
|
||||||
meta_event_type: "heartbeat",
|
|
||||||
status: {
|
|
||||||
online: true,
|
|
||||||
good: true
|
|
||||||
},
|
|
||||||
interval: heartInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static groupRecallEvent(group_id: string, user_id: string, operator_id: string, message_id: number): OB11GroupRecallNoticeEvent {
|
|
||||||
return {
|
|
||||||
...eventBase(OB11EventPostType.NOTICE) as OB11NoticeEvent,
|
|
||||||
notice_type: "group_recall",
|
|
||||||
group_id: parseInt(group_id),
|
|
||||||
user_id: parseInt(user_id),
|
|
||||||
operator_id: parseInt(operator_id),
|
|
||||||
message_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static friendRecallEvent(user_id: string, message_id: number): OB11FriendRecallNoticeEvent {
|
|
||||||
return {
|
|
||||||
...eventBase(OB11EventPostType.NOTICE) as OB11NoticeEvent,
|
|
||||||
notice_type: "friend_recall",
|
|
||||||
user_id: parseInt(user_id),
|
|
||||||
message_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
import { OB11Status } from "../types";
|
|
||||||
|
|
||||||
export enum OB11EventPostType{
|
|
||||||
META = "meta_event",
|
|
||||||
NOTICE = "notice"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11EventBase {
|
|
||||||
time: number
|
|
||||||
self_id: number
|
|
||||||
post_type: OB11EventPostType
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11MetaEvent extends OB11EventBase{
|
|
||||||
post_type: OB11EventPostType.META
|
|
||||||
meta_event_type: "lifecycle" | "heartbeat"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11NoticeEvent extends OB11EventBase{
|
|
||||||
post_type: OB11EventPostType.NOTICE
|
|
||||||
notice_type: "group_admin" | "group_decrease" | "group_increase" | "group_ban" | "friend_add" | "group_recall" | "friend_recall"
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OB11GroupNoticeBase extends OB11NoticeEvent{
|
|
||||||
group_id: number
|
|
||||||
user_id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11GroupAdminNoticeEvent extends OB11GroupNoticeBase{
|
|
||||||
notice_type: "group_admin"
|
|
||||||
sub_type: "set" | "unset"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11GroupMemberDecNoticeEvent extends OB11GroupNoticeBase{
|
|
||||||
notice_type: "group_decrease"
|
|
||||||
sub_type: "leave" | "kick" | "kick_me"
|
|
||||||
operator_id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11GroupMemberIncNoticeEvent extends OB11GroupNoticeBase{
|
|
||||||
notice_type: "group_increase"
|
|
||||||
sub_type: "approve" | "invite"
|
|
||||||
operator_id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11GroupRecallNoticeEvent extends OB11GroupNoticeBase{
|
|
||||||
notice_type: "group_recall"
|
|
||||||
operator_id: number
|
|
||||||
message_id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11FriendRecallNoticeEvent extends OB11NoticeEvent{
|
|
||||||
notice_type: "friend_recall"
|
|
||||||
user_id: number
|
|
||||||
message_id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11LifeCycleEvent extends OB11MetaEvent {
|
|
||||||
meta_event_type: "lifecycle"
|
|
||||||
sub_type: "enable" | "disable" | "connect"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OB11HeartEvent extends OB11MetaEvent {
|
|
||||||
meta_event_type: "heartbeat"
|
|
||||||
status: OB11Status
|
|
||||||
interval: number
|
|
||||||
}
|
|
@ -1,200 +1,185 @@
|
|||||||
import * as http from "http";
|
|
||||||
import * as websocket from "ws";
|
import * as websocket from "ws";
|
||||||
import urlParse from "url";
|
import urlParse from "url";
|
||||||
import express from "express";
|
import {getConfigUtil, log} from "../common/utils";
|
||||||
import { Request } from 'express';
|
import {selfInfo} from "../common/data";
|
||||||
import { Response } from 'express';
|
import {OB11Message} from './types';
|
||||||
import { getConfigUtil, log } from "../common/utils";
|
import {actionMap} from "./action";
|
||||||
import { heartInterval, selfInfo } from "../common/data";
|
import {OB11WebsocketResponse} from "./action/utils";
|
||||||
import { OB11Message, OB11Return, OB11MessageData } from './types';
|
import {callEvent, registerEventSender, unregisterEventSender} from "./event/manager";
|
||||||
import { actionHandlers } from "./actions";
|
import {ReconnectingWebsocket} from "./ReconnectingWebsocket";
|
||||||
import { OB11Response } from "./actions/utils";
|
import {ActionName} from "./action/types";
|
||||||
import { ActionName } from "./actions/types";
|
import {OB11BaseMetaEvent} from "./event/meta/OB11BaseMetaEvent";
|
||||||
import BaseAction from "./actions/BaseAction";
|
import {OB11BaseNoticeEvent} from "./event/notice/OB11BaseNoticeEvent";
|
||||||
import { OB11Constructor } from "./constructor";
|
import BaseAction from "./action/BaseAction";
|
||||||
import { OB11EventBase, OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent } from "./events/types";
|
import {LifeCycleSubType, OB11LifeCycleEvent} from "./event/meta/OB11LifeCycleEvent";
|
||||||
|
import {OB11HeartbeatEvent} from "./event/meta/OB11HeartbeatEvent";
|
||||||
|
|
||||||
let wsServer: websocket.Server = null;
|
let heartbeatRunning = false;
|
||||||
let accessToken = ""
|
let websocketServer = null;
|
||||||
|
|
||||||
const JSONbig = require('json-bigint')({storeAsString: true});
|
export function initWebsocket(port: number) {
|
||||||
const expressAPP = express();
|
const {heartInterval, ob11: {enableWs}, token} = getConfigUtil().getConfig()
|
||||||
let httpServer: http.Server = null;
|
if (!heartbeatRunning) {
|
||||||
expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
|
setInterval(() => {
|
||||||
|
callEvent(new OB11HeartbeatEvent(true, true, heartInterval));
|
||||||
|
}, heartInterval); // 心跳包
|
||||||
|
|
||||||
expressAPP.use((req, res, next) => {
|
heartbeatRunning = true;
|
||||||
let data = '';
|
}
|
||||||
req.on('data', chunk => {
|
if (enableWs) {
|
||||||
data += chunk.toString();
|
if (websocketServer) {
|
||||||
});
|
websocketServer.close((err) => {
|
||||||
req.on('end', () => {
|
log("ws server close failed!", err)
|
||||||
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) => {
|
websocketServer = new websocket.Server({port});
|
||||||
try {
|
console.log(`llonebot websocket service started 0.0.0.0:${port}`);
|
||||||
let token = ""
|
|
||||||
const authHeader = req.get("authorization")
|
websocketServer.on("connection", (ws, req) => {
|
||||||
if (authHeader) {
|
const url = req.url.split("?").shift();
|
||||||
token = authHeader.split("Bearer ").pop()
|
log("receive ws connect", url)
|
||||||
log("receive http header token", token)
|
let clientToken: string = ""
|
||||||
} else if (req.query.access_token) {
|
const authHeader = req.headers['authorization'];
|
||||||
if (Array.isArray(req.query.access_token)) {
|
if (authHeader) {
|
||||||
token = req.query.access_token[0].toString();
|
clientToken = authHeader.split("Bearer ").pop()
|
||||||
|
log("receive ws header token", clientToken);
|
||||||
} else {
|
} else {
|
||||||
token = req.query.access_token.toString();
|
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
|
||||||
|
}
|
||||||
|
log("receive ws url token", clientToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (token && clientToken != token) {
|
||||||
|
ws.send(JSON.stringify(OB11WebsocketResponse.res(null, "failed", 1403, "token验证失败")))
|
||||||
|
return ws.close()
|
||||||
}
|
}
|
||||||
log("receive http url token", token)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
if (url == "/api" || url == "/api/" || url == "/") {
|
||||||
if (token != accessToken) {
|
ws.on("message", async (msg) => {
|
||||||
return res.status(403).send(JSON.stringify({message: 'token verify failed!'}));
|
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
|
||||||
|
let echo = ""
|
||||||
|
log("收到正向Websocket消息", msg.toString())
|
||||||
|
try {
|
||||||
|
receiveData = JSON.parse(msg.toString())
|
||||||
|
echo = receiveData.echo
|
||||||
|
} catch (e) {
|
||||||
|
return wsReply(ws, OB11WebsocketResponse.error("json解析失败,请检查数据格式", 1400, echo))
|
||||||
|
}
|
||||||
|
const action: BaseAction<any, any> = actionMap.get(receiveData.action);
|
||||||
|
if (!action) {
|
||||||
|
return wsReply(ws, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let handleResult = await action.websocketHandle(receiveData.params, echo);
|
||||||
|
wsReply(ws, handleResult)
|
||||||
|
} catch (e) {
|
||||||
|
wsReply(ws, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
if (url == "/event" || url == "/event/" || url == "/") {
|
||||||
}catch (e) {
|
registerEventSender(ws);
|
||||||
log("receive http failed", e.stack)
|
|
||||||
|
log("event上报ws客户端已连接")
|
||||||
|
|
||||||
|
try {
|
||||||
|
wsReply(ws, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT))
|
||||||
|
} catch (e) {
|
||||||
|
log("发送生命周期失败", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.on("close", () => {
|
||||||
|
log("event上报ws客户端已断开")
|
||||||
|
unregisterEventSender(ws);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
next();
|
|
||||||
|
|
||||||
};
|
initReverseWebsocket();
|
||||||
|
|
||||||
export function setToken(token: string) {
|
|
||||||
accessToken = token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startHTTPServer(port: number) {
|
function initReverseWebsocket() {
|
||||||
if (httpServer) {
|
const config = getConfigUtil().getConfig();
|
||||||
httpServer.close();
|
if (config.ob11.enableWsReverse) {
|
||||||
}
|
console.log("Prepare to connect all reverse websockets...");
|
||||||
expressAPP.get('/', (req: Request, res: Response) => {
|
for (const url of config.ob11.wsHosts) {
|
||||||
res.send('LLOneBot已启动');
|
new Promise(() => {
|
||||||
})
|
try {
|
||||||
|
let wsClient = new ReconnectingWebsocket(url);
|
||||||
|
registerEventSender(wsClient);
|
||||||
|
|
||||||
httpServer = expressAPP.listen(port, "0.0.0.0", () => {
|
wsClient.onopen = function () {
|
||||||
console.log(`LLOneBot http server started 0.0.0.0:${port}`);
|
wsReply(wsClient, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
wsClient.onclose = function () {
|
||||||
|
unregisterEventSender(wsClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
wsClient.onmessage = async function (msg) {
|
||||||
|
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
|
||||||
|
let echo = ""
|
||||||
|
log("收到反向Websocket消息", msg.toString())
|
||||||
|
try {
|
||||||
|
receiveData = JSON.parse(msg.toString())
|
||||||
|
echo = receiveData.echo
|
||||||
|
} catch (e) {
|
||||||
|
return wsReply(wsClient, OB11WebsocketResponse.error("json解析失败,请检查数据格式", 1400, echo))
|
||||||
|
}
|
||||||
|
const action: BaseAction<any, any> = actionMap.get(receiveData.action);
|
||||||
|
if (!action) {
|
||||||
|
return wsReply(wsClient, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let handleResult = await action.websocketHandle(receiveData.params, echo);
|
||||||
|
wsReply(wsClient, handleResult)
|
||||||
|
} catch (e) {
|
||||||
|
wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e.stack);
|
||||||
|
}
|
||||||
|
}).then();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let wsEventClients: websocket.WebSocket[] = [];
|
export function wsReply(wsClient: websocket.WebSocket | ReconnectingWebsocket, data: OB11WebsocketResponse | PostMsgType) {
|
||||||
type RouterHandler = (payload: any) => Promise<OB11Return<any>>
|
|
||||||
let routers: Record<string, RouterHandler> = {};
|
|
||||||
|
|
||||||
function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | PostMsgType) {
|
|
||||||
try {
|
try {
|
||||||
wsClient.send(JSON.stringify(data))
|
let packet = Object.assign({
|
||||||
|
echo: ""
|
||||||
|
}, data);
|
||||||
|
if (!packet.echo) {
|
||||||
|
packet.echo = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
wsClient.send(JSON.stringify(packet))
|
||||||
log("ws 消息上报", data)
|
log("ws 消息上报", data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("websocket 回复失败", e)
|
log("websocket 回复失败", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startWSServer(port: number) {
|
export type PostMsgType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent
|
||||||
if (wsServer) {
|
|
||||||
wsServer.close((err) => {
|
|
||||||
log("ws server close failed!", err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
wsServer = new websocket.Server({port})
|
|
||||||
wsServer.on("connection", (ws, req) => {
|
|
||||||
const url = req.url.split("?").shift();
|
|
||||||
log("receive ws connect", url)
|
|
||||||
let token: string = ""
|
|
||||||
const authHeader = req.headers['authorization'];
|
|
||||||
if (authHeader) {
|
|
||||||
token = authHeader.split("Bearer ").pop()
|
|
||||||
log("receive ws header token", token);
|
|
||||||
} else {
|
|
||||||
const parsedUrl = urlParse.parse(req.url, true);
|
|
||||||
const urlToken = parsedUrl.query.access_token;
|
|
||||||
if (urlToken) {
|
|
||||||
if (Array.isArray(urlToken)) {
|
|
||||||
token = urlToken[0]
|
|
||||||
} else {
|
|
||||||
token = urlToken
|
|
||||||
}
|
|
||||||
log("receive ws url token", token);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if (accessToken) {
|
|
||||||
if (token != accessToken) {
|
|
||||||
ws.send(JSON.stringify(OB11Response.res(null, 1403, "token验证失败")))
|
|
||||||
return ws.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (url == "/api" || url == "/api/" || url == "/") {
|
|
||||||
ws.on("message", async (msg) => {
|
|
||||||
|
|
||||||
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
|
|
||||||
let echo = ""
|
|
||||||
log("收到ws消息", msg.toString())
|
|
||||||
try {
|
|
||||||
receiveData = JSON.parse(msg.toString())
|
|
||||||
echo = receiveData.echo
|
|
||||||
} catch (e) {
|
|
||||||
return wsReply(ws, {...OB11Response.error("json解析失败,请检查数据格式"), echo})
|
|
||||||
}
|
|
||||||
const handle: RouterHandler | undefined = routers[receiveData.action]
|
|
||||||
if (!handle) {
|
|
||||||
let handleResult = OB11Response.error("不支持的api " + receiveData.action, 1404)
|
|
||||||
handleResult.echo = echo
|
|
||||||
return wsReply(ws, handleResult)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
let handleResult = await handle(receiveData.params)
|
|
||||||
if (echo){
|
|
||||||
handleResult.echo = echo
|
|
||||||
}
|
|
||||||
wsReply(ws, handleResult)
|
|
||||||
} catch (e) {
|
|
||||||
wsReply(ws, OB11Response.error(`api处理出错:${e}`))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (url == "/event" || url == "/event/" || url == "/") {
|
|
||||||
log("event上报ws客户端已连接")
|
|
||||||
wsEventClients.push(ws)
|
|
||||||
try {
|
|
||||||
wsReply(ws, OB11Constructor.lifeCycleEvent())
|
|
||||||
}catch (e){
|
|
||||||
log("发送生命周期失败", e)
|
|
||||||
}
|
|
||||||
// 心跳
|
|
||||||
let wsHeart = setInterval(()=>{
|
|
||||||
if (wsEventClients.find(c => c == ws)){
|
|
||||||
wsReply(ws, OB11Constructor.heartEvent())
|
|
||||||
}
|
|
||||||
}, heartInterval)
|
|
||||||
ws.on("close", () => {
|
|
||||||
clearInterval(wsHeart);
|
|
||||||
log("event上报ws客户端已断开")
|
|
||||||
wsEventClients = wsEventClients.filter((c) => c != ws)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostMsgType = OB11Message | OB11MetaEvent | OB11NoticeEvent
|
|
||||||
|
|
||||||
export function postMsg(msg: PostMsgType) {
|
export function postMsg(msg: PostMsgType) {
|
||||||
const {reportSelfMessage} = getConfigUtil().getConfig()
|
const config = getConfigUtil().getConfig();
|
||||||
// 判断msg是否是event
|
// 判断msg是否是event
|
||||||
if (!reportSelfMessage) {
|
if (!config.reportSelfMessage) {
|
||||||
if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
|
if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const host of getConfigUtil().getConfig().hosts) {
|
for (const host of config.ob11.httpHosts) {
|
||||||
fetch(host, {
|
fetch(host, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@ -208,41 +193,7 @@ export function postMsg(msg: PostMsgType) {
|
|||||||
log(`新消息事件HTTP上报失败: ${host} ` + err + JSON.stringify(msg));
|
log(`新消息事件HTTP上报失败: ${host} ` + err + JSON.stringify(msg));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const wsClient of wsEventClients) {
|
|
||||||
log("新消息事件ws上报", msg)
|
log("新消息事件ws上报", msg);
|
||||||
new Promise((resolve, reject) => {
|
callEvent(msg);
|
||||||
wsReply(wsClient, msg);
|
|
||||||
}).then();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
routers[action] = handle
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const action of actionHandlers) {
|
|
||||||
registerRouter(action.actionName, (payload) => action.handle(payload))
|
|
||||||
}
|
|
26
src/onebot11/server/http.ts
Normal file
26
src/onebot11/server/http.ts
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { AtType, RawMessage } from "../ntqqapi/types";
|
import {AtType, RawMessage} from "../ntqqapi/types";
|
||||||
|
|
||||||
export interface OB11User {
|
export interface OB11User {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -72,14 +72,14 @@ export interface OB11Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface OB11Return<DataType> {
|
export interface OB11Return<DataType> {
|
||||||
status: number
|
status: string
|
||||||
retcode: number
|
retcode: number
|
||||||
data: DataType
|
data: DataType
|
||||||
message: string,
|
message: string,
|
||||||
echo?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OB11SendMsgReturn extends OB11Return<{ message_id: string }> {
|
export interface OB11WebsocketReturn<DataType> extends OB11Return<DataType>{
|
||||||
|
echo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OB11MessageDataType {
|
export enum OB11MessageDataType {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { CONFIG_DIR, isGIF } from "../common/utils";
|
import {CONFIG_DIR, isGIF} from "../common/utils";
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { NTQQApi } from '../ntqqapi/ntcall';
|
import {OB11MessageData} from "./types";
|
||||||
import { OB11MessageData } from "./types";
|
|
||||||
const fs = require("fs").promises;
|
const fs = require("fs").promises;
|
||||||
|
|
||||||
export async function uri2local(fileName: string, uri: string){
|
export async function uri2local(fileName: string, uri: string){
|
||||||
|
@ -20,5 +20,4 @@ const llonebot = {
|
|||||||
export type LLOneBot = typeof llonebot;
|
export type LLOneBot = typeof llonebot;
|
||||||
|
|
||||||
// 在window对象下导出只读对象
|
// 在window对象下导出只读对象
|
||||||
contextBridge.exposeInMainWorld("llonebot", llonebot);
|
contextBridge.exposeInMainWorld("llonebot", llonebot);
|
||||||
;
|
|
186
src/renderer.ts
186
src/renderer.ts
@ -5,47 +5,103 @@
|
|||||||
async function onSettingWindowCreated(view: Element) {
|
async function onSettingWindowCreated(view: Element) {
|
||||||
window.llonebot.log("setting window created");
|
window.llonebot.log("setting window created");
|
||||||
let config = await window.llonebot.getConfig()
|
let config = await window.llonebot.getConfig()
|
||||||
|
const httpClass = "http";
|
||||||
|
const httpPostClass = "http-post";
|
||||||
|
const wsClass = "ws";
|
||||||
|
const reverseWSClass = "reverse-ws";
|
||||||
|
|
||||||
function creatHostEleStr(host: string) {
|
function createHttpHostEleStr(host: string) {
|
||||||
let eleStr = `
|
let eleStr = `
|
||||||
<setting-item data-direction="row" class="hostItem vertical-list-item">
|
<setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}">
|
||||||
<h2>事件上报地址(http)</h2>
|
<h2>HTTP事件上报地址(http)</h2>
|
||||||
<input class="host input-text" type="text" value="${host}"
|
<input class="httpHost input-text" type="text" value="${host}"
|
||||||
style="width:60%;padding: 5px"
|
style="width:60%;padding: 5px"
|
||||||
placeholder="如果localhost上报失败试试局域网ip"/>
|
placeholder="如果localhost上报失败试试局域网ip"/>
|
||||||
</setting-item>
|
</setting-item>
|
||||||
|
|
||||||
`
|
`
|
||||||
return eleStr
|
return eleStr
|
||||||
}
|
}
|
||||||
|
|
||||||
let hostsEleStr = ""
|
function createWsHostEleStr(host: string) {
|
||||||
for (const host of config.hosts) {
|
let eleStr = `
|
||||||
hostsEleStr += creatHostEleStr(host);
|
<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.ob11.httpHosts) {
|
||||||
|
httpHostsEleStr += createHttpHostEleStr(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
let wsHostsEleStr = ""
|
||||||
|
for (const host of config.ob11.wsHosts) {
|
||||||
|
wsHostsEleStr += createWsHostEleStr(host);
|
||||||
|
}
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
<div class="config_view llonebot">
|
<div class="config_view llonebot">
|
||||||
<setting-section>
|
<setting-section>
|
||||||
<setting-panel>
|
<setting-panel>
|
||||||
<setting-list class="wrap">
|
<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>
|
<setting-text>HTTP监听端口</setting-text>
|
||||||
<input id="port" type="number" value="${config.port}"/>
|
<input id="httpPort" type="number" value="${config.ob11.httpPort}"/>
|
||||||
</setting-item>
|
</setting-item>
|
||||||
<setting-item class="vertical-list-item" data-direction="row">
|
<setting-item data-direction="row" class="hostItem vertical-list-item">
|
||||||
<setting-text>正向ws监听端口</setting-text>
|
<div>
|
||||||
<input id="wsPort" type="number" value="${config.wsPort}"/>
|
<div>启用HTTP事件上报</div>
|
||||||
|
</div>
|
||||||
|
<setting-switch id="httpPost" ${config.ob11.enableHttpPost ? "is-active" : ""}></setting-switch>
|
||||||
</setting-item>
|
</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>
|
||||||
|
<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.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-item class="vertical-list-item" data-direction="row">
|
||||||
<setting-text>Access Token</setting-text>
|
<setting-text>Access Token</setting-text>
|
||||||
<input id="token" type="text" placeholder="可为空" value="${config.token}"/>
|
<input id="token" type="text" placeholder="可为空" value="${config.token}"/>
|
||||||
</setting-item>
|
</setting-item>
|
||||||
<div>
|
|
||||||
<button id="addHost" class="q-button">添加HTTP上报地址</button>
|
|
||||||
</div>
|
|
||||||
<div id="hostItems">
|
|
||||||
${hostsEleStr}
|
|
||||||
</div>
|
|
||||||
<button id="save" class="q-button">保存</button>
|
<button id="save" class="q-button">保存</button>
|
||||||
</setting-list>
|
</setting-list>
|
||||||
</setting-panel>
|
</setting-panel>
|
||||||
@ -74,7 +130,7 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
<setting-item data-direction="row" class="hostItem vertical-list-item">
|
<setting-item data-direction="row" class="hostItem vertical-list-item">
|
||||||
<div>
|
<div>
|
||||||
<div>日志</div>
|
<div>日志</div>
|
||||||
<div class="tips">日志目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
|
<div class="tips">目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
|
||||||
</div>
|
</div>
|
||||||
<setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch>
|
<setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch>
|
||||||
</setting-item>
|
</setting-item>
|
||||||
@ -100,30 +156,57 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
const doc = parser.parseFromString(html, "text/html");
|
const doc = parser.parseFromString(html, "text/html");
|
||||||
|
|
||||||
|
|
||||||
function addHostEle(initValue: string = "") {
|
function addHostEle(type: string, initValue: string = "") {
|
||||||
let addressDoc = parser.parseFromString(creatHostEleStr(initValue), "text/html");
|
let addressEle, hostItemsEle;
|
||||||
let addressEle = addressDoc.querySelector("setting-item")
|
if (type === "ws") {
|
||||||
let hostItemsEle = document.getElementById("hostItems");
|
let addressDoc = parser.parseFromString(createWsHostEleStr(initValue), "text/html");
|
||||||
|
addressEle = addressDoc.querySelector("setting-item")
|
||||||
|
hostItemsEle = document.getElementById("wsHostItems");
|
||||||
|
} else {
|
||||||
|
let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html");
|
||||||
|
addressEle = addressDoc.querySelector("setting-item")
|
||||||
|
hostItemsEle = document.getElementById("httpHostItems");
|
||||||
|
}
|
||||||
|
|
||||||
hostItemsEle.appendChild(addressEle);
|
hostItemsEle.appendChild(addressEle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
doc.getElementById("addHost").addEventListener("click", () => addHostEle())
|
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) => {
|
doc.getElementById(eleId)?.addEventListener("click", (e) => {
|
||||||
const switchEle = e.target as HTMLInputElement
|
const switchEle = e.target as HTMLInputElement
|
||||||
if (config[configKey]) {
|
if (_config[configKey]) {
|
||||||
config[configKey] = false
|
_config[configKey] = false
|
||||||
switchEle.removeAttribute("is-active")
|
switchEle.removeAttribute("is-active")
|
||||||
} else {
|
} else {
|
||||||
config[configKey] = true
|
_config[configKey] = true
|
||||||
switchEle.setAttribute("is-active", "")
|
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)
|
window.llonebot.setConfig(config)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switchClick("http", "enableHttp", config.ob11);
|
||||||
|
switchClick("httpPost", "enableHttpPost", config.ob11);
|
||||||
|
switchClick("websocket", "enableWs", config.ob11);
|
||||||
|
switchClick("websocketReverse", "enableWsReverse", config.ob11);
|
||||||
switchClick("debug", "debug");
|
switchClick("debug", "debug");
|
||||||
switchClick("switchBase64", "enableBase64");
|
switchClick("switchBase64", "enableBase64");
|
||||||
switchClick("reportSelfMessage", "reportSelfMessage");
|
switchClick("reportSelfMessage", "reportSelfMessage");
|
||||||
@ -131,27 +214,35 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
|
|
||||||
doc.getElementById("save")?.addEventListener("click",
|
doc.getElementById("save")?.addEventListener("click",
|
||||||
() => {
|
() => {
|
||||||
const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement
|
const httpPortEle: HTMLInputElement = document.getElementById("httpPort") as HTMLInputElement;
|
||||||
const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement
|
const httpHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("httpHost") as HTMLCollectionOf<HTMLInputElement>;
|
||||||
const hostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>;
|
const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement;
|
||||||
|
const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("wsHost") as HTMLCollectionOf<HTMLInputElement>;
|
||||||
const tokenEle = document.getElementById("token") as HTMLInputElement;
|
const tokenEle = document.getElementById("token") as HTMLInputElement;
|
||||||
// const port = doc.querySelector("input[type=number]")?.value
|
|
||||||
// const host = doc.querySelector("input[type=text]")?.value
|
|
||||||
// 获取端口和host
|
|
||||||
const port = portEle.value
|
|
||||||
const wsPort = wsPortEle.value
|
|
||||||
const token = tokenEle.value
|
|
||||||
|
|
||||||
let hosts: string[] = [];
|
// 获取端口和host
|
||||||
for (const hostEle of hostEles) {
|
const httpPort = httpPortEle.value
|
||||||
if (hostEle.value) {
|
let httpHosts: string[] = [];
|
||||||
hosts.push(hostEle.value.trim());
|
|
||||||
}
|
for (const hostEle of httpHostEles) {
|
||||||
|
const value = hostEle.value.trim();
|
||||||
|
value && httpHosts.push(value);
|
||||||
}
|
}
|
||||||
config.port = parseInt(port);
|
|
||||||
config.wsPort = parseInt(wsPort);
|
const wsPort = wsPortEle.value;
|
||||||
config.hosts = hosts;
|
const token = tokenEle.value.trim();
|
||||||
config.token = token.trim();
|
let wsHosts: string[] = [];
|
||||||
|
|
||||||
|
for (const hostEle of wsHostEles) {
|
||||||
|
const value = hostEle.value.trim();
|
||||||
|
value && wsHosts.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
window.llonebot.setConfig(config);
|
||||||
alert("保存成功");
|
alert("保存成功");
|
||||||
})
|
})
|
||||||
@ -161,7 +252,6 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
view.appendChild(node);
|
view.appendChild(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
6
src/server/base.ts
Normal file
6
src/server/base.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export abstract class ServerBase{
|
||||||
|
abstract start: () => void
|
||||||
|
abstract restart: ()=>void
|
||||||
|
}
|
106
src/server/http.ts
Normal file
106
src/server/http.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user