Compare commits

...

8 Commits

Author SHA1 Message Date
linyuchen
42d6f1528a Merge branch 'dev'
# Conflicts:
#	manifest.json
#	src/common/data.ts
2024-02-17 20:07:13 +08:00
linyuchen
1a1d673c8c feat: face msg
feat: recall notice
2024-02-17 20:06:17 +08:00
linyuchen
06ad92b846 ver: 3.2.2 2024-02-17 01:44:21 +08:00
linyuchen
df5968ccc1 Merge pull request #47 from YuChuXi/patch-1
fix get_group_info
2024-02-17 01:43:12 +08:00
linyuchen
ba387b40ca 暂存 2024-02-17 01:42:14 +08:00
YuChuXi
e554d805b5 修东西
fix: get_group_info和get_group_list都返回群列表
2024-02-17 01:39:06 +08:00
linyuchen
d54111ce94 fix: ws url token parse 2024-02-16 22:48:43 +08:00
linyuchen
4f9682289c feat: api /get_version_info
feat: api /can_send_image
feat: api /can_send_record
feat: ws heart & lifecycle
2024-02-16 21:32:37 +08:00
26 changed files with 539 additions and 143 deletions

View File

@@ -57,6 +57,9 @@ LiteLoaderQQNT的OneBot11协议插件
- [x] get_group_member_info - [x] get_group_member_info
- [x] get_friend_list - [x] get_friend_list
- [x] get_msg - [x] get_msg
- [x] get_version_info
- [x] can_send_image
- [x] can_send_record
## 示例 ## 示例

View File

@@ -1,31 +1,33 @@
{ {
"manifest_version": 4, "manifest_version": 4,
"type": "extension", "type": "extension",
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "LiteLoaderQQNT的OneBotApi", "description": "LiteLoaderQQNT的OneBotApi",
"version": "3.1.2", "version": "3.3.0",
"thumbnail": "./icon.png", "thumbnail": "./icon.png",
"authors": [{ "authors": [
"name": "linyuchen", {
"link": "https://github.com/linyuchen" "name": "linyuchen",
}], "link": "https://github.com/linyuchen"
"repository": { }
],
"repository": {
"repo": "linyuchen/LiteLoaderQQNT-OneBotApi", "repo": "linyuchen/LiteLoaderQQNT-OneBotApi",
"branch": "main", "branch": "main",
"release": { "release": {
"tag": "latest", "tag": "latest",
"name": "LLOneBot.zip" "name": "LLOneBot.zip"
}
},
"platform": [
"win32",
"linux",
"darwin"
],
"injects": {
"renderer": "./renderer.js",
"main": "./main.js",
"preload": "./preload.js"
} }
},
"platform": [
"win32",
"linux",
"darwin"
],
"injects": {
"renderer": "./renderer.js",
"main": "./main.js",
"preload": "./preload.js"
}
} }

View File

@@ -10,17 +10,36 @@ export class ConfigUtil {
} }
getConfig(): Config { getConfig(): Config {
let defaultConfig: Config = {
port: 3000,
wsPort: 3001,
hosts: [],
token: "",
enableBase64: false,
debug: false,
log: false,
reportSelfMessage: false
}
if (!fs.existsSync(this.configPath)) { if (!fs.existsSync(this.configPath)) {
return {port: 3000, hosts: ["http://192.168.1.2:5000/"], wsPort: 3001} return defaultConfig
} else { } else {
const data = fs.readFileSync(this.configPath, "utf-8"); const data = fs.readFileSync(this.configPath, "utf-8");
let jsonData = JSON.parse(data); let jsonData: Config = defaultConfig;
try {
jsonData = JSON.parse(data)
}
catch (e){
}
if (!jsonData.hosts) { if (!jsonData.hosts) {
jsonData.hosts = [] jsonData.hosts = []
} }
if (!jsonData.wsPort){ if (!jsonData.wsPort){
jsonData.wsPort = 3001 jsonData.wsPort = 3001
} }
if (!jsonData.token){
jsonData.token = ""
}
return jsonData; return jsonData;
} }
} }

View File

@@ -86,4 +86,7 @@ export function getStrangerByUin(uin: string) {
return uidMaps[key]; return uidMaps[key];
} }
} }
} }
export const version = "v3.3.0"
export const heartInterval = 15000 // 毫秒

View File

@@ -2,6 +2,7 @@ export interface Config {
port: number port: number
wsPort: number wsPort: number
hosts: string[] hosts: string[]
token?: string
enableBase64?: boolean enableBase64?: boolean
debug?: boolean debug?: boolean
reportSelfMessage?: boolean reportSelfMessage?: boolean

View File

@@ -33,7 +33,7 @@ export function log(...msg: any[]) {
} }
logMsg += msgItem + " "; logMsg += msgItem + " ";
} }
logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n` logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n`
// sendLog(...msg); // sendLog(...msg);
// console.log(msg) // console.log(msg)
fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), logMsg, (err: any) => { fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), logMsg, (err: any) => {

View File

@@ -1,22 +1,16 @@
// 运行在 Electron 主进程 下的插件入口 // 运行在 Electron 主进程 下的插件入口
import * as path from "path";
import { BrowserWindow, ipcMain } from 'electron'; import { BrowserWindow, ipcMain } from 'electron';
import * as util from 'util';
import { Config } from "../common/types"; import { Config } from "../common/types";
import { import { CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG, } from "../common/channels";
CHANNEL_GET_CONFIG, import { postMsg, setToken, startHTTPServer, startWSServer } from "../onebot11/server";
CHANNEL_LOG,
CHANNEL_SET_CONFIG,
} from "../common/channels";
import { postMsg, startHTTPServer, startWSServer } from "../onebot11/server";
import { CONFIG_DIR, getConfigUtil, log } from "../common/utils"; import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
import { addHistoryMsg, msgHistory, selfInfo } from "../common/data"; import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data";
import { 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 { Group, RawMessage, SelfInfo } from "../ntqqapi/types"; import { ChatType, RawMessage } from "../ntqqapi/types";
const fs = require('fs'); const fs = require('fs');
@@ -39,12 +33,15 @@ 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.port != oldConfig.port) {
startHTTPServer(arg.port) startHTTPServer(arg.port)
} }
if (arg.wsPort != oldConfig.wsPort){ if (arg.wsPort != oldConfig.wsPort) {
startWSServer(arg.wsPort) startWSServer(arg.wsPort)
} }
if (arg.token != oldConfig.token) {
setToken(arg.token);
}
}) })
ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => { ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
@@ -55,6 +52,7 @@ function onLoad() {
function postRawMsg(msgList: RawMessage[]) { function postRawMsg(msgList: RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig(); const {debug, reportSelfMessage} = getConfigUtil().getConfig();
for (let message of msgList) { for (let message of msgList) {
log("收到新消息", message)
message.msgShortId = msgHistory[message.msgId]?.msgShortId message.msgShortId = msgHistory[message.msgId]?.msgShortId
if (!message.msgShortId) { if (!message.msgShortId) {
addHistoryMsg(message) addHistoryMsg(message)
@@ -73,7 +71,7 @@ function onLoad() {
} }
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); // log("received msg length", payload.msgList.length);
@@ -82,7 +80,38 @@ function onLoad() {
log("report message error: ", e.toString()) log("report message error: ", e.toString())
} }
}) })
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
for (const message of payload.msgList) {
// log("message update", message, message.sendStatus)
if (message.sendStatus === 2) {
// 撤回消息上报
const oriMessage = msgHistory[message.msgId]
if (!oriMessage) {
continue
}
if (message.chatType == ChatType.friend) {
const friendRecallEvent = OB11Constructor.friendRecallEvent(message.senderUin, oriMessage.msgShortId)
postMsg(friendRecallEvent)
} else if (message.chatType == ChatType.group) {
let operatorId = message.senderUin
for (const element of message.elements) {
const operatorUid = element.grayTipElement?.revokeElement.operatorUid
const operator = await getGroupMember(message.peerUin, null, operatorUid)
operatorId = operator.uin
}
const groupRecallEvent = OB11Constructor.groupRecallEvent(
message.peerUin,
message.senderUin,
operatorId,
oriMessage.msgShortId
)
postMsg(groupRecallEvent)
}
continue
}
addHistoryMsg(message)
}
})
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) {
@@ -99,6 +128,7 @@ function onLoad() {
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
startHTTPServer(config.port) startHTTPServer(config.port)
startWSServer(config.wsPort) startWSServer(config.wsPort)
setToken(config.token)
log("LLOneBot start") log("LLOneBot start")
} }
@@ -124,9 +154,8 @@ function onLoad() {
log("get self nickname failed", e.toString()) log("get self nickname failed", e.toString())
return setTimeout(init, 1000) return setTimeout(init, 1000)
} }
start(); start().then();
} } else {
else{
setTimeout(init, 1000) setTimeout(init, 1000)
} }
} }

View File

@@ -1,5 +1,13 @@
import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, AtType} from "./types"; import {
import {NTQQApi} from "./ntcall"; ElementType,
SendPicElement,
SendPttElement,
SendReplyElement,
SendTextElement,
AtType,
SendFaceElement
} from "./types";
import { NTQQApi } from "./ntcall";
export class SendMsgElementConstructor { export class SendMsgElementConstructor {
@@ -44,7 +52,7 @@ export class SendMsgElementConstructor {
} }
} }
static async pic(picPath: string): Promise<SendPicElement>{ static async pic(picPath: string): Promise<SendPicElement> {
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath); const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath);
const imageSize = await NTQQApi.getImageSize(picPath); const imageSize = await NTQQApi.getImageSize(picPath);
const picElement = { const picElement = {
@@ -70,7 +78,7 @@ export class SendMsgElementConstructor {
}; };
} }
static async ptt(pttPath: string):Promise<SendPttElement> { static async ptt(pttPath: string): Promise<SendPttElement> {
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath); const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath);
return { return {
elementType: ElementType.PTT, elementType: ElementType.PTT,
@@ -94,4 +102,15 @@ export class SendMsgElementConstructor {
} }
}; };
} }
static face(faceId: number): SendFaceElement {
return {
elementType: ElementType.FACE,
elementId: "",
faceElement: {
faceIndex: faceId,
faceType: 1
}
}
}
} }

View File

@@ -34,7 +34,7 @@ interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
let receiveHooks: Array<{ let receiveHooks: Array<{
method: ReceiveCmd, method: ReceiveCmd,
hookFunc: (payload: any) => void, hookFunc: ((payload: any) => void | Promise<void>)
id: string id: string
}> = [] }> = []
@@ -50,7 +50,10 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
if (hook.method === ntQQApiMethodName) { if (hook.method === ntQQApiMethodName) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
try { try {
hook.hookFunc(receiveData.payload); let _ = hook.hookFunc(receiveData.payload)
if (hook.hookFunc.constructor.name === "AsyncFunction"){
(_ as Promise<void>).then()
}
} catch (e) { } catch (e) {
log("hook error", e, receiveData.payload) log("hook error", e, receiveData.payload)
} }
@@ -129,11 +132,7 @@ registerReceiveHook<{
// log("user info", payload); // log("user info", payload);
// }) // })
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payload) => {
for (const message of payload.msgList) {
addHistoryMsg(message)
}
})
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) {

View File

@@ -7,13 +7,14 @@ export interface User {
remark?: string remark?: string
} }
export interface SelfInfo extends User{ export interface SelfInfo extends User {
} }
export interface Friend extends User{} export interface Friend extends User {
}
export interface Group{ export interface Group {
groupCode: string, groupCode: string,
maxMember: number, maxMember: number,
memberCount: number, memberCount: number,
@@ -62,6 +63,7 @@ export enum ElementType {
TEXT = 1, TEXT = 1,
PIC = 2, PIC = 2,
PTT = 4, PTT = 4,
FACE = 6,
REPLY = 7, REPLY = 7,
} }
@@ -76,6 +78,7 @@ export interface SendTextElement {
atNtUid: string, atNtUid: string,
} }
} }
export interface SendPttElement { export interface SendPttElement {
elementType: ElementType.PTT, elementType: ElementType.PTT,
elementId: "", elementId: "",
@@ -127,7 +130,13 @@ export interface SendReplyElement {
} }
} }
export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement export interface SendFaceElement {
elementType: ElementType.FACE,
elementId: "",
faceElement: FaceElement
}
export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement | SendFaceElement
export enum AtType { export enum AtType {
notAt = 0, notAt = 0,
@@ -140,6 +149,7 @@ export enum ChatType {
group = 2, group = 2,
temp = 100 temp = 100
} }
export interface PttElement { export interface PttElement {
canConvert2Text: boolean; canConvert2Text: boolean;
duration: number; // 秒数 duration: number; // 秒数
@@ -180,6 +190,22 @@ export interface PicElement {
fileUuid: string; fileUuid: string;
} }
export interface GrayTipElement {
revokeElement: {
operatorRole: string;
operatorUid: string;
operatorNick: string;
operatorRemark: string;
operatorMemRemark?: string;
wording: string; // 自定义的撤回提示语
}
}
export interface FaceElement {
faceIndex: number,
faceType: 1
}
export interface RawMessage { export interface RawMessage {
msgId: string; msgId: string;
msgShortId?: number; // 自己维护的消息id msgShortId?: number; // 自己维护的消息id
@@ -191,6 +217,7 @@ export interface RawMessage {
sendNickName: string; sendNickName: string;
sendMemberName?: string; // 发送者群名片 sendMemberName?: string; // 发送者群名片
chatType: ChatType; chatType: ChatType;
sendStatus?: number; // 消息状态2是已撤回
elements: { elements: {
elementId: string, elementId: string,
replyElement: { replyElement: {
@@ -208,17 +235,7 @@ export interface RawMessage {
picElement: PicElement; picElement: PicElement;
pttElement: PttElement; pttElement: PttElement;
arkElement: ArkElement; arkElement: ArkElement;
grayTipElement: GrayTipElement;
faceElement: FaceElement;
}[]; }[];
} }
export interface MessageElement {
raw: RawMessage;
peer: any;
sender: {
uid: string; // 一串加密的字符串
memberName: string;
nickname: string;
};
}

View File

@@ -0,0 +1,10 @@
import { ActionName } from "./types";
import CanSendRecord from "./CanSendRecord";
interface ReturnType{
yes: boolean
}
export default class CanSendImage extends CanSendRecord{
actionName = ActionName.CanSendImage
}

View File

@@ -0,0 +1,16 @@
import BaseAction from "./BaseAction";
import { ActionName } from "./types";
interface ReturnType{
yes: boolean
}
export default class CanSendRecord extends BaseAction<any, ReturnType>{
actionName = ActionName.CanSendRecord
protected async _handle(payload): Promise<ReturnType>{
return {
yes: true
}
}
}

View File

@@ -1,5 +1,5 @@
import { OB11Group } from '../types'; import { OB11Group } from '../types';
import { getGroup, groups } 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";
@@ -8,17 +8,17 @@ interface PayloadType {
group_id: number group_id: number
} }
class GetGroupInfo extends BaseAction<PayloadType, OB11Group[]> { class GetGroupInfo extends BaseAction<PayloadType, OB11Group> {
actionName = ActionName.GetGroupInfo actionName = ActionName.GetGroupInfo
protected async _handle(payload: PayloadType) { protected async _handle(payload: PayloadType) {
const group = await getGroup(payload.group_id.toString()) const group = await getGroup(payload.group_id.toString())
if (group) { if (group) {
return OB11Constructor.groups(groups) return OB11Constructor.group(group)
} else { } else {
throw `${payload.group_id}不存在` throw `${payload.group_id}不存在`
} }
} }
} }
export default GetGroupInfo export default GetGroupInfo

View File

@@ -17,6 +17,9 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
protected async _handle(payload: PayloadType){ protected async _handle(payload: PayloadType){
// log("history msg ids", Object.keys(msgHistory)); // log("history msg ids", Object.keys(msgHistory));
if (!payload.message_id){
throw("参数message_id不能为空")
}
const msg = getHistoryMsgByShortId(payload.message_id) const msg = getHistoryMsgByShortId(payload.message_id)
if (msg) { if (msg) {
const msgData = await OB11Constructor.message(msg); const msgData = await OB11Constructor.message(msg);

View File

@@ -0,0 +1,14 @@
import BaseAction from "./BaseAction";
import {OB11Status} from "../types";
import { ActionName } from "./types";
export default class GetStatus extends BaseAction<any, OB11Status> {
actionName = ActionName.GetStatus
protected async _handle(payload: any): Promise<OB11Status> {
return {
online: null,
good: true
}
}
}

View File

@@ -0,0 +1,15 @@
import BaseAction from "./BaseAction";
import { OB11Version } from "../types";
import {version} from "../../common/data";
import { ActionName } from "./types";
export default class GetVersionInfo extends BaseAction<any, OB11Version>{
actionName = ActionName.GetVersionInfo
protected async _handle(payload: any): Promise<OB11Version> {
return {
app_name: "LLOneBot",
protocol_version: "v11",
app_version: version
}
}
}

View File

@@ -1,19 +1,10 @@
import { AtType, ChatType, Group } from "../../ntqqapi/types"; import { AtType, ChatType, Group, SendMessageElement } from "../../ntqqapi/types";
import { import { addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getStrangerByUin, } from "../../common/data";
addHistoryMsg,
friends,
getGroup,
getHistoryMsgByShortId,
getStrangerByUin,
} from "../../common/data";
import { OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types'; import { OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types';
import { NTQQApi } from "../../ntqqapi/ntcall"; import { NTQQApi, Peer } from "../../ntqqapi/ntcall";
import { Peer } from "../../ntqqapi/ntcall";
import { SendMessageElement } from "../../ntqqapi/types";
import { SendMsgElementConstructor } from "../../ntqqapi/constructor"; import { SendMsgElementConstructor } from "../../ntqqapi/constructor";
import { uri2local } from "../utils"; import { uri2local } from "../utils";
import { v4 as uuid4 } from 'uuid'; import { v4 as uuid4 } from 'uuid';
import { log } from "../../common/utils";
import BaseAction from "./BaseAction"; import BaseAction from "./BaseAction";
import { ActionName } from "./types"; import { ActionName } from "./types";
import * as fs from "fs"; import * as fs from "fs";
@@ -25,7 +16,7 @@ export interface ReturnDataType {
class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> { class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
actionName = ActionName.SendMsg actionName = ActionName.SendMsg
protected async _handle(payload: OB11PostSendMsg){ protected async _handle(payload: OB11PostSendMsg) {
const peer: Peer = { const peer: Peer = {
chatType: ChatType.friend, chatType: ChatType.friend,
peerUid: "" peerUid: ""
@@ -40,18 +31,16 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
peer.chatType = ChatType.group peer.chatType = ChatType.group
// peer.name = group.name // peer.name = group.name
peer.peerUid = group.groupCode peer.peerUid = group.groupCode
} } else if (payload?.user_id) {
else if (payload?.user_id) {
const friend = friends.find(f => f.uin == payload.user_id.toString()) const friend = friends.find(f => f.uin == payload.user_id.toString())
if (friend) { if (friend) {
// peer.name = friend.nickName // peer.name = friend.nickName
peer.peerUid = friend.uid peer.peerUid = friend.uid
} } else {
else {
peer.chatType = ChatType.temp peer.chatType = ChatType.temp
const tempUser = getStrangerByUin(payload.user_id.toString()) const tempUser = getStrangerByUin(payload.user_id.toString())
if (!tempUser) { if (!tempUser) {
throw(`找不到私聊对象${payload.user_id}`) throw (`找不到私聊对象${payload.user_id}`)
} }
// peer.name = tempUser.nickName // peer.name = tempUser.nickName
peer.peerUid = tempUser.uid peer.peerUid = tempUser.uid
@@ -64,8 +53,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
text: payload.message text: payload.message
} }
}] as OB11MessageData[] }] as OB11MessageData[]
} } else if (!Array.isArray(payload.message)) {
else if (!Array.isArray(payload.message)) {
payload.message = [payload.message] payload.message = [payload.message]
} }
const sendElements: SendMessageElement[] = [] const sendElements: SendMessageElement[] = []
@@ -76,22 +64,23 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (text) { if (text) {
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text)) sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
} }
} break; }
break;
case OB11MessageDataType.at: { case OB11MessageDataType.at: {
let atQQ = sendMsg.data?.qq; let atQQ = sendMsg.data?.qq;
if (atQQ) { if (atQQ) {
atQQ = atQQ.toString() atQQ = atQQ.toString()
if (atQQ === "all") { if (atQQ === "all") {
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员")) sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
} } else {
else {
const atMember = group?.members.find(m => m.uin == atQQ) const atMember = group?.members.find(m => m.uin == atQQ)
if (atMember) { if (atMember) {
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick)) sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
} }
} }
} }
} break; }
break;
case OB11MessageDataType.reply: { case OB11MessageDataType.reply: {
let replyMsgId = sendMsg.data.id; let replyMsgId = sendMsg.data.id;
if (replyMsgId) { if (replyMsgId) {
@@ -101,20 +90,27 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin, replyMsg.senderUin)) sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin, replyMsg.senderUin))
} }
} }
} break; }
break;
case OB11MessageDataType.face: {
const faceId = sendMsg.data?.id
if (faceId) {
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
}
}
break;
case OB11MessageDataType.image: case OB11MessageDataType.image:
case OB11MessageDataType.voice: { case OB11MessageDataType.voice: {
const file = sendMsg.data?.file const file = sendMsg.data?.file
if (file) { if (file) {
const {path, isLocal} = (await uri2local(uuid4(), file)) const {path, isLocal} = (await uri2local(uuid4(), file))
if (path) { if (path) {
if (!isLocal){ // 只删除http和base64转过来的文件 if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path) deleteAfterSentFiles.push(path)
} }
if (sendMsg.type === OB11MessageDataType.image){ if (sendMsg.type === OB11MessageDataType.image) {
sendElements.push(await SendMsgElementConstructor.pic(path)) sendElements.push(await SendMsgElementConstructor.pic(path))
} } else {
else {
sendElements.push(await SendMsgElementConstructor.ptt(path)) sendElements.push(await SendMsgElementConstructor.ptt(path))
} }
} }
@@ -126,10 +122,11 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
try { try {
const returnMsg = await NTQQApi.sendMsg(peer, sendElements) const returnMsg = await NTQQApi.sendMsg(peer, sendElements)
addHistoryMsg(returnMsg) addHistoryMsg(returnMsg)
deleteAfterSentFiles.map(f=>fs.unlink(f, ()=>{})) deleteAfterSentFiles.map(f => fs.unlink(f, () => {
return { message_id: returnMsg.msgShortId } }))
return {message_id: returnMsg.msgShortId}
} catch (e) { } catch (e) {
throw(e.toString()) throw (e.toString())
} }
} }
} }

View File

@@ -9,6 +9,10 @@ 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 GetVersionInfo from "./GetVersionInfo";
import CanSendRecord from "./CanSendRecord";
import CanSendImage from "./CanSendImage";
import GetStatus from "./GetStatus";
export const actionHandlers = [ export const actionHandlers = [
new GetMsg(), new GetMsg(),
@@ -16,5 +20,9 @@ export const actionHandlers = [
new GetFriendList(), new GetFriendList(),
new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(), new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(),
new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(), new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(),
new DeleteMsg() new DeleteMsg(),
new GetVersionInfo(),
new CanSendRecord(),
new CanSendImage(),
new GetStatus()
] ]

View File

@@ -1,3 +1,5 @@
import GetVersionInfo from "./GetVersionInfo";
export type BaseCheckResult = ValidCheckResult | InvalidCheckResult export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
export interface ValidCheckResult { export interface ValidCheckResult {
@@ -22,5 +24,9 @@ export enum ActionName{
SendMsg = "send_msg", SendMsg = "send_msg",
SendGroupMsg = "send_group_msg", SendGroupMsg = "send_group_msg",
SendPrivateMsg = "send_private_msg", SendPrivateMsg = "send_private_msg",
DeleteMsg = "delete_msg" DeleteMsg = "delete_msg",
GetVersionInfo = "get_version_info",
GetStatus = "get_status",
CanSendRecord = "can_send_record",
CanSendImage = "can_send_image",
} }

View File

@@ -1,18 +1,19 @@
import { OB11Return } from '../types'; import { OB11Return } from '../types';
export class OB11Response { export class OB11Response {
static res<T>(data: T, status: number = 0, message: string = ""): OB11Return<T> { static res<T>(data: T, status: number = 0, message: string = "", echo=""): OB11Return<T> {
return { return {
status: status, status: status,
retcode: status, retcode: status,
data: data, data: data,
message: message message: message,
echo,
} }
} }
static ok<T>(data: T) { static ok<T>(data: T) {
return OB11Response.res<T>(data) return OB11Response.res<T>(data)
} }
static error(err: string) { static error(err: string, status=-1) {
return OB11Response.res(null, -1, err) return OB11Response.res(null, status, err)
} }
} }

View File

@@ -7,12 +7,13 @@ import {
OB11User OB11User
} from "./types"; } from "./types";
import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types'; import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
import { getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo } from '../common/data'; import { getFriend, getGroupMember, getHistoryMsgBySeq, heartInterval, msgHistory, selfInfo } from '../common/data';
import { file2base64, getConfigUtil, log } from "../common/utils"; import { file2base64, getConfigUtil, log } from "../common/utils";
import { NTQQApi } from "../ntqqapi/ntcall"; import { NTQQApi } from "../ntqqapi/ntcall";
import {OB11EventConstructor} from "./events/constructor";
export class OB11Constructor { export class OB11Constructor extends OB11EventConstructor{
static async message(msg: RawMessage): Promise<OB11Message> { static async message(msg: RawMessage): Promise<OB11Message> {
const {enableBase64} = getConfigUtil().getConfig() const {enableBase64} = getConfigUtil().getConfig()
@@ -41,6 +42,7 @@ export class OB11Constructor {
const member = await getGroupMember(msg.peerUin, msg.senderUin); const member = await getGroupMember(msg.peerUin, msg.senderUin);
if (member) { if (member) {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role); resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
resMsg.sender.nickname = member.nick
} }
} else if (msg.chatType == ChatType.friend) { } else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = "friend" resMsg.sub_type = "friend"
@@ -111,6 +113,9 @@ export class OB11Constructor {
} else if (element.arkElement) { } else if (element.arkElement) {
message_data["type"] = OB11MessageDataType.json; message_data["type"] = OB11MessageDataType.json;
message_data["data"]["data"] = element.arkElement.bytesData; message_data["data"]["data"] = element.arkElement.bytesData;
} else if (element.faceElement){
message_data["type"] = OB11MessageDataType.face;
message_data["data"]["id"] = element.faceElement.faceIndex.toString();
} }
if (message_data.data.http_file) { if (message_data.data.http_file) {
message_data.data.file = message_data.data.http_file message_data.data.file = message_data.data.http_file

View File

@@ -0,0 +1,58 @@
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
}
}
}

View File

@@ -0,0 +1,67 @@
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
}

View File

@@ -1,17 +1,21 @@
import * as http from "http"; import * as http from "http";
import * as websocket from "ws"; import * as websocket from "ws";
import urlParse from "url";
import express from "express"; import express from "express";
import { Request } from 'express'; import { Request } from 'express';
import { Response } from 'express'; import { Response } from 'express';
import { getConfigUtil, log } from "../common/utils"; import { getConfigUtil, log } from "../common/utils";
import { selfInfo } from "../common/data"; import { heartInterval, selfInfo } from "../common/data";
import { OB11Message, OB11Return, OB11MessageData } from './types'; import { OB11Message, OB11Return, OB11MessageData } from './types';
import { actionHandlers } from "./actions"; import { actionHandlers } from "./actions";
import { OB11Response } from "./actions/utils"; import { OB11Response } from "./actions/utils";
import { ActionName } from "./actions/types"; import { ActionName } from "./actions/types";
import BaseAction from "./actions/BaseAction"; import BaseAction from "./actions/BaseAction";
import { OB11Constructor } from "./constructor";
import { OB11EventBase, OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent } from "./events/types";
let wsServer: websocket.Server = null; let wsServer: websocket.Server = null;
let accessToken = ""
const JSONbig = require('json-bigint')({storeAsString: true}); const JSONbig = require('json-bigint')({storeAsString: true});
const expressAPP = express(); const expressAPP = express();
@@ -36,6 +40,33 @@ expressAPP.use((req, res, next) => {
}); });
}); });
const expressAuthorize = (req: Request, res: Response, next: () => void) => {
let token = ""
const authHeader = req.get("authorization")
if (authHeader) {
token = authHeader.split("Bearer ").pop()
log("receive http header token", token)
} else if (req.query.access_token) {
if (Array.isArray(req.query.access_token)) {
token = req.query.access_token[0].toString();
} else {
token = req.query.access_token.toString();
}
log("receive http url token", token)
}
if (accessToken) {
if (token != accessToken) {
return res.status(403).send(JSON.stringify({message: 'token verify failed!'}));
}
}
next();
};
export function setToken(token: string) {
accessToken = token
}
export function startHTTPServer(port: number) { export function startHTTPServer(port: number) {
if (httpServer) { if (httpServer) {
@@ -54,9 +85,10 @@ let wsEventClients: websocket.WebSocket[] = [];
type RouterHandler = (payload: any) => Promise<OB11Return<any>> type RouterHandler = (payload: any) => Promise<OB11Return<any>>
let routers: Record<string, RouterHandler> = {}; let routers: Record<string, RouterHandler> = {};
function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Message) { function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | PostMsgType) {
try { try {
wsClient.send(JSON.stringify(data)) wsClient.send(JSON.stringify(data))
log("ws 消息上报", data)
} catch (e) { } catch (e) {
log("websocket 回复失败", e) log("websocket 回复失败", e)
} }
@@ -64,31 +96,61 @@ function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Mess
export function startWSServer(port: number) { export function startWSServer(port: number) {
if (wsServer) { if (wsServer) {
wsServer.close((err)=>{ wsServer.close((err) => {
log("ws server close failed!", err) log("ws server close failed!", err)
}) })
} }
wsServer = new websocket.Server({port}) wsServer = new websocket.Server({port})
wsServer.on("connection", (ws, req) => { wsServer.on("connection", (ws, req) => {
const url = req.url; const url = req.url.split("?").shift();
// ws.send('Welcome to the LLOneBot WebSocket server! url:' + url); 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 == "/") { if (url == "/api" || url == "/api/" || url == "/") {
ws.on("message", async (msg) => { ws.on("message", async (msg) => {
let receiveData: { action: ActionName, params: any } = {action: null, params: {}} let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
let echo = ""
log("收到ws消息", msg.toString()) log("收到ws消息", msg.toString())
try { try {
receiveData = JSON.parse(msg.toString()) receiveData = JSON.parse(msg.toString())
echo = receiveData.echo
} catch (e) { } catch (e) {
return wsReply(ws, OB11Response.error("json解析失败请检查数据格式")) return wsReply(ws, {...OB11Response.error("json解析失败请检查数据格式"), echo})
} }
const handle: RouterHandler | undefined = routers[receiveData.action] const handle: RouterHandler | undefined = routers[receiveData.action]
if (!handle) { if (!handle) {
return wsReply(ws, OB11Response.error("不支持的api " + receiveData.action)) let handleResult = OB11Response.error("不支持的api " + receiveData.action, 1404)
handleResult.echo = echo
return wsReply(ws, handleResult)
} }
try { try {
const handleResult = await handle(receiveData.params) let handleResult = await handle(receiveData.params)
if (echo){
handleResult.echo = echo
}
wsReply(ws, handleResult) wsReply(ws, handleResult)
} catch (e) { } catch (e) {
wsReply(ws, OB11Response.error(`api处理出错:${e}`)) wsReply(ws, OB11Response.error(`api处理出错:${e}`))
@@ -98,7 +160,19 @@ export function startWSServer(port: number) {
if (url == "/event" || url == "/event/" || url == "/") { if (url == "/event" || url == "/event/" || url == "/") {
log("event上报ws客户端已连接") log("event上报ws客户端已连接")
wsEventClients.push(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", () => { ws.on("close", () => {
clearInterval(wsHeart);
log("event上报ws客户端已断开") log("event上报ws客户端已断开")
wsEventClients = wsEventClients.filter((c) => c != ws) wsEventClients = wsEventClients.filter((c) => c != ws)
}) })
@@ -106,11 +180,13 @@ export function startWSServer(port: number) {
}) })
} }
type PostMsgType = OB11Message | OB11MetaEvent | OB11NoticeEvent
export function postMsg(msg: OB11Message) { export function postMsg(msg: PostMsgType) {
const {reportSelfMessage} = getConfigUtil().getConfig() const {reportSelfMessage} = getConfigUtil().getConfig()
// 判断msg是否是event
if (!reportSelfMessage) { if (!reportSelfMessage) {
if (msg.user_id.toString() == selfInfo.uin) { if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
return return
} }
} }
@@ -154,11 +230,11 @@ function registerRouter(action: string, handle: (payload: any) => Promise<any>)
} }
} }
expressAPP.post(url, (req: Request, res: Response) => { expressAPP.post(url, expressAuthorize, (req: Request, res: Response) => {
_handle(res, req.body).then() _handle(res, req.body || {}).then()
}); });
expressAPP.get(url, (req: Request, res: Response) => { expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => {
_handle(res, req.query as any).then() _handle(res, req.query as any || {}).then()
}); });
routers[action] = handle routers[action] = handle
} }

View File

@@ -1,19 +1,18 @@
import { AtType } from "../ntqqapi/types"; import { AtType, RawMessage } from "../ntqqapi/types";
import { RawMessage } from "../ntqqapi/types";
export interface OB11User{ export interface OB11User {
user_id: number; user_id: number;
nickname: string; nickname: string;
remark?: string remark?: string
} }
export enum OB11UserSex{ export enum OB11UserSex {
male = "male", male = "male",
female = "female", female = "female",
unknown = "unknown" unknown = "unknown"
} }
export enum OB11GroupMemberRole{ export enum OB11GroupMemberRole {
owner = "owner", owner = "owner",
admin = "admin", admin = "admin",
member = "member", member = "member",
@@ -33,7 +32,7 @@ export interface OB11GroupMember {
title?: string title?: string
} }
export interface OB11Group{ export interface OB11Group {
group_id: number group_id: number
group_name: string group_name: string
member_count?: number member_count?: number
@@ -89,10 +88,12 @@ export interface OB11Return<DataType> {
status: number status: number
retcode: number retcode: number
data: DataType data: DataType
message: string message: string,
echo?: string
} }
export interface OB11SendMsgReturn extends OB11Return<{message_id: string}>{} export interface OB11SendMsgReturn extends OB11Return<{ message_id: string }> {
}
export enum OB11MessageDataType { export enum OB11MessageDataType {
text = "text", text = "text",
@@ -100,7 +101,8 @@ export enum OB11MessageDataType {
voice = "record", voice = "record",
at = "at", at = "at",
reply = "reply", reply = "reply",
json = "json" json = "json",
face = "face"
} }
export type OB11MessageData = { export type OB11MessageData = {
@@ -132,6 +134,11 @@ export type OB11MessageData = {
data: { data: {
id: string, id: string,
} }
} | {
type: OB11MessageDataType.face,
data: {
id: string
}
} }
export interface OB11PostSendMsg { export interface OB11PostSendMsg {
@@ -139,4 +146,17 @@ export interface OB11PostSendMsg {
user_id: string, user_id: string,
group_id?: string, group_id?: string,
message: OB11MessageData[] | string | OB11MessageData; message: OB11MessageData[] | string | OB11MessageData;
} }
export interface OB11Version {
app_name: "LLOneBot"
app_version: string
protocol_version: "v11"
}
export interface OB11Status {
online: boolean | null,
good: boolean
}

View File

@@ -36,6 +36,10 @@ async function onSettingWindowCreated(view: Element) {
<setting-text>正向ws监听端口</setting-text> <setting-text>正向ws监听端口</setting-text>
<input id="wsPort" type="number" value="${config.wsPort}"/> <input id="wsPort" type="number" value="${config.wsPort}"/>
</setting-item> </setting-item>
<setting-item class="vertical-list-item" data-direction="row">
<setting-text>Access Token</setting-text>
<input id="token" type="text" placeholder="可为空" value="${config.token}"/>
</setting-item>
<div> <div>
<button id="addHost" class="q-button">添加HTTP上报地址</button> <button id="addHost" class="q-button">添加HTTP上报地址</button>
</div> </div>
@@ -130,20 +134,24 @@ async function onSettingWindowCreated(view: Element) {
const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement
const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement
const hostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>; const hostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>;
const tokenEle = document.getElementById("token") as HTMLInputElement;
// const port = doc.querySelector("input[type=number]")?.value // const port = doc.querySelector("input[type=number]")?.value
// const host = doc.querySelector("input[type=text]")?.value // const host = doc.querySelector("input[type=text]")?.value
// 获取端口和host // 获取端口和host
const port = portEle.value const port = portEle.value
const wsPort = wsPortEle.value const wsPort = wsPortEle.value
const token = tokenEle.value
let hosts: string[] = []; let hosts: string[] = [];
for (const hostEle of hostEles) { for (const hostEle of hostEles) {
if (hostEle.value) { if (hostEle.value) {
hosts.push(hostEle.value); hosts.push(hostEle.value.trim());
} }
} }
config.port = parseInt(port); config.port = parseInt(port);
config.wsPort = parseInt(wsPort); config.wsPort = parseInt(wsPort);
config.hosts = hosts; config.hosts = hosts;
config.token = token.trim();
window.llonebot.setConfig(config); window.llonebot.setConfig(config);
alert("保存成功"); alert("保存成功");
}) })