mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
feat: convert wav by ffmpeg
This commit is contained in:
parent
730294236c
commit
d57c14a8b9
107
package-lock.json
generated
107
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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"
|
@ -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>();
|
@ -19,4 +19,10 @@ export interface Config {
|
||||
reportSelfMessage?: boolean
|
||||
log?: boolean
|
||||
autoDeleteFile?: boolean
|
||||
ffmpeg?: string // ffmpeg路径
|
||||
}
|
||||
|
||||
export type LLOneBotError = {
|
||||
ffmpegError?: string
|
||||
otherError?: string
|
||||
}
|
@ -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,
|
||||
|
105
src/main/main.ts
105
src/main/main.ts
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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',
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user