feat: convert wav by ffmpeg

This commit is contained in:
linyuchen 2024-02-26 22:19:37 +08:00
parent 730294236c
commit d57c14a8b9
11 changed files with 329 additions and 63 deletions

107
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"audio-buffer-from": "^1.1.1",
"express": "^4.18.2",
"fluent-ffmpeg": "^2.1.2",
"json-bigint": "^1.0.0",
@ -2725,6 +2726,51 @@
"resolved": "https://mirrors.cloud.tencent.com/npm/async/-/async-3.2.5.tgz",
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
},
"node_modules/atob-lite": {
"version": "2.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/atob-lite/-/atob-lite-2.0.0.tgz",
"integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY="
},
"node_modules/audio-buffer": {
"version": "4.0.4",
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-buffer/-/audio-buffer-4.0.4.tgz",
"integrity": "sha512-phH+MR3G+N/PO5ZKKxx7HlU6vJwAJFa0+FCaTjr/4lUZU/RCjUTqlk3nMJTRy5+b+6cbx8m//EtwZOVI5Ht9+w==",
"dependencies": {
"audio-context": "^1.0.0"
}
},
"node_modules/audio-buffer-from": {
"version": "1.1.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-buffer-from/-/audio-buffer-from-1.1.1.tgz",
"integrity": "sha512-8Wcira24z+26GXDFe7ZFRF1bJm1iWrz8O+XL8iNZxZjxqAPXIoK1IrJiOqStv35ASPbRjG57ZK/T0WO92MDUSg==",
"dependencies": {
"audio-buffer": "^4.0.4",
"audio-context": "^1.0.1",
"audio-format": "^2.0.0",
"is-audio-buffer": "^1.0.11",
"is-plain-obj": "^1.1.0",
"pcm-convert": "^1.6.0",
"pick-by-alias": "^1.2.0",
"string-to-arraybuffer": "^1.0.0"
}
},
"node_modules/audio-context": {
"version": "1.0.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-context/-/audio-context-1.0.3.tgz",
"integrity": "sha512-RH3/rM74f2ITlohhjgC7oYZVS97wtv/SEjXLCzEinnrIPIDxc39m2aFc6wmdkM0NYRKo1DMleYPMAIbnTRW0eA=="
},
"node_modules/audio-format": {
"version": "2.3.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/audio-format/-/audio-format-2.3.2.tgz",
"integrity": "sha512-5IA2grZhaVhpGxX6lbJm8VVh/SKQULMXXrFxuiodi0zhzDPRB8BJfieo89AclEQv4bDxZRH4lv06qNnxqkFhKQ==",
"dependencies": {
"is-audio-buffer": "^1.0.11",
"is-buffer": "^1.1.5",
"is-plain-obj": "^1.1.0",
"pick-by-alias": "^1.2.0",
"sample-rate": "^2.0.0"
}
},
"node_modules/babel-loader": {
"version": "9.1.3",
"resolved": "https://mirrors.cloud.tencent.com/npm/babel-loader/-/babel-loader-9.1.3.tgz",
@ -4256,6 +4302,21 @@
"node": ">= 0.10"
}
},
"node_modules/is-audio-buffer": {
"version": "1.1.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/is-audio-buffer/-/is-audio-buffer-1.1.0.tgz",
"integrity": "sha512-fmPC/dizJmP4ITCsW5oTQGMJ9wZVE+A/zAe6FQo3XwgERxmXHmm3ON5XkWDAxmyxvsrDmWx3NArpSgamp/59AA=="
},
"node_modules/is-base64": {
"version": "0.1.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/is-base64/-/is-base64-0.1.0.tgz",
"integrity": "sha512-WRRyllsGXJM7ZN7gPTCCQ/6wNPTRDwiWdPK66l5sJzcU/oOzcIcRRf0Rux8bkpox/1yjt0F6VJRsQOIG2qz5sg=="
},
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://mirrors.cloud.tencent.com/npm/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@ -4298,6 +4359,14 @@
"node": ">=0.12.0"
}
},
"node_modules/is-plain-obj": {
"version": "1.1.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@ -4681,6 +4750,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/object-inspect/-/object-inspect-1.13.1.tgz",
@ -4813,6 +4890,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pcm-convert": {
"version": "1.6.5",
"resolved": "https://mirrors.cloud.tencent.com/npm/pcm-convert/-/pcm-convert-1.6.5.tgz",
"integrity": "sha512-5CEspU4j8aEQ80AhNbcLfpT0apc93E6endFxahWd4sV70I6PN7LPdz8GoYm/1qr400K9bUVsVA+KxNgbFROZPw==",
"dependencies": {
"audio-format": "^2.3.2",
"is-audio-buffer": "^1.0.11",
"is-buffer": "^1.1.5",
"object-assign": "^4.1.1"
}
},
"node_modules/peek-readable": {
"version": "5.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/peek-readable/-/peek-readable-5.0.0.tgz",
@ -4831,6 +4919,11 @@
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
"dev": true
},
"node_modules/pick-by-alias": {
"version": "1.2.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/pick-by-alias/-/pick-by-alias-1.2.0.tgz",
"integrity": "sha1-X3yysfIabh6ISgyHhVqko3NhEHs="
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.0.0.tgz",
@ -5223,6 +5316,11 @@
"resolved": "https://mirrors.cloud.tencent.com/npm/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sample-rate": {
"version": "2.0.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/sample-rate/-/sample-rate-2.0.1.tgz",
"integrity": "sha512-AIK0vVBiAEObmpJOxQu/WCyklnWGqzTSDII4O7nBo+SJHmfgBUiYhgV/Y3Ohz76gfSlU6R5CIAKggj+nAOLSvg=="
},
"node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@ -5454,6 +5552,15 @@
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-to-arraybuffer": {
"version": "1.0.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz",
"integrity": "sha512-DaGZidzi93dwjQen5I2osxR9ERS/R7B1PFyufNMnzhj+fmlDQAc1DSDIJVJhgI8Oq221efIMbABUBdPHDRt43Q==",
"dependencies": {
"atob-lite": "^2.0.0",
"is-base64": "^0.1.0"
}
},
"node_modules/strtok3": {
"version": "7.0.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/strtok3/-/strtok3-7.0.0.tgz",

View File

@ -22,6 +22,7 @@
"json-bigint": "^1.0.0",
"music-metadata": "^8.1.4",
"silk-wasm": "^3.2.3",
"utf-8-validate": "^6.0.3",
"uuid": "^9.0.1",
"ws": "^8.16.0"
},
@ -34,7 +35,6 @@
"@types/ws": "^8.5.10",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^12.0.2",
"cross-env": "^7.0.3",
"electron": "^29.0.1",
"ts-loader": "^9.5.0",
"typescript": "^5.2.2",

View File

@ -2,4 +2,6 @@ import {Peer} from "../ntqqapi/ntcall";
export const CHANNEL_GET_CONFIG = "llonebot_get_config"
export const CHANNEL_SET_CONFIG = "llonebot_set_config"
export const CHANNEL_LOG = "llonebot_log"
export const CHANNEL_LOG = "llonebot_log"
export const CHANNEL_ERROR = "llonebot_error"
export const CHANNEL_SELECT_FILE = "llonebot_select_ffmpeg"

View File

@ -1,10 +1,17 @@
import {NTQQApi} from '../ntqqapi/ntcall';
import {Friend, FriendRequest, Group, GroupMember, GroupNotify, RawMessage, SelfInfo} from "../ntqqapi/types";
import {LLOneBotError} from "./types";
export let groups: Group[] = []
export let friends: Friend[] = []
export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage
export const version = "3.7.0"
export let groupNotifies: Map<string, GroupNotify> = new Map<string, GroupNotify>();
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>();
export let llonebotError: LLOneBotError = {
ffmpegError: "",
otherError: ""
}
let globalMsgId = Math.floor(Date.now() / 1000);
export function addHistoryMsg(msg: RawMessage): boolean {
@ -86,7 +93,3 @@ export function getUidByUin(uin: string) {
}
}
export const version = "3.7.0"
export let groupNotifies: Map<string, GroupNotify> = new Map<string, GroupNotify>();
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>();

View File

@ -19,4 +19,10 @@ export interface Config {
reportSelfMessage?: boolean
log?: boolean
autoDeleteFile?: boolean
ffmpeg?: string // ffmpeg路径
}
export type LLOneBotError = {
ffmpegError?: string
otherError?: string
}

View File

@ -138,17 +138,18 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
});
}
export function checkFFMPEG(): boolean {
let ffmpegExist: boolean;
exec('ffmpeg -version', (error, stdout, stderr) => {
if (error) {
log('ffmpeg is not installed or not found in PATH:', error);
ffmpegExist = false
}
log('ffmpeg is installed. Version info:', stdout);
ffmpegExist = true;
export function checkFFMPEG(newPath: string=null): Promise<boolean> {
return new Promise((resolve, reject) => {
const ffmpegPath = newPath || 'ffmpeg'
exec(ffmpegPath + ' -version', (error, stdout, stderr) => {
if (error) {
log('ffmpeg is not installed or not found in PATH:', error);
resolve(false)
}
log('ffmpeg is installed. Version info:', stdout);
resolve(true);
});
});
return ffmpegExist;
}
export async function encodeSilk(filePath: string) {
@ -214,28 +215,37 @@ export async function encodeSilk(filePath: string) {
try {
const fileName = path.basename(filePath);
const pcm = fs.readFileSync(filePath);
const pttPath = path.join(CONFIG_DIR, uuidv4());
if (getFileHeader(filePath) !== "02232153494c4b") {
log(`语音文件${filePath}需要转换`)
const isWav = await isWavFile(filePath);
if (!isWav) {
log(`语音文件${filePath}正在转换成wav`)
let voiceData = await fsp.readFile(filePath)
// let voiceData = await fsp.readFile(filePath)
const wavPath = pttPath + ".wav"
await new Promise((resolve, reject) => {
const ffmpegPath = getConfigUtil().getConfig().ffmpeg;
if (ffmpegPath){
ffmpeg.setFfmpegPath(ffmpegPath);
}
ffmpeg(filePath).toFormat("wav").on('end', function () {
// console.log('转换完成');
filePath = wavPath
log('wav转换完成');
})
.on('error', function (err) {
// console.log('转换出错: ' + err.message);
log(`wav转换出错: `, err.message,);
reject(err);
})
.save(wavPath);
.save(wavPath)
.on("end", ()=>{
filePath = wavPath
resolve(wavPath);
});
})
const sampleRate = await getAudioSampleRate(filePath) || 44100;
const pcm = fs.readFileSync(filePath);
const silk = await encode(pcm, sampleRate);
fs.writeFileSync(pttPath, silk.data);
fs.unlink(wavPath, (err) => {});
log(`语音文件${filePath}转换成功!`, pttPath)
return {
converted: true,
@ -243,6 +253,7 @@ export async function encodeSilk(filePath: string) {
duration: silk.duration,
};
} else {
const pcm = fs.readFileSync(filePath);
const duration = getDuration(pcm);
return {
converted: false,

View File

@ -1,17 +1,24 @@
// 运行在 Electron 主进程 下的插件入口
import {BrowserWindow, ipcMain} from 'electron';
import {BrowserWindow, dialog, ipcMain} from 'electron';
import fs from 'fs';
import {Config} from "../common/types";
import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "../common/channels";
import {
CHANNEL_ERROR,
CHANNEL_GET_CONFIG,
CHANNEL_LOG,
CHANNEL_SELECT_FILE,
CHANNEL_SET_CONFIG,
} from "../common/channels";
import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
import {CONFIG_DIR, getConfigUtil, log} from "../common/utils";
import {checkFFMPEG, CONFIG_DIR, getConfigUtil, log} from "../common/utils";
import {
addHistoryMsg,
friendRequests,
getGroup,
getGroupMember,
groupNotifies,
llonebotError,
msgHistory,
selfInfo
} from "../common/data";
@ -35,6 +42,7 @@ import {OB11GroupAdminNoticeEvent} from "../onebot11/event/notice/OB11GroupAdmin
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupRequestEvent} from "../onebot11/event/request/OB11GroupRequest";
import {OB11FriendRequestEvent} from "../onebot11/event/request/OB11FriendRequest";
import * as path from "node:path";
let running = false;
@ -43,13 +51,48 @@ let running = false;
// 加载插件时触发
function onLoad() {
log("llonebot main onLoad");
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => {
const selectPath = new Promise<string>((resolve, reject) => {
dialog
.showOpenDialog({
title: "请选择ffmpeg",
properties: ["openFile"],
buttonLabel: "确定",
})
.then((result) => {
log("选择文件", result);
if (!result.canceled) {
const _selectPath = path.join(result.filePaths[0]);
resolve(_selectPath);
// let config = getConfigUtil().getConfig()
// config.ffmpeg = path.join(result.filePaths[0]);
// getConfigUtil().setConfig(config);
}
resolve("")
})
.catch((err) => {
reject(err);
});
})
try {
return await selectPath;
} catch (e) {
log("选择文件出错", e)
return ""
}
})
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, {recursive: true});
}
ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => {
return getConfigUtil().getConfig()
ipcMain.handle(CHANNEL_ERROR, (event, arg) => {
return llonebotError;
})
ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => {
const config = getConfigUtil().getConfig()
return config;
})
ipcMain.on(CHANNEL_SET_CONFIG, (event, arg: Config) => {
let oldConfig = getConfigUtil().getConfig();
getConfigUtil().setConfig(arg)
if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) {
@ -94,13 +137,20 @@ function onLoad() {
}
}
}
// 检查ffmpeg
if (arg.ffmpeg) {
checkFFMPEG(arg.ffmpeg).then(success => {
llonebotError.ffmpegError = ''
})
}
})
ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
ipcMain.on(CHANNEL_LOG, (event, arg) => {
log(arg);
})
function postReceiveMsg(msgList: RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig();
for (let message of msgList) {
@ -186,7 +236,7 @@ function onLoad() {
let notify: GroupNotifies;
try {
notify = await NTQQApi.getGroupNotifies();
}catch (e) {
} catch (e) {
// log("获取群通知详情失败", e);
return
}
@ -197,12 +247,12 @@ function onLoad() {
for (const notify of notifies) {
const notifyTime = parseInt(notify.seq) / 1000
log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
if ( notifyTime < startTime){
if (notifyTime < startTime) {
continue;
}
const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
let member2: GroupMember;
if (notify.user2.uid){
if (notify.user2.uid) {
member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
}
if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET].includes(notify.type)) {
@ -210,22 +260,19 @@ function onLoad() {
let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent()
groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode);
log("开始获取变动的管理员")
if(member1){
if (member1) {
log("变动管理员获取成功")
groupAdminNoticeEvent.user_id = parseInt(member1.uin);
groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? "unset" : "set";
postOB11Event(groupAdminNoticeEvent, true);
}
else{
} else {
log("获取群通知的成员信息失败", notify, getGroup(notify.group.groupCode));
}
}
else if (notify.type == GroupNotifyTypes.MEMBER_EXIT){
} else if (notify.type == GroupNotifyTypes.MEMBER_EXIT) {
log("有成员退出通知");
let groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(notify.group.groupCode), parseInt(member1.uin))
// postEvent(groupDecreaseEvent, true);
}
else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)){
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
log("有加群请求");
groupNotifies[notify.seq] = notify;
let groupRequestEvent = new OB11GroupRequestEvent();
@ -233,7 +280,7 @@ function onLoad() {
let requestQQ = ""
try {
requestQQ = (await NTQQApi.getUserDetailInfo(notify.user1.uid)).uin;
}catch (e) {
} catch (e) {
log("获取加群人QQ号失败", e)
}
groupRequestEvent.user_id = parseInt(requestQQ) || 0;
@ -243,22 +290,22 @@ function onLoad() {
postOB11Event(groupRequestEvent);
}
}
}catch (e) {
} catch (e) {
log("解析群通知失败", e.stack);
}
}
})
registerReceiveHook<FriendRequestNotify>(ReceiveCmd.FRIEND_REQUEST, async (payload) => {
for(const req of payload.data.buddyReqs){
if (req.isUnread && !friendRequests[req.sourceId] && (parseInt(req.reqTime) > startTime / 1000)){
for (const req of payload.data.buddyReqs) {
if (req.isUnread && !friendRequests[req.sourceId] && (parseInt(req.reqTime) > startTime / 1000)) {
friendRequests[req.sourceId] = req;
log("有新的好友请求", req);
let friendRequestEvent = new OB11FriendRequestEvent();
try{
try {
let requester = await NTQQApi.getUserDetailInfo(req.friendUid)
friendRequestEvent.user_id = parseInt(requester.uin);
}catch (e) {
} catch (e) {
log("获取加好友者QQ号失败", e);
}
friendRequestEvent.flag = req.sourceId.toString();
@ -268,12 +315,20 @@ function onLoad() {
}
})
}
let startTime = 0;
async function start() {
startTime = Date.now();
startReceiveHook().then();
NTQQApi.getGroups(true).then()
const config = getConfigUtil().getConfig()
// 检查ffmpeg
checkFFMPEG(config.ffmpeg).then(exist => {
if (!exist) {
llonebotError.ffmpegError = `环境变量${process.env.PATH}中不存在ffmpeg,音频只能发送wav和silk`
}
})
if (config.ob11.enableHttp) {
try {
ob11HTTPServer.start(config.ob11.httpPort)
@ -309,7 +364,7 @@ function onLoad() {
selfInfo.nick = userInfo.nick;
} else {
getSelfNickCount++;
if (getSelfNickCount < 10){
if (getSelfNickCount < 10) {
return setTimeout(init, 1000);
}
}

View File

@ -9,6 +9,7 @@ import {
} from "./types";
import {NTQQApi} from "./ntcall";
import {encodeSilk} from "../common/utils";
import fs from "fs";
export class SendMsgElementConstructor {
@ -84,7 +85,7 @@ export class SendMsgElementConstructor {
// log("生成语音", silkPath, duration);
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath, ElementType.PTT);
if (converted){
// fs.unlink(silkPath, ()=>{});
fs.unlink(silkPath, ()=>{});
}
return {
elementType: ElementType.PTT,

View File

@ -1,7 +1,14 @@
// Electron 主进程 与 渲染进程 交互的桥梁
import {Config} from "./common/types";
import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "./common/channels";
import {Config, LLOneBotError} from "./common/types";
import {
CHANNEL_ERROR,
CHANNEL_GET_CONFIG,
CHANNEL_LOG,
CHANNEL_SELECT_FILE,
CHANNEL_SET_CONFIG,
} from "./common/channels";
const {contextBridge} = require("electron");
const {ipcRenderer} = require('electron');
@ -12,9 +19,15 @@ const llonebot = {
setConfig: (config: Config) => {
ipcRenderer.send(CHANNEL_SET_CONFIG, config);
},
getConfig: async () => {
getConfig: async (): Promise<Config> => {
return ipcRenderer.invoke(CHANNEL_GET_CONFIG);
},
getError: async (): Promise<LLOneBotError> => {
return ipcRenderer.invoke(CHANNEL_ERROR);
},
selectFile: (): Promise<string> => {
return ipcRenderer.invoke(CHANNEL_SELECT_FILE);
}
}
export type LLOneBot = typeof llonebot;

View File

@ -2,6 +2,7 @@
// 打开设置界面时触发
async function onSettingWindowCreated(view: Element) {
window.llonebot.log("setting window created");
const isEmpty = (value: any) => value === undefined || value === null || value === '';
@ -10,7 +11,8 @@ async function onSettingWindowCreated(view: Element) {
const httpPostClass = "http-post";
const wsClass = "ws";
const reverseWSClass = "reverse-ws";
const llonebotError = await window.llonebot.getError();
window.llonebot.log("获取error" + JSON.stringify(llonebotError));
function createHttpHostEleStr(host: string) {
let eleStr = `
<setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}">
@ -48,6 +50,18 @@ async function onSettingWindowCreated(view: Element) {
let html = `
<div class="config_view llonebot">
<setting-section>
<setting-panel id="llonebotError" style="display:${llonebotError.ffmpegError || llonebotError.otherError ? '' : 'none'}">
<setting-item id="ffmpegError" data-direction="row"
style="diplay:${llonebotError.ffmpegError ? '' : 'none'}"
class="hostItem vertical-list-item">
<setting-text data-type="secondary" class="err-content">${llonebotError.ffmpegError}</setting-text>
</setting-item>
<setting-item id="otherError" data-direction="row"
style="diplay:${llonebotError.otherError ? '' : 'none'}"
class="hostItem vertical-list-item">
<setting-text data-type="secondary" class="err-content">${llonebotError.otherError}</setting-text>
</setting-item>
</setting-panel>
<setting-panel>
<setting-list class="wrap">
<setting-item data-direction="row" class="hostItem vertical-list-item">
@ -107,7 +121,16 @@ async function onSettingWindowCreated(view: Element) {
</setting-list>
</setting-panel>
<setting-panel>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item" style="width: 80%">
<setting-text>ffmpeg路径</setting-text>
<input id="ffmpegPath" class="input-text" type="text"
style="width:80%;padding: 5px"
value="${config.ffmpeg || ''}"/>
</setting-item>
<button id="selectFFMPEG" class="q-button q-button--small q-button--secondary">ffmpeg</button>
</setting-item>
<setting-item data-direction="row" class="vertical-list-item">
<div>
<setting-text></setting-text>
<setting-text data-type="secondary"> <a href="javascript:LiteLoader.api.openExternal('https://github.com/botuniverse/onebot-11/tree/master/message#readme');">OneBot v11 </a></setting-text>
@ -117,35 +140,35 @@ async function onSettingWindowCreated(view: Element) {
<setting-option data-value="string" ${config.ob11.messagePostFormat === "string" ? "is-selected" : ""}>CQ码</setting-option>
</setting-select>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div></div>
<div class="tips">()http链接或base64编码</div>
</div>
<setting-switch id="switchFileUrl" ${config.enableLocalFile2Url ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div>debug模式</div>
<div class="tips">raw字段附带原始消息</div>
</div>
<setting-switch id="debug" ${config.debug ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div></div>
<div class="tips"></div>
</div>
<setting-switch id="reportSelfMessage" ${config.reportSelfMessage ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div></div>
<div class="tips">目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
</div>
<setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch>
</setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item">
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div></div>
<div class="tips"></div>
@ -173,6 +196,36 @@ async function onSettingWindowCreated(view: Element) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const getError = async ()=> {
const llonebotError = await window.llonebot.getError();
console.log(llonebotError);
const llonebotErrorEle = document.getElementById("llonebotError");
const ffmpegErrorEle = document.getElementById("ffmpegError");
const otherErrorEle = document.getElementById("otherError");
if (llonebotError.otherError || llonebotError.ffmpegError){
llonebotErrorEle.style.display = ''
}
else{
llonebotErrorEle.style.display = 'none'
}
if (llonebotError.ffmpegError) {
const errContentEle = doc.querySelector("#ffmpegError .err-content")
// const errContent = ffmpegErrorEle.getElementsByClassName("err-content")[0];
errContentEle.textContent = llonebotError.ffmpegError;
(ffmpegErrorEle as HTMLElement).style.display = ''
}
else{
ffmpegErrorEle.style.display = ''
}
if (llonebotError.otherError) {
const errContentEle = doc.querySelector("#otherError .err-content")
errContentEle.textContent = llonebotError.otherError;
otherErrorEle.style.display = ''
}
else{
otherErrorEle.style.display = 'none'
}
}
function addHostEle(type: string, initValue: string = "") {
let addressEle, hostItemsEle;
@ -197,8 +250,8 @@ async function onSettingWindowCreated(view: Element) {
window.llonebot.setConfig(config);
})
function switchClick(eleId: string, configKey: string, _config=null) {
if (!_config){
function switchClick(eleId: string, configKey: string, _config = null) {
if (!_config) {
_config = config
}
doc.getElementById(eleId)?.addEventListener("click", (e) => {
@ -242,6 +295,7 @@ async function onSettingWindowCreated(view: Element) {
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 ffmpegPathEle = document.getElementById("ffmpegPath") as HTMLInputElement;
// 获取端口和host
const httpPort = httpPortEle.value
@ -266,15 +320,25 @@ async function onSettingWindowCreated(view: Element) {
config.ob11.wsPort = parseInt(wsPort);
config.ob11.wsHosts = wsHosts;
config.token = token;
config.ffmpeg = ffmpegPathEle.value.trim();
window.llonebot.setConfig(config);
setTimeout(()=>{
getError().then();
}, 1000);
alert("保存成功");
})
doc.getElementById("selectFFMPEG")?.addEventListener("click", ()=>{
window.llonebot.selectFile().then(selectPath=>{
if (selectPath){
(document.getElementById("ffmpegPath") as HTMLInputElement).value = selectPath;
}
});
})
doc.body.childNodes.forEach(node => {
view.appendChild(node);
});
}

View File

@ -1,4 +1,5 @@
// import path from "path";
const webpack = require('webpack');
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
@ -62,7 +63,7 @@ let config = {
},
plugins: [
new CopyPlugin({
patterns: copyModules.map(m=>{
patterns: copyModules.map(m => {
m = `node_modules/${m}`
return {
from: m,
@ -70,6 +71,9 @@ let config = {
}
})
}),
new webpack.DefinePlugin({
'process.env.FLUENTFFMPEG_COV': false,
}),
], // devtool: 'source-map',
}