mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2a1aa8c649 | ||
![]() |
1633734e08 | ||
![]() |
dff92e6f27 | ||
![]() |
dba5e30d5d | ||
![]() |
2d04ab2e72 | ||
![]() |
1a015ac8d3 | ||
![]() |
6390620ddd | ||
![]() |
0d19005dc3 | ||
![]() |
c6479dd2c4 | ||
![]() |
8871331b7c | ||
![]() |
e01148b86a | ||
![]() |
2f87e3818e | ||
![]() |
2c8a594c38 | ||
![]() |
1508dab7fe | ||
![]() |
958b21e47e | ||
![]() |
781c3311ae | ||
![]() |
52850d172e | ||
![]() |
52a065542e | ||
![]() |
fd10469685 | ||
![]() |
a2ee75b113 | ||
![]() |
0f7f243b98 | ||
![]() |
97d7996a50 | ||
![]() |
b658d164f9 |
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot v3.26.5",
|
||||
"name": "LLOneBot v3.27.0",
|
||||
"slug": "LLOneBot",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
||||
"version": "3.26.5",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发",
|
||||
"version": "3.27.0",
|
||||
"icon": "./icon.jpg",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -23,7 +23,7 @@
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"level": "^8.0.1",
|
||||
"silk-wasm": "^3.3.4",
|
||||
"silk-wasm": "^3.6.0",
|
||||
"utf-8-validate": "^6.0.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
|
@@ -43,6 +43,7 @@ export class ConfigUtil {
|
||||
enableQOAutoQuote: false
|
||||
}
|
||||
let defaultConfig: Config = {
|
||||
enableLLOB: true,
|
||||
ob11: ob11Default,
|
||||
heartInterval: 60000,
|
||||
token: '',
|
||||
|
@@ -17,6 +17,7 @@ export interface CheckVersion {
|
||||
version: string
|
||||
}
|
||||
export interface Config {
|
||||
enableLLOB: boolean
|
||||
ob11: OB11Config
|
||||
token?: string
|
||||
heartInterval?: number // ms
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import fs from 'fs'
|
||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm'
|
||||
import fsPromise from 'fs/promises'
|
||||
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm'
|
||||
import { log } from './log'
|
||||
import path from 'node:path'
|
||||
import { DATA_DIR, TEMP_DIR } from './index'
|
||||
import { TEMP_DIR } from './index'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getConfigUtil } from '../config'
|
||||
import { spawn } from 'node:child_process'
|
||||
@@ -60,10 +60,11 @@ export async function encodeSilk(filePath: string) {
|
||||
// }
|
||||
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath)
|
||||
const pttPath = path.join(TEMP_DIR, uuidv4())
|
||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
||||
if (!isSilk(file)) {
|
||||
log(`语音文件${filePath}需要转换成silk`)
|
||||
const _isWav = await isWavFile(filePath)
|
||||
const _isWav = isWav(file)
|
||||
const pcmPath = pttPath + '.pcm'
|
||||
let sampleRate = 0
|
||||
const convert = () => {
|
||||
@@ -79,7 +80,8 @@ export async function encodeSilk(filePath: string) {
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000
|
||||
const data = fs.readFileSync(pcmPath)
|
||||
fs.unlink(pcmPath, (err) => {})
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
})
|
||||
return resolve(data)
|
||||
}
|
||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
||||
@@ -91,7 +93,7 @@ export async function encodeSilk(filePath: string) {
|
||||
if (!_isWav) {
|
||||
input = await convert()
|
||||
} else {
|
||||
input = fs.readFileSync(filePath)
|
||||
input = file
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
||||
const { fmt } = getWavFileInfo(input)
|
||||
// log(`wav文件信息`, fmt)
|
||||
@@ -108,7 +110,7 @@ export async function encodeSilk(filePath: string) {
|
||||
duration: silk.duration / 1000,
|
||||
}
|
||||
} else {
|
||||
const silk = fs.readFileSync(filePath)
|
||||
const silk = file
|
||||
let duration = 0
|
||||
try {
|
||||
duration = getDuration(silk) / 1000
|
||||
@@ -128,3 +130,41 @@ export async function encodeSilk(filePath: string) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') {
|
||||
const silkArrayBuffer = await fsPromise.readFile(inputFilePath)
|
||||
const data = (await decode(silkArrayBuffer, 24000)).data
|
||||
const fileName = path.join(TEMP_DIR, path.basename(inputFilePath))
|
||||
const outPCMPath = fileName + '.pcm'
|
||||
const outFilePath = fileName + '.' + outFormat
|
||||
await fsPromise.writeFile(outPCMPath, data)
|
||||
const convert = () => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg'
|
||||
const cp = spawn(ffmpegPath, [
|
||||
'-y',
|
||||
'-f', 's16le', // PCM format
|
||||
'-ar', '24000', // Sample rate
|
||||
'-ac', '1', // Number of audio channels
|
||||
'-i', outPCMPath,
|
||||
outFilePath,
|
||||
])
|
||||
cp.on('error', (err) => {
|
||||
log(`FFmpeg处理转换出错: `, err.message)
|
||||
return reject(err)
|
||||
})
|
||||
cp.on('exit', (code, signal) => {
|
||||
const EXIT_CODES = [0, 255]
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
fs.unlink(outPCMPath, (err) => {
|
||||
})
|
||||
return resolve(outFilePath)
|
||||
}
|
||||
const exitErr = `FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`
|
||||
log(exitErr)
|
||||
reject(Error(`FFmpeg处理转换失败,${exitErr}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
return convert()
|
||||
}
|
@@ -13,7 +13,7 @@ import {
|
||||
CHANNEL_UPDATE,
|
||||
} from '../common/channels'
|
||||
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
|
||||
import { DATA_DIR } from '../common/utils'
|
||||
import { DATA_DIR, qqPkgInfo } from '../common/utils'
|
||||
import {
|
||||
friendRequests,
|
||||
getFriend,
|
||||
@@ -56,6 +56,7 @@ import { getConfigUtil } from '../common/config'
|
||||
import { checkFfmpeg } from '../common/utils/video'
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||
import '../ntqqapi/native/wrapper'
|
||||
import { sentMessages } from '@/ntqqapi/api'
|
||||
|
||||
let running = false
|
||||
|
||||
@@ -202,6 +203,10 @@ function onLoad() {
|
||||
async function startReceiveHook() {
|
||||
startHook().then()
|
||||
if (getConfigUtil().getConfig().enablePoke) {
|
||||
if ( qqPkgInfo.buildVersion > '23873'){
|
||||
log(`当前版本${qqPkgInfo.buildVersion}不支持发送戳一戳模块`)
|
||||
return
|
||||
}
|
||||
crychic.loadNode()
|
||||
crychic.registerPokeHandler((id, isGroup) => {
|
||||
log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`)
|
||||
@@ -227,6 +232,10 @@ function onLoad() {
|
||||
const recallMsgIds: string[] = [] // 避免重复上报
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
||||
for (const message of payload.msgList) {
|
||||
const sentMessage = sentMessages[message.msgId]
|
||||
if (sentMessage){
|
||||
Object.assign(sentMessage, message)
|
||||
}
|
||||
log('message update', message.msgId, message)
|
||||
if (message.recallTime != '0') {
|
||||
if (recallMsgIds.includes(message.msgId)) {
|
||||
@@ -430,6 +439,11 @@ function onLoad() {
|
||||
|
||||
async function start() {
|
||||
log('llonebot pid', process.pid)
|
||||
const config = getConfigUtil().getConfig()
|
||||
if (!config.enableLLOB){
|
||||
log('LLOneBot 开关设置为关闭,不启动LLOneBot')
|
||||
return
|
||||
}
|
||||
llonebotError.otherError = ''
|
||||
startTime = Date.now()
|
||||
dbUtil.getReceivedTempUinMap().then((m) => {
|
||||
@@ -464,7 +478,6 @@ function onLoad() {
|
||||
}
|
||||
|
||||
|
||||
const config = getConfigUtil().getConfig()
|
||||
if (config.ob11.enableHttp) {
|
||||
ob11HTTPServer.start(config.ob11.httpPort)
|
||||
}
|
||||
|
@@ -10,15 +10,23 @@ import {
|
||||
ElementType,
|
||||
IMAGE_HTTP_HOST,
|
||||
IMAGE_HTTP_HOST_NT, PicElement,
|
||||
RawMessage,
|
||||
} from '../types'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { ReceiveCmdS } from '../hook'
|
||||
import { log } from '@/common/utils'
|
||||
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
||||
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
||||
import { Peer } from '@/ntqqapi/api/msg'
|
||||
|
||||
export class NTQQFileApi {
|
||||
static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string> {
|
||||
return (await wrapperApi.NodeIQQNTWrapperSession.getRichMediaService().getVideoPlayUrlV2(peer,
|
||||
msgId,
|
||||
elementId,
|
||||
0,
|
||||
{ downSourceType: 1, triggerType: 1 })).urlResult?.domainUrl[0]?.url;
|
||||
}
|
||||
static async getFileType(filePath: string) {
|
||||
return await callNTQQApi<{ ext: string }>({
|
||||
className: NTQQApiClass.FS_API,
|
||||
|
@@ -7,7 +7,9 @@ import { log } from '../../common/utils/log'
|
||||
import { sleep } from '../../common/utils/helper'
|
||||
import { isQQ998 } from '../../common/utils'
|
||||
|
||||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunnc
|
||||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunc
|
||||
|
||||
export let sentMessages: Record<string, RawMessage> = {} // msgId: RawMessage
|
||||
|
||||
export interface Peer {
|
||||
chatType: ChatType
|
||||
@@ -40,17 +42,20 @@ async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 100
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
delete sendMessagePool[peerUid]
|
||||
sentMessage = rawMessage
|
||||
sentMessages[rawMessage.msgId] = rawMessage
|
||||
}
|
||||
|
||||
let checkSendCompleteUsingTime = 0
|
||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||
if (sentMessage) {
|
||||
if (waitComplete) {
|
||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
||||
if (sentMessage.sendStatus == 2) {
|
||||
delete sentMessages[sentMessage.msgId]
|
||||
return sentMessage
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete sentMessages[sentMessage.msgId]
|
||||
return sentMessage
|
||||
}
|
||||
// log(`给${peerUid}发送消息成功`)
|
||||
|
@@ -188,6 +188,7 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
|
||||
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
||||
|
||||
export interface PicElement {
|
||||
picSubType: PicSubType
|
||||
picType: PicType // 有这玩意儿吗
|
||||
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||
originImageMd5?: string
|
||||
@@ -226,6 +227,7 @@ export interface GrayTipElement {
|
||||
content: string
|
||||
}
|
||||
jsonGrayTipElement: {
|
||||
busiId: number
|
||||
jsonStr: string
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction'
|
||||
import fs from 'fs/promises'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import { log, sleep, uri2local } from '@/common/utils'
|
||||
import { checkFileReceived, log, sleep, uri2local } from '@/common/utils'
|
||||
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||
import { ActionName } from '../types'
|
||||
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||
@@ -38,20 +38,21 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
log('找到了文件 element', element)
|
||||
// 构建下载函数
|
||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
||||
await sleep(1000) // 这里延时是为何?
|
||||
// 等待文件下载完成
|
||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||
log('下载完成后的msg', msg)
|
||||
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
||||
await checkFileReceived(cache.filePath, 10 * 1000)
|
||||
dbUtil.addFileCache(file, cache).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||
const cache = await dbUtil.getFileCache(payload.file)
|
||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||
let cache = await dbUtil.getFileCache(payload.file)
|
||||
if (!cache) {
|
||||
throw new Error('file not found')
|
||||
}
|
||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||
if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
}
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
||||
import { ActionName } from '../types'
|
||||
import {decodeSilk} from "@/common/utils/audio";
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
interface Payload extends GetFilePayload {
|
||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||
@@ -9,7 +13,13 @@ export default class GetRecord extends GetFileBase {
|
||||
actionName = ActionName.GetRecord
|
||||
|
||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
||||
let res = super._handle(payload)
|
||||
let res = await super._handle(payload)
|
||||
res.file = await decodeSilk(res.file, payload.out_format)
|
||||
res.file_name = path.basename(res.file)
|
||||
res.file_size = fs.statSync(res.file).size.toString()
|
||||
if (getConfigUtil().getConfig().enableLocalFile2Url){
|
||||
res.base64 = fs.readFileSync(res.file, 'base64')
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ import {
|
||||
User,
|
||||
VideoElement,
|
||||
} from '../ntqqapi/types'
|
||||
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap } from '../common/data'
|
||||
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap, uidMaps } from '../common/data'
|
||||
import { EventType } from './event/OB11BaseEvent'
|
||||
import { encodeCQCode } from './cqcode'
|
||||
import { dbUtil } from '../common/db'
|
||||
@@ -47,6 +47,7 @@ import { mFaceCache } from '../ntqqapi/constructor'
|
||||
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
||||
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
|
||||
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
|
||||
import { OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'
|
||||
|
||||
let lastRKeyUpdateTime = 0
|
||||
|
||||
@@ -161,10 +162,12 @@ export class OB11Constructor {
|
||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||
let fileName = element.picElement.fileName
|
||||
const sourcePath = element.picElement.sourcePath
|
||||
if (element.picElement.picType === PicType.gif && !fileName.endsWith('.gif')) {
|
||||
const isGif = element.picElement.picType === PicType.gif
|
||||
if (isGif && !fileName.endsWith('.gif')) {
|
||||
fileName += '.gif'
|
||||
}
|
||||
message_data['data']['file'] = fileName
|
||||
message_data['data']['subType'] = element.picElement.picSubType
|
||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
||||
|
||||
@@ -188,9 +191,7 @@ export class OB11Constructor {
|
||||
element.picElement.sourcePath,
|
||||
)
|
||||
},
|
||||
})
|
||||
.then()
|
||||
// 不在自动下载图片
|
||||
}).then()
|
||||
}
|
||||
else if (element.videoElement || element.fileElement) {
|
||||
const videoOrFileElement = element.videoElement || element.fileElement
|
||||
@@ -200,6 +201,13 @@ export class OB11Constructor {
|
||||
message_data['data']['path'] = videoOrFileElement.filePath
|
||||
message_data['data']['file_id'] = videoOrFileElement.fileUuid
|
||||
message_data['data']['file_size'] = videoOrFileElement.fileSize
|
||||
if (element.videoElement) {
|
||||
message_data['data']['url'] = await NTQQFileApi.getVideoUrl({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
}, msg.msgId, element.elementId,
|
||||
)
|
||||
}
|
||||
dbUtil
|
||||
.addFileCache(videoOrFileElement.fileUuid, {
|
||||
msgId: msg.msgId,
|
||||
@@ -290,7 +298,7 @@ export class OB11Constructor {
|
||||
if (message_data.type !== 'unknown' && message_data.data) {
|
||||
const cqCode = encodeCQCode(message_data)
|
||||
if (messagePostFormat === 'string') {
|
||||
;(resMsg.message as string) += cqCode
|
||||
(resMsg.message as string) += cqCode
|
||||
}
|
||||
else (resMsg.message as OB11MessageData[]).push(message_data)
|
||||
|
||||
@@ -485,6 +493,16 @@ export class OB11Constructor {
|
||||
}
|
||||
|
||||
* */
|
||||
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||
//判断业务类型
|
||||
//Poke事件
|
||||
let pokedetail: any[] = json.items;
|
||||
//筛选item带有uid的元素
|
||||
pokedetail = pokedetail.filter(item => item.uid);
|
||||
if (pokedetail.length == 2) {
|
||||
return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((uidMaps[pokedetail[0].uid])!), parseInt((uidMaps[pokedetail[1].uid])));
|
||||
}
|
||||
}
|
||||
const memberUin = json.items[1].param[0]
|
||||
const title = json.items[3].txt
|
||||
log('收到群成员新头衔消息', json)
|
||||
|
@@ -50,6 +50,7 @@ export function encodeCQCode(data: OB11MessageData) {
|
||||
}
|
||||
|
||||
const CQCodeEscape = (text: string) => {
|
||||
text = text.toString()
|
||||
return text.replace(/\&/g, '&').replace(/\[/g, '[').replace(/\]/g, ']').replace(/,/g, ',')
|
||||
}
|
||||
|
||||
|
@@ -21,10 +21,10 @@ export class OB11FriendPokeEvent extends OB11PokeEvent {
|
||||
export class OB11GroupPokeEvent extends OB11PokeEvent {
|
||||
group_id: number
|
||||
|
||||
constructor(group_id: number, user_id: number = 0) {
|
||||
constructor(group_id: number, user_id: number = 0, target_id: number = 0) {
|
||||
super()
|
||||
this.group_id = group_id
|
||||
this.target_id = user_id
|
||||
this.target_id = target_id
|
||||
this.user_id = user_id
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import { WebSocket as WebSocketClass } from 'ws'
|
||||
import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent'
|
||||
import { log } from '../../../common/utils/log'
|
||||
import { getConfigUtil } from '../../../common/config'
|
||||
import { version } from '../../../version'
|
||||
|
||||
export let rwsList: ReverseWebsocket[] = []
|
||||
|
||||
@@ -85,6 +86,7 @@ export class ReverseWebsocket {
|
||||
'X-Self-ID': selfInfo.uin,
|
||||
Authorization: `Bearer ${token}`,
|
||||
'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段
|
||||
'User-Agent': `LLOneBot/${version}`,
|
||||
},
|
||||
})
|
||||
registerWsEventSender(this.websocket)
|
||||
|
@@ -27,6 +27,7 @@ class OB11WebsocketServer extends WebsocketServerBase {
|
||||
}
|
||||
try {
|
||||
let handleResult = await action.websocketHandle(params, echo)
|
||||
handleResult.echo = echo
|
||||
wsReply(wsClient, handleResult)
|
||||
} catch (e) {
|
||||
wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo))
|
||||
|
@@ -62,6 +62,13 @@ async function onSettingWindowCreated(view: Element) {
|
||||
SettingButton('请稍候', 'llonebot-update-button', 'secondary'),
|
||||
),
|
||||
]),
|
||||
SettingList([
|
||||
SettingItem(
|
||||
'是否启用 LLOneBot, 重启QQ后生效',
|
||||
null,
|
||||
SettingSwitch('enableLLOB', config.enableLLOB, { 'control-display-id': 'config-enableLLOB' }),
|
||||
)]
|
||||
),
|
||||
SettingList([
|
||||
SettingItem(
|
||||
'启用 HTTP 服务',
|
||||
|
@@ -1 +1 @@
|
||||
export const version = '3.26.5'
|
||||
export const version = '3.27.0'
|
||||
|
Reference in New Issue
Block a user