mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
feat: auto encode silk
This commit is contained in:
parent
0e4de038ca
commit
c4e54fa259
@ -43,8 +43,8 @@ LiteLoaderQQNT的OneBot11协议插件
|
||||
- [x] @群成员
|
||||
- [x] 语音
|
||||
- [x] json消息(只上报)
|
||||
- [x] 转发消息记录(目前只能发不能收)
|
||||
- [ ] 红包
|
||||
- [ ] 转发消息记录
|
||||
- [ ] xml
|
||||
|
||||
支持的api:
|
||||
@ -109,7 +109,7 @@ LiteLoaderQQNT的OneBot11协议插件
|
||||
## TODO
|
||||
- [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
|
||||
- [x] 支持正、反向websocket(感谢@disymayufei的PR)
|
||||
- [ ] 转发消息记录
|
||||
- [x] 转发消息记录
|
||||
- [ ] 好友点赞api
|
||||
|
||||
## onebot11文档
|
||||
|
892
package-lock.json
generated
892
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -5,13 +5,13 @@
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"postinstall": "ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save",
|
||||
"postinstall": "cross-env ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save",
|
||||
"build": "npm run build-main && npm run build-preload && npm run build-renderer",
|
||||
"build-main": "webpack --config webpack.main.config.js",
|
||||
"build-preload": "webpack --config webpack.preload.config.js",
|
||||
"build-renderer": "webpack --config webpack.renderer.config.js",
|
||||
"build-mac": "npm run build && cp manifest.json dist/ && npm run deploy-mac",
|
||||
"deploy-mac": "cp dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOnebot/",
|
||||
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOnebot/",
|
||||
"build-win": "npm run build && cp manifest.json dist/ && npm run deploy-win",
|
||||
"deploy-win": "cmd /c \"copy dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOnebot\\\""
|
||||
},
|
||||
@ -20,6 +20,8 @@
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"json-bigint": "^1.0.0",
|
||||
"music-metadata": "^8.1.4",
|
||||
"silk-wasm": "^3.2.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
@ -29,10 +31,11 @@
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-loader": "^9.5.0",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"ws": "^8.16.0"
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ import * as path from "path";
|
||||
import {selfInfo} from "./data";
|
||||
import {ConfigUtil} from "./config";
|
||||
import util from "util";
|
||||
|
||||
const fs = require('fs');
|
||||
import {encode, getDuration} from "silk-wasm";
|
||||
import fs from 'fs';
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
|
||||
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
||||
|
||||
@ -13,7 +14,7 @@ export function getConfigUtil() {
|
||||
}
|
||||
|
||||
export function log(...msg: any[]) {
|
||||
if (!getConfigUtil().getConfig().log){
|
||||
if (!getConfigUtil().getConfig().log) {
|
||||
return
|
||||
}
|
||||
let currentDateTime = new Date().toLocaleString();
|
||||
@ -24,9 +25,9 @@ export function log(...msg: any[]) {
|
||||
const currentDate = `${year}-${month}-${day}`;
|
||||
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : ""
|
||||
let logMsg = "";
|
||||
for (let msgItem of msg){
|
||||
for (let msgItem of msg) {
|
||||
// 判断是否是对象
|
||||
if (typeof msgItem === "object"){
|
||||
if (typeof msgItem === "object") {
|
||||
logMsg += JSON.stringify(msgItem) + " ";
|
||||
continue;
|
||||
}
|
||||
@ -35,7 +36,7 @@ export function log(...msg: any[]) {
|
||||
logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n`
|
||||
// sendLog(...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) => {
|
||||
|
||||
})
|
||||
}
|
||||
@ -54,7 +55,7 @@ export function sleep(ms: number): Promise<void> {
|
||||
|
||||
|
||||
// 定义一个异步函数来检查文件是否存在
|
||||
export function checkFileReceived(path: string, timeout: number=3000): Promise<void> {
|
||||
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
@ -72,7 +73,7 @@ export function checkFileReceived(path: string, timeout: number=3000): Promise<v
|
||||
});
|
||||
}
|
||||
|
||||
export async function file2base64(path: string){
|
||||
export async function file2base64(path: string) {
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
let result = {
|
||||
err: "",
|
||||
@ -109,10 +110,69 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
|
||||
// 如果老对象和新对象的当前属性都是对象,则递归合并
|
||||
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
|
||||
mergeNewProperties(newObj[key], oldObj[key]);
|
||||
} else if(typeof oldObj[key] === 'object' || typeof newObj[key] === 'object'){
|
||||
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
|
||||
// 属性冲突,有一方不是对象,直接覆盖
|
||||
oldObj[key] = newObj[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function encodeSilk(filePath: string) {
|
||||
function getFileHeader(filePath: string) {
|
||||
// 定义要读取的字节数
|
||||
const bytesToRead = 7;
|
||||
try {
|
||||
const buffer = fs.readFileSync(filePath, {
|
||||
encoding: null,
|
||||
flag: "r",
|
||||
});
|
||||
|
||||
const fileHeader = buffer.toString("hex", 0, bytesToRead);
|
||||
return fileHeader;
|
||||
} catch (err) {
|
||||
console.error("读取文件错误:", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAudioSampleRate(filePath: string) {
|
||||
try {
|
||||
const mm = await import('music-metadata');
|
||||
const metadata = await mm.parseFile(filePath);
|
||||
log(`${filePath}采样率`, metadata.format.sampleRate);
|
||||
return metadata.format.sampleRate;
|
||||
} catch (error) {
|
||||
log(`${filePath}采样率获取失败`, error.stack);
|
||||
// console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
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 sampleRate = await getAudioSampleRate(filePath) || 44100;
|
||||
const silk = await encode(pcm, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
log(`语音文件${filePath}转换成功!`)
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration,
|
||||
};
|
||||
} else {
|
||||
const duration = getDuration(pcm);
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration: duration,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
log("convert silk failed", error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
import {BrowserWindow, 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 {initWebsocket, postMsg} from "../onebot11/server";
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
SendTextElement
|
||||
} from "./types";
|
||||
import {NTQQApi} from "./ntcall";
|
||||
import {encodeSilk, log} from "../common/utils";
|
||||
import fs from "fs";
|
||||
|
||||
|
||||
export class SendMsgElementConstructor {
|
||||
@ -79,7 +81,12 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
|
||||
static async ptt(pttPath: string): Promise<SendPttElement> {
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath);
|
||||
const {converted, path: silkPath, duration} = await encodeSilk(pttPath);
|
||||
// log("生成语音", silkPath, duration);
|
||||
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath);
|
||||
if (converted){
|
||||
fs.unlink(silkPath, ()=>{});
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
elementId: "",
|
||||
@ -88,7 +95,8 @@ export class SendMsgElementConstructor {
|
||||
filePath: path,
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize,
|
||||
duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算
|
||||
// duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算
|
||||
duration: duration / 1000,
|
||||
formatType: 1,
|
||||
voiceType: 1,
|
||||
voiceChangeType: 0,
|
||||
|
@ -3,10 +3,10 @@ import {log, sleep} from "../common/utils";
|
||||
import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
|
||||
import {Group, RawMessage, User} from "./types";
|
||||
import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
|
||||
import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
|
||||
import {postMsg} from "../onebot11/server";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
|
||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {ipcMain} from "electron";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook";
|
||||
import {log} from "../common/utils";
|
||||
import {ChatType, Friend, Group, GroupMember, RawMessage, SelfInfo, SendMessageElement, User} from "./types";
|
||||
import * as fs from "fs";
|
||||
import {addHistoryMsg, msgHistory, selfInfo} from "../common/data";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
|
||||
interface IPCReceiveEvent {
|
||||
eventName: string
|
||||
|
@ -4,11 +4,11 @@ import {OB11MessageData, OB11MessageDataType, OB11MessageNode, OB11PostSendMsg}
|
||||
import {NTQQApi, Peer} from "../../ntqqapi/ntcall";
|
||||
import {SendMsgElementConstructor} from "../../ntqqapi/constructor";
|
||||
import {uri2local} from "../utils";
|
||||
import {v4 as uuid4} from 'uuid';
|
||||
import BaseAction from "./BaseAction";
|
||||
import {ActionName, BaseCheckResult} from "./types";
|
||||
import * as fs from "fs";
|
||||
import {log, sleep} from "../../common/utils";
|
||||
import {log} from "../../common/utils";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
|
||||
function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
||||
function checkUri(uri: string): boolean {
|
||||
@ -55,7 +55,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
const messages = this.convertMessage2List(payload);
|
||||
const fmNum = this.forwardMsgNum(payload)
|
||||
if ( fmNum && fmNum != messages.length) {
|
||||
if (fmNum && fmNum != messages.length) {
|
||||
return {
|
||||
valid: false,
|
||||
message: "转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素"
|
||||
@ -227,7 +227,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
case OB11MessageDataType.voice: {
|
||||
const file = sendMsg.data?.file
|
||||
if (file) {
|
||||
const {path, isLocal} = (await uri2local(uuid4(), file))
|
||||
const {path, isLocal} = (await uri2local(uuidv4(), file))
|
||||
if (path) {
|
||||
if (!isLocal) { // 只删除http和base64转过来的文件
|
||||
deleteAfterSentFiles.push(path)
|
||||
@ -239,15 +239,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
// case OB11MessageDataType.node: {
|
||||
// try {
|
||||
// await this.handleForwardNode(peer, sendMsg, group);
|
||||
// } catch (e) {
|
||||
// log("forward msg crash", e.stack)
|
||||
// }
|
||||
// }
|
||||
} break;
|
||||
}
|
||||
|
||||
}
|
||||
@ -258,7 +250,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
|
||||
private async send(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete=false) {
|
||||
private async send(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete = false) {
|
||||
if (!sendElements.length) {
|
||||
throw ("消息体无法解析")
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node",
|
||||
// "sourceMap": true
|
||||
},
|
||||
"include": ["src/*"],
|
||||
"exclude": [
|
||||
|
@ -1,8 +1,14 @@
|
||||
// import path from "path";
|
||||
const path = require('path');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
const ignoreModules = [
|
||||
"silk-wasm", "electron"
|
||||
];
|
||||
const copyModules = ["silk-wasm"]
|
||||
|
||||
let config = {
|
||||
// target: 'node',
|
||||
entry: {
|
||||
// main: './src/main.ts',
|
||||
@ -15,11 +21,10 @@ module.exports = {
|
||||
// libraryTarget: "commonjs2",
|
||||
// chunkFormat: "commonjs",
|
||||
},
|
||||
externals: [
|
||||
// "express",
|
||||
"electron", "fs"],
|
||||
externals: ignoreModules,
|
||||
experiments: {
|
||||
// outputModule: true
|
||||
// asyncWebAssembly: true
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts']
|
||||
@ -33,7 +38,6 @@ module.exports = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -46,8 +50,7 @@ module.exports = {
|
||||
// configFile: 'src/tsconfig.json'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
@ -56,5 +59,18 @@ module.exports = {
|
||||
extractComments: false,
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: copyModules.map(m=>{
|
||||
m = `node_modules/${m}`
|
||||
return {
|
||||
from: m,
|
||||
to: m
|
||||
}
|
||||
})
|
||||
}),
|
||||
], // devtool: 'source-map',
|
||||
}
|
||||
|
||||
module.exports = config
|
Loading…
x
Reference in New Issue
Block a user