Compare commits

..

21 Commits

Author SHA1 Message Date
linyuchen
1f657f3e84 Merge branch 'dev' 2024-02-26 23:24:03 +08:00
linyuchen
329dc433fb refactor: ffmpeg setting ui 2024-02-26 23:22:34 +08:00
linyuchen
90f64ab04e docs: update readme thanks 2024-02-26 22:30:27 +08:00
linyuchen
1583a36c2e Merge remote-tracking branch 'origin/main' 2024-02-26 22:26:38 +08:00
linyuchen
d70e95a451 chore: ver 3.8 2024-02-26 22:25:21 +08:00
linyuchen
c6256abcb2 fix: report image url filed 2024-02-26 22:21:20 +08:00
linyuchen
d57c14a8b9 feat: convert wav by ffmpeg 2024-02-26 22:19:37 +08:00
linyuchen
82268c619c Merge pull request #82 from disymayufei/patch-1 2024-02-26 17:37:53 +08:00
Disy
befdf8571a chore: Update README.md
补全README的一些信息
2024-02-26 16:04:57 +08:00
linyuchen
730294236c feat: convert wav by ffmpeg 2024-02-25 12:46:37 +08:00
linyuchen
d9d7e9e830 feat: auto delete receive file 2024-02-25 02:17:18 +08:00
linyuchen
6170307241 Merge remote-tracking branch 'origin/dev' into dev 2024-02-25 01:28:44 +08:00
linyuchen
138614cc4a feat: 好友请求时间,处理好友请求api 2024-02-25 01:28:15 +08:00
linyuchen
62870576a1 feat: 好友请求时间,处理还有请求api 2024-02-25 01:27:25 +08:00
linyuchen
cfb066971f feat: 上报支持CQCode 2024-02-24 18:27:49 +08:00
linyuchen
4941f0071a Merge remote-tracking branch 'origin/dev' into dev 2024-02-24 17:53:35 +08:00
linyuchen
6e61621f44 Merge pull request #75 from MisaLiu/feat_msg_format
增加对 `event.message_format` 和 CQ 码(仅接收)的支持
2024-02-24 17:47:44 +08:00
linyuchen
eb1a867a0e refactor: senderShowName of forward message 2024-02-24 17:24:30 +08:00
Misa Liu
f9ec7eddf2 feat: Support CQCode message format 2024-02-24 01:06:41 +08:00
Misa Liu
ffdec86209 feat: Add setting section of messagePostFormat 2024-02-24 00:40:45 +08:00
Misa Liu
66de0076d4 feat: Add message_format to message event 2024-02-23 21:48:36 +08:00
32 changed files with 1497 additions and 150 deletions

View File

@@ -17,7 +17,7 @@ jobs:
node-version: 18 node-version: 18
- name: install dependenies - name: install dependenies
run: npm install run: export ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install
- name: build - name: build
run: npm run build run: npm run build

View File

@@ -33,13 +33,15 @@ TG群<https://t.me/+nLZEnpne-pQ1OWFl>
- [x] 获取群列表 - [x] 获取群列表
- [x] 获取群成员列表 - [x] 获取群成员列表
- [x] 撤回消息 - [x] 撤回消息
- [x] 处理添加好友请求
- [x] 处理加群请求 - [x] 处理加群请求
- [x] 退群 - [x] 退群
- [x] 上报好友消息 - [x] 上报好友消息
- [x] 上报添加好友请求
- [x] 上报群消息 - [x] 上报群消息
- [x] 上报好友、群消息撤回 - [x] 上报好友、群消息撤回
- [x] 上报加群请求 - [x] 上报加群请求
- [x] 上报群员人数变动 - [x] 上报群员人数变动(尚不支持识别群员人数变动原因)
消息格式支持: 消息格式支持:
- [x] cq码 - [x] cq码
@@ -65,6 +67,7 @@ TG群<https://t.me/+nLZEnpne-pQ1OWFl>
- [x] get_group_member_list - [x] get_group_member_list
- [x] get_group_member_info - [x] get_group_member_info
- [x] get_friend_list - [x] get_friend_list
- [x] set_friend_add_request
- [x] get_msg - [x] get_msg
- [x] send_like - [x] send_like
- [x] set_group_add_request - [x] set_group_add_request
@@ -122,3 +125,9 @@ TG群<https://t.me/+nLZEnpne-pQ1OWFl>
## onebot11文档 ## onebot11文档
<https://11.onebot.dev/> <https://11.onebot.dev/>
## 鸣谢
* [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
* [LLAPI](https://github.com/Night-stars-1/LiteLoaderQQNT-Plugin-LLAPI)
* chronocat
* [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot)

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "LiteLoaderQQNT的OneBotApi", "description": "LiteLoaderQQNT的OneBotApi",
"version": "3.7.0", "version": "3.8.0",
"thumbnail": "./icon.png", "thumbnail": "./icon.png",
"authors": [ "authors": [
{ {

883
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"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": "npm run build-main && npm run build-preload && npm run build-renderer",
"build-main": "webpack --config webpack.main.config.js", "build-main": "webpack --config webpack.main.config.js",
"build-preload": "webpack --config webpack.preload.config.js", "build-preload": "webpack --config webpack.preload.config.js",
@@ -19,21 +18,24 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.18.2", "express": "^4.18.2",
"fluent-ffmpeg": "^2.1.2",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"music-metadata": "^8.1.4", "music-metadata": "^8.1.4",
"silk-wasm": "^3.2.3", "silk-wasm": "^3.2.3",
"utf-8-validate": "^6.0.3",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.23.2", "@babel/preset-env": "^7.23.2",
"@types/express": "^4.17.20", "@types/express": "^4.17.20",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^20.11.19", "@types/node": "^20.11.19",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"copy-webpack-plugin": "^12.0.2", "copy-webpack-plugin": "^12.0.2",
"cross-env": "^7.0.3", "electron": "^29.0.1",
"ts-loader": "^9.5.0", "ts-loader": "^9.5.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"webpack": "^5.89.0", "webpack": "^5.89.0",

View File

@@ -3,3 +3,5 @@ import {Peer} from "../ntqqapi/ntcall";
export const CHANNEL_GET_CONFIG = "llonebot_get_config" export const CHANNEL_GET_CONFIG = "llonebot_get_config"
export const CHANNEL_SET_CONFIG = "llonebot_set_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

@@ -29,7 +29,8 @@ export class ConfigUtil {
enableHttp: true, enableHttp: true,
enableHttpPost: true, enableHttpPost: true,
enableWs: true, enableWs: true,
enableWsReverse: false enableWsReverse: false,
messagePostFormat: "array",
} }
let defaultConfig: Config = { let defaultConfig: Config = {
ob11: ob11Default, ob11: ob11Default,
@@ -38,7 +39,8 @@ export class ConfigUtil {
enableLocalFile2Url: false, enableLocalFile2Url: false,
debug: false, debug: false,
log: false, log: false,
reportSelfMessage: false reportSelfMessage: false,
autoDeleteFile: false,
}; };
if (!fs.existsSync(this.configPath)) { if (!fs.existsSync(this.configPath)) {

View File

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

View File

@@ -7,6 +7,7 @@ export interface OB11Config {
enableHttpPost?: boolean enableHttpPost?: boolean
enableWs?: boolean enableWs?: boolean
enableWsReverse?: boolean enableWsReverse?: boolean
messagePostFormat?: 'array' | 'string'
} }
export interface Config { export interface Config {
@@ -17,4 +18,11 @@ export interface Config {
debug?: boolean debug?: boolean
reportSelfMessage?: boolean reportSelfMessage?: boolean
log?: boolean log?: boolean
autoDeleteFile?: boolean
ffmpeg?: string // ffmpeg路径
}
export type LLOneBotError = {
ffmpegError?: string
otherError?: string
} }

View File

@@ -5,6 +5,8 @@ import util from "util";
import {encode, getDuration} from "silk-wasm"; import {encode, getDuration} from "silk-wasm";
import fs from 'fs'; import fs from 'fs';
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {exec} from "node:child_process";
import ffmpeg from "fluent-ffmpeg"
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data; export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
@@ -136,7 +138,23 @@ export function mergeNewProperties(newObj: any, oldObj: any) {
}); });
} }
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);
});
});
}
export async function encodeSilk(filePath: string) { export async function encodeSilk(filePath: string) {
const fsp = require("fs").promises
function getFileHeader(filePath: string) { function getFileHeader(filePath: string) {
// 定义要读取的字节数 // 定义要读取的字节数
const bytesToRead = 7; const bytesToRead = 7;
@@ -154,6 +172,35 @@ export async function encodeSilk(filePath: string) {
} }
} }
function isWavFile(filePath: string) {
return new Promise((resolve, reject) => {
fs.open(filePath, 'r', (err, fd) => {
if (err) {
reject(err);
return;
}
// 读取前12个字节
const buffer = Buffer.alloc(12);
fs.read(fd, buffer, 0, 12, 0, (err, bytesRead, buffer) => {
if (err) {
reject(err);
return;
}
fs.close(fd, (err) => {
if (err) {
reject(err);
return;
}
// 检查RIFF头和WAVE格式标识
const isRIFF = buffer.toString('utf8', 0, 4) === 'RIFF';
const isWAVE = buffer.toString('utf8', 8, 12) === 'WAVE';
resolve(isRIFF && isWAVE);
});
});
});
});
}
async function getAudioSampleRate(filePath: string) { async function getAudioSampleRate(filePath: string) {
try { try {
const mm = await import('music-metadata'); const mm = await import('music-metadata');
@@ -168,20 +215,45 @@ export async function encodeSilk(filePath: string) {
try { try {
const fileName = path.basename(filePath); const fileName = path.basename(filePath);
const pcm = fs.readFileSync(filePath);
const pttPath = path.join(CONFIG_DIR, uuidv4()); const pttPath = path.join(CONFIG_DIR, uuidv4());
if (getFileHeader(filePath) !== "02232153494c4b") { if (getFileHeader(filePath) !== "02232153494c4b") {
log(`语音文件${filePath}需要转换`) log(`语音文件${filePath}需要转换`)
const isWav = await isWavFile(filePath);
if (!isWav) {
log(`语音文件${filePath}正在转换成wav`)
// 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 () {
log('wav转换完成');
})
.on('error', function (err) {
log(`wav转换出错: `, err.message,);
reject(err);
})
.save(wavPath)
.on("end", ()=>{
filePath = wavPath
resolve(wavPath);
});
})
const sampleRate = await getAudioSampleRate(filePath) || 44100; const sampleRate = await getAudioSampleRate(filePath) || 44100;
const pcm = fs.readFileSync(filePath);
const silk = await encode(pcm, sampleRate); const silk = await encode(pcm, sampleRate);
fs.writeFileSync(pttPath, silk.data); fs.writeFileSync(pttPath, silk.data);
log(`语音文件${filePath}转换成功!`) fs.unlink(wavPath, (err) => {});
log(`语音文件${filePath}转换成功!`, pttPath)
return { return {
converted: true, converted: true,
path: pttPath, path: pttPath,
duration: silk.duration, duration: silk.duration,
}; };
} else { } else {
const pcm = fs.readFileSync(filePath);
const duration = getDuration(pcm); const duration = getDuration(pcm);
return { return {
converted: false, converted: false,
@@ -189,6 +261,7 @@ export async function encodeSilk(filePath: string) {
duration: duration, duration: duration,
}; };
} }
}
} catch (error) { } catch (error) {
log("convert silk failed", error.stack); log("convert silk failed", error.stack);
return {}; return {};

2
src/global.d.ts vendored
View File

@@ -4,7 +4,7 @@ import {LLOneBot} from "./preload";
declare global { declare global {
interface Window { interface Window {
llonebot: typeof llonebot; llonebot: LLOneBot;
LiteLoader: any; LiteLoader: any;
} }
} }

View File

@@ -1,24 +1,48 @@
// 运行在 Electron 主进程 下的插件入口 // 运行在 Electron 主进程 下的插件入口
import {BrowserWindow, ipcMain} from 'electron'; import {BrowserWindow, dialog, ipcMain} from 'electron';
import fs from 'fs'; import fs from 'fs';
import {Config} from "../common/types"; 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 {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, getGroup, getGroupMember, groupNotifies, msgHistory, selfInfo} from "../common/data"; import {
addHistoryMsg,
friendRequests,
getGroup,
getGroupMember,
groupNotifies,
llonebotError,
msgHistory,
selfInfo
} from "../common/data";
import {hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook"; import {hookNTQQApiCall, 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 {ChatType, GroupMember, GroupNotifies, GroupNotifyTypes, RawMessage} from "../ntqqapi/types"; import {
ChatType,
FriendRequestNotify,
GroupMember,
GroupNotifies,
GroupNotifyTypes,
RawMessage
} from "../ntqqapi/types";
import {ob11HTTPServer} from "../onebot11/server/http"; import {ob11HTTPServer} from "../onebot11/server/http";
import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent"; import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent";
import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent"; import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent";
import {postEvent} from "../onebot11/server/postevent"; import {postOB11Event} from "../onebot11/server/postOB11Event";
import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket"; import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket";
import {OB11GroupAdminNoticeEvent} from "../onebot11/event/notice/OB11GroupAdminNoticeEvent"; import {OB11GroupAdminNoticeEvent} from "../onebot11/event/notice/OB11GroupAdminNoticeEvent";
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent"; import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupRequestEvent} from "../onebot11/event/request/OB11GroupRequest"; import {OB11GroupRequestEvent} from "../onebot11/event/request/OB11GroupRequest";
import {OB11FriendRequestEvent} from "../onebot11/event/request/OB11FriendRequest";
import * as path from "node:path";
let running = false; let running = false;
@@ -27,13 +51,48 @@ let running = false;
// 加载插件时触发 // 加载插件时触发
function onLoad() { function onLoad() {
log("llonebot main 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)) { if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, {recursive: true}); fs.mkdirSync(CONFIG_DIR, {recursive: true});
} }
ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => { ipcMain.handle(CHANNEL_ERROR, (event, arg) => {
return getConfigUtil().getConfig() 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(); let oldConfig = getConfigUtil().getConfig();
getConfigUtil().setConfig(arg) getConfigUtil().setConfig(arg)
if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) { if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) {
@@ -78,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); log(arg);
}) })
function postReceiveMsg(msgList: RawMessage[]) { function postReceiveMsg(msgList: RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig(); const {debug, reportSelfMessage} = getConfigUtil().getConfig();
for (let message of msgList) { for (let message of msgList) {
@@ -101,7 +167,7 @@ function onLoad() {
if (isSelfMsg && !reportSelfMessage) { if (isSelfMsg && !reportSelfMessage) {
return return
} }
postEvent(msg); postOB11Event(msg);
// log("post msg", msg) // log("post msg", msg)
}).catch(e => log("constructMessage error: ", e.toString())); }).catch(e => log("constructMessage error: ", e.toString()));
} }
@@ -126,7 +192,7 @@ function onLoad() {
} }
if (message.chatType == ChatType.friend) { if (message.chatType == ChatType.friend) {
const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message.senderUin), oriMessage.msgShortId); const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message.senderUin), oriMessage.msgShortId);
postEvent(friendRecallEvent); postOB11Event(friendRecallEvent);
} else if (message.chatType == ChatType.group) { } else if (message.chatType == ChatType.group) {
let operatorId = message.senderUin let operatorId = message.senderUin
for (const element of message.elements) { for (const element of message.elements) {
@@ -141,7 +207,7 @@ function onLoad() {
oriMessage.msgShortId oriMessage.msgShortId
) )
postEvent(groupRecallEvent); postOB11Event(groupRecallEvent);
} }
continue continue
} }
@@ -179,7 +245,9 @@ function onLoad() {
log("获取群通知详情完成", notifies, payload); log("获取群通知详情完成", notifies, payload);
try { try {
for (const notify of notifies) { for (const notify of notifies) {
if (parseInt(notify.seq) / 1000 < startTime){ const notifyTime = parseInt(notify.seq) / 1000
log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
if (notifyTime < startTime) {
continue; continue;
} }
const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid); const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
@@ -196,25 +264,22 @@ function onLoad() {
log("变动管理员获取成功") log("变动管理员获取成功")
groupAdminNoticeEvent.user_id = parseInt(member1.uin); groupAdminNoticeEvent.user_id = parseInt(member1.uin);
groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? "unset" : "set"; groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? "unset" : "set";
postEvent(groupAdminNoticeEvent, true); postOB11Event(groupAdminNoticeEvent, true);
} } else {
else{
log("获取群通知的成员信息失败", notify, getGroup(notify.group.groupCode)); log("获取群通知的成员信息失败", notify, getGroup(notify.group.groupCode));
} }
} } else if (notify.type == GroupNotifyTypes.MEMBER_EXIT) {
else if (notify.type == GroupNotifyTypes.MEMBER_EXIT){
log("有成员退出通知"); log("有成员退出通知");
let groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(notify.group.groupCode), parseInt(member1.uin)) let groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(notify.group.groupCode), parseInt(member1.uin))
// postEvent(groupDecreaseEvent, true); // postEvent(groupDecreaseEvent, true);
} } else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)){
log("有加群请求"); log("有加群请求");
groupNotifies[notify.seq] = notify; groupNotifies[notify.seq] = notify;
let groupRequestEvent = new OB11GroupRequestEvent(); let groupRequestEvent = new OB11GroupRequestEvent();
groupRequestEvent.group_id = parseInt(notify.group.groupCode); groupRequestEvent.group_id = parseInt(notify.group.groupCode);
let requestQQ = "" let requestQQ = ""
try { try {
requestQQ = (await NTQQApi.getUserInfo(notify.user1.uid)).uin; requestQQ = (await NTQQApi.getUserDetailInfo(notify.user1.uid)).uin;
} catch (e) { } catch (e) {
log("获取加群人QQ号失败", e) log("获取加群人QQ号失败", e)
} }
@@ -222,7 +287,7 @@ function onLoad() {
groupRequestEvent.sub_type = "add" groupRequestEvent.sub_type = "add"
groupRequestEvent.comment = notify.postscript; groupRequestEvent.comment = notify.postscript;
groupRequestEvent.flag = notify.seq; groupRequestEvent.flag = notify.seq;
postEvent(groupRequestEvent); postOB11Event(groupRequestEvent);
} }
} }
} catch (e) { } catch (e) {
@@ -230,13 +295,40 @@ function onLoad() {
} }
} }
}) })
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)) {
friendRequests[req.sourceId] = req;
log("有新的好友请求", req);
let friendRequestEvent = new OB11FriendRequestEvent();
try {
let requester = await NTQQApi.getUserDetailInfo(req.friendUid)
friendRequestEvent.user_id = parseInt(requester.uin);
} catch (e) {
log("获取加好友者QQ号失败", e);
} }
friendRequestEvent.flag = req.sourceId.toString();
friendRequestEvent.comment = req.extWords;
postOB11Event(friendRequestEvent);
}
}
})
}
let startTime = 0; let startTime = 0;
async function start() { async function start() {
startTime = Date.now(); startTime = Date.now();
startReceiveHook().then(); startReceiveHook().then();
NTQQApi.getGroups(true).then() NTQQApi.getGroups(true).then()
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
// 检查ffmpeg
checkFFMPEG(config.ffmpeg).then(exist => {
if (!exist) {
llonebotError.ffmpegError = `没有找到ffmpeg,音频只能发送wav和silk`
}
})
if (config.ob11.enableHttp) { if (config.ob11.enableHttp) {
try { try {
ob11HTTPServer.start(config.ob11.httpPort) ob11HTTPServer.start(config.ob11.httpPort)

View File

@@ -8,7 +8,7 @@ import {
SendTextElement SendTextElement
} from "./types"; } from "./types";
import {NTQQApi} from "./ntcall"; import {NTQQApi} from "./ntcall";
import {encodeSilk, log} from "../common/utils"; import {encodeSilk} from "../common/utils";
import fs from "fs"; import fs from "fs";
@@ -83,7 +83,7 @@ export class SendMsgElementConstructor {
static async ptt(pttPath: string): Promise<SendPttElement> { static async ptt(pttPath: string): Promise<SendPttElement> {
const {converted, path: silkPath, duration} = await encodeSilk(pttPath); const {converted, path: silkPath, duration} = await encodeSilk(pttPath);
// log("生成语音", silkPath, duration); // log("生成语音", silkPath, duration);
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath); const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(silkPath, ElementType.PTT);
if (converted){ if (converted){
fs.unlink(silkPath, ()=>{}); fs.unlink(silkPath, ()=>{});
} }

View File

@@ -1,13 +1,14 @@
import {BrowserWindow} from 'electron'; import {BrowserWindow} from 'electron';
import {log, sleep} from "../common/utils"; import {getConfigUtil, log, sleep} from "../common/utils";
import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall"; import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
import {Group, RawMessage, User} from "./types"; import {Group, RawMessage, User} from "./types";
import {addHistoryMsg, friends, groups, msgHistory} from "../common/data"; import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent"; import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent"; import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
import {v4 as uuidv4} from "uuid" import {v4 as uuidv4} from "uuid"
import {postEvent} from "../onebot11/server/postevent"; import {postOB11Event} from "../onebot11/server/postOB11Event";
import {HOOK_LOG} from "../common/config"; import {HOOK_LOG} from "../common/config";
import fs from "fs";
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
@@ -16,12 +17,14 @@ export enum ReceiveCmd {
NEW_MSG = "nodeIKernelMsgListener/onRecvMsg", NEW_MSG = "nodeIKernelMsgListener/onRecvMsg",
SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg", SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg",
USER_INFO = "nodeIKernelProfileListener/onProfileSimpleChanged", USER_INFO = "nodeIKernelProfileListener/onProfileSimpleChanged",
USER_DETAIL_INFO = "nodeIKernelProfileListener/onProfileDetailInfoChanged",
GROUPS = "nodeIKernelGroupListener/onGroupListUpdate", GROUPS = "nodeIKernelGroupListener/onGroupListUpdate",
GROUPS_UNIX = "onGroupListUpdate", GROUPS_UNIX = "onGroupListUpdate",
FRIENDS = "onBuddyListChange", FRIENDS = "onBuddyListChange",
MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete", MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete",
UNREAD_GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated", UNREAD_GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated",
GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupSingleScreenNotifies" GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupSingleScreenNotifies",
FRIEND_REQUEST = "nodeIKernelBuddyListener/onBuddyReqChange"
} }
interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> { interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
@@ -122,8 +125,7 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
let existGroup = groups.find(g => g.groupCode == group.groupCode); let existGroup = groups.find(g => g.groupCode == group.groupCode);
if (existGroup) { if (existGroup) {
Object.assign(existGroup, group); Object.assign(existGroup, group);
} } else {
else {
groups.push(group); groups.push(group);
existGroup = group; existGroup = group;
} }
@@ -159,13 +161,12 @@ async function processGroupEvent(payload) {
for (const member of oldMembers) { for (const member of oldMembers) {
if (!newMembersSet.has(member.uin)) { if (!newMembersSet.has(member.uin)) {
postEvent(new OB11GroupDecreaseEvent(group.groupCode, parseInt(member.uin))); postOB11Event(new OB11GroupDecreaseEvent(group.groupCode, parseInt(member.uin)));
break; break;
} }
} }
} } else if (existGroup.memberCount < group.memberCount) {
else if (existGroup.memberCount < group.memberCount) {
const oldMembers = existGroup.members; const oldMembers = existGroup.members;
const oldMembersSet = new Set<string>(); const oldMembersSet = new Set<string>();
for (const member of oldMembers) { for (const member of oldMembers) {
@@ -178,7 +179,7 @@ async function processGroupEvent(payload) {
group.members = newMembers; group.members = newMembers;
for (const member of newMembers) { for (const member of newMembers) {
if (!oldMembersSet.has(member.uin)) { if (!oldMembersSet.has(member.uin)) {
postEvent(new OB11GroupIncreaseEvent(group.groupCode, parseInt(member.uin))); postOB11Event(new OB11GroupIncreaseEvent(group.groupCode, parseInt(member.uin)));
break; break;
} }
} }
@@ -187,8 +188,7 @@ async function processGroupEvent(payload) {
} }
updateGroups(newGroupList, false).then(); updateGroups(newGroupList, false).then();
} } catch (e) {
catch (e) {
updateGroups(payload.groupList).then(); updateGroups(payload.groupList).then();
console.log(e); console.log(e);
} }
@@ -197,8 +197,7 @@ async function processGroupEvent(payload) {
registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => { registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => {
if (payload.updateType != 2) { if (payload.updateType != 2) {
updateGroups(payload.groupList).then(); updateGroups(payload.groupList).then();
} } else {
else {
if (process.platform == "win32") { if (process.platform == "win32") {
processGroupEvent(payload).then(); processGroupEvent(payload).then();
} }
@@ -207,13 +206,13 @@ registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUP
registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => { registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => {
if (payload.updateType != 2) { if (payload.updateType != 2) {
updateGroups(payload.groupList).then(); updateGroups(payload.groupList).then();
} } else {
else {
if (process.platform != "win32") { if (process.platform != "win32") {
processGroupEvent(payload).then(); processGroupEvent(payload).then();
} }
} }
}) })
registerReceiveHook<{ registerReceiveHook<{
data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[] data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
}>(ReceiveCmd.FRIENDS, payload => { }>(ReceiveCmd.FRIENDS, payload => {
@@ -231,9 +230,32 @@ registerReceiveHook<{
}) })
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
const {autoDeleteFile} = getConfigUtil().getConfig();
for (const message of payload.msgList) { for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message) // log("收到新消息push到历史记录", message)
addHistoryMsg(message) addHistoryMsg(message)
// 清理文件
if (!autoDeleteFile) {
continue
}
for (const msgElement of message.elements) {
setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath;
const pttPath = msgElement.pttElement?.filePath;
const pathList = [picPath, pttPath];
if (msgElement.picElement){
pathList.push(...Object.values(msgElement.picElement.thumbPath));
}
// log("需要清理的文件", pathList);
for (const path of pathList) {
if (path) {
fs.unlink(picPath, () => {
log("删除文件成功", path)
});
}
}
}, 60 * 1000)
}
} }
const msgIds = Object.keys(msgHistory); const msgIds = Object.keys(msgHistory);
if (msgIds.length > 30000) { if (msgIds.length > 30000) {

View File

@@ -3,17 +3,21 @@ import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} fr
import {log} from "../common/utils"; import {log} from "../common/utils";
import { import {
ChatType, ChatType,
ElementType,
Friend, Friend,
FriendRequest,
Group, Group,
GroupMember, GroupMember,
GroupNotifies, GroupNotify, GroupRequestOperateTypes, GroupNotifies,
GroupNotify,
GroupRequestOperateTypes,
RawMessage, RawMessage,
SelfInfo, SelfInfo,
SendMessageElement, SendMessageElement,
User User
} from "./types"; } from "./types";
import * as fs from "fs"; import * as fs from "fs";
import {addHistoryMsg, groupNotifies, msgHistory, selfInfo} from "../common/data"; import {addHistoryMsg, friendRequests, groupNotifies, msgHistory, selfInfo} from "../common/data";
import {v4 as uuidv4} from "uuid" import {v4 as uuidv4} from "uuid"
interface IPCReceiveEvent { interface IPCReceiveEvent {
@@ -42,6 +46,7 @@ export enum NTQQApiMethod {
GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene", GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene",
GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList", GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList",
USER_INFO = "nodeIKernelProfileService/getUserSimpleInfo", USER_INFO = "nodeIKernelProfileService/getUserSimpleInfo",
USER_DETAIL_INFO = "nodeIKernelProfileService/getUserDetailInfo",
FILE_TYPE = "getFileType", FILE_TYPE = "getFileType",
FILE_MD5 = "getFileMd5", FILE_MD5 = "getFileMd5",
FILE_COPY = "copyFile", FILE_COPY = "copyFile",
@@ -55,6 +60,8 @@ export enum NTQQApiMethod {
GET_GROUP_NOTICE = "nodeIKernelGroupService/getSingleScreenNotifies", GET_GROUP_NOTICE = "nodeIKernelGroupService/getSingleScreenNotifies",
HANDLE_GROUP_REQUEST = "nodeIKernelGroupService/operateSysNotify", HANDLE_GROUP_REQUEST = "nodeIKernelGroupService/operateSysNotify",
QUIT_GROUP = "nodeIKernelGroupService/quitGroup", QUIT_GROUP = "nodeIKernelGroupService/quitGroup",
// READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange"
HANDLE_FRIEND_REQUEST = "nodeIKernelBuddyService/approvalFriendRequest",
} }
enum NTQQApiChannel { enum NTQQApiChannel {
@@ -77,6 +84,7 @@ interface NTQQApiParams {
args?: unknown[], args?: unknown[],
cbCmd?: ReceiveCmd | null, cbCmd?: ReceiveCmd | null,
cmdCB?: (payload: any) => boolean; cmdCB?: (payload: any) => boolean;
afterFirstCmd?: boolean, // 是否在methodName调用完之后再去hook cbCmd
timeoutSecond?: number, timeoutSecond?: number,
} }
@@ -84,12 +92,13 @@ function callNTQQApi<ReturnType>(params: NTQQApiParams) {
let { let {
className, methodName, channel, args, className, methodName, channel, args,
cbCmd, timeoutSecond: timeout, cbCmd, timeoutSecond: timeout,
classNameIsRegister, cmdCB classNameIsRegister, cmdCB, afterFirstCmd
} = params; } = params;
className = className ?? NTQQApiClass.NT_API; className = className ?? NTQQApiClass.NT_API;
channel = channel ?? NTQQApiChannel.IPC_UP_2; channel = channel ?? NTQQApiChannel.IPC_UP_2;
args = args ?? []; args = args ?? [];
timeout = timeout ?? 5; timeout = timeout ?? 5;
afterFirstCmd = afterFirstCmd ?? true;
const uuid = uuidv4(); const uuid = uuidv4();
// log("callNTQQApi", channel, className, methodName, args, uuid) // log("callNTQQApi", channel, className, methodName, args, uuid)
return new Promise((resolve: (data: ReturnType) => void, reject) => { return new Promise((resolve: (data: ReturnType) => void, reject) => {
@@ -109,12 +118,10 @@ function callNTQQApi<ReturnType>(params: NTQQApiParams) {
}; };
} else { } else {
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
hookApiCallbacks[uuid] = (result: GeneralCallResult) => { const secondCallback = () => {
log(`${methodName} callback`, result)
if (result?.result == 0 || result === undefined) {
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => { const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
log(methodName, "second callback", cbCmd, payload); // log(methodName, "second callback", cbCmd, payload, cmdCB);
if (cmdCB) { if (!!cmdCB) {
if (cmdCB(payload)) { if (cmdCB(payload)) {
removeReceiveHook(hookId); removeReceiveHook(hookId);
success = true success = true
@@ -126,6 +133,12 @@ function callNTQQApi<ReturnType>(params: NTQQApiParams) {
resolve(payload); resolve(payload);
} }
}) })
}
!afterFirstCmd && secondCallback();
hookApiCallbacks[uuid] = (result: GeneralCallResult) => {
log(`${methodName} callback`, result)
if (result?.result == 0 || result === undefined) {
afterFirstCmd && secondCallback();
} else { } else {
success = true success = true
reject(`ntqq api call failed, ${result.errMsg}`); reject(`ntqq api call failed, ${result.errMsg}`);
@@ -190,6 +203,26 @@ export class NTQQApi {
return result.profiles.get(uid) return result.profiles.get(uid)
} }
static async getUserDetailInfo(uid: string) {
const result = await callNTQQApi<{ info: User }>({
methodName: NTQQApiMethod.USER_DETAIL_INFO,
cbCmd: ReceiveCmd.USER_DETAIL_INFO,
afterFirstCmd: false,
cmdCB: (payload) => {
const success = payload.info.uid == uid
// log("get user detail info", success, uid, payload)
return success
},
args: [
{
uid
},
null
]
})
return result.info
}
static async getFriends(forced = false) { static async getFriends(forced = false) {
const data = await callNTQQApi<{ const data = await callNTQQApi<{
data: { data: {
@@ -298,7 +331,7 @@ export class NTQQApi {
} }
// 上传文件到QQ的文件夹 // 上传文件到QQ的文件夹
static async uploadFile(filePath: string) { static async uploadFile(filePath: string, elementType: ElementType=ElementType.PIC) {
const md5 = await NTQQApi.getFileMd5(filePath); const md5 = await NTQQApi.getFileMd5(filePath);
let ext = (await NTQQApi.getFileType(filePath))?.ext let ext = (await NTQQApi.getFileType(filePath))?.ext
if (ext) { if (ext) {
@@ -313,7 +346,7 @@ export class NTQQApi {
path_info: { path_info: {
md5HexStr: md5, md5HexStr: md5,
fileName: fileName, fileName: fileName,
elementType: 2, elementType: elementType,
elementSubType: 0, elementSubType: 0,
thumbSize: 0, thumbSize: 0,
needCreate: true, needCreate: true,
@@ -443,7 +476,7 @@ export class NTQQApi {
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
let msgInfos = msgIds.map(id => { let msgInfos = msgIds.map(id => {
return {msgId: id, senderShowName: "LLOneBot"} return {msgId: id, senderShowName: selfInfo.nick}
}) })
const apiArgs = [ const apiArgs = [
{ {
@@ -497,13 +530,14 @@ export class NTQQApi {
static async getGroupNotifies() { static async getGroupNotifies() {
// 获取管理员变更 // 获取管理员变更
// 加群通知,退出通知,需要管理员权限 // 加群通知,退出通知,需要管理员权限
await callNTQQApi<GeneralCallResult>({ callNTQQApi<GeneralCallResult>({
methodName: ReceiveCmd.GROUP_NOTIFY, methodName: ReceiveCmd.GROUP_NOTIFY,
classNameIsRegister: true, classNameIsRegister: true,
}) }).then()
return await callNTQQApi<GroupNotifies>({ return await callNTQQApi<GroupNotifies>({
methodName: NTQQApiMethod.GET_GROUP_NOTICE, methodName: NTQQApiMethod.GET_GROUP_NOTICE,
cbCmd: ReceiveCmd.GROUP_NOTIFY, cbCmd: ReceiveCmd.GROUP_NOTIFY,
afterFirstCmd: false,
args: [ args: [
{"doubt": false, "startSeq": "", "number": 14}, {"doubt": false, "startSeq": "", "number": 14},
null null
@@ -545,4 +579,25 @@ export class NTQQApi {
] ]
}) })
} }
static async handleFriendRequest(sourceId: number, accept: boolean,) {
const request: FriendRequest = friendRequests[sourceId]
if (!request){
throw `sourceId ${sourceId}, 对应的好友请求不存在`
}
const result = await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.HANDLE_FRIEND_REQUEST,
args: [
{
"approvalInfo": {
"friendUid": request.friendUid,
"reqTime": request.reqTime,
accept
}
}
]
})
delete friendRequests[sourceId];
return result;
}
} }

View File

@@ -280,3 +280,19 @@ export enum GroupRequestOperateTypes{
approve = 1, approve = 1,
reject = 2 reject = 2
} }
export interface FriendRequest{
friendUid: string,
reqTime: string, // 时间戳,秒
extWords: string, // 申请人填写的验证消息
isUnread: boolean,
friendNick: string,
sourceId: number,
groupCode: string
}
export interface FriendRequestNotify{
data: {
unreadNums: number,
buddyReqs: FriendRequest[]
}
}

View File

@@ -0,0 +1,28 @@
import BaseAction from "./BaseAction";
import {NTQQApi} from "../../ntqqapi/ntcall";
import {friends} from "../../common/data";
import {ActionName} from "./types";
import {log} from "../../common/utils";
interface Payload{
method: string,
args: any[],
}
export default class Debug extends BaseAction<Payload, any>{
actionName = ActionName.Debug
protected async _handle(payload: Payload): Promise<any> {
log("debug call ntqq api", payload);
const method = NTQQApi[payload.method]
if (!method){
throw `${method} 不存在`
}
const result = method(...payload.args);
if (method.constructor.name === "AsyncFunction"){
return await result
}
return result
// const info = await NTQQApi.getUserDetailInfo(friends[0].uid);
// return info
}
}

View File

@@ -9,7 +9,7 @@ import {ActionName, BaseCheckResult} from "./types";
import * as fs from "fs"; import * as fs from "fs";
import {log} from "../../common/utils"; import {log} from "../../common/utils";
import {v4 as uuidv4} from "uuid" import {v4 as uuidv4} from "uuid"
import {parseCQCode} from "../cqcode"; import {decodeCQCode} from "../cqcode";
function checkSendMessage(sendMsgList: OB11MessageData[]) { function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean { function checkUri(uri: string): boolean {
@@ -124,7 +124,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
// text: message // text: message
// } // }
// }] as OB11MessageData[] // }] as OB11MessageData[]
message = parseCQCode(message.toString()) message = decodeCQCode(message.toString())
} else if (!Array.isArray(message)) { } else if (!Array.isArray(message)) {
message = [message] message = [message]
} }

View File

@@ -0,0 +1,18 @@
import BaseAction from "./BaseAction";
import {NTQQApi} from "../../ntqqapi/ntcall";
import {ActionName} from "./types";
interface Payload {
flag: string,
approve: boolean,
remark?: string,
}
export default class SetFriendAddRequest extends BaseAction<Payload, null> {
actionName = ActionName.SetFriendAddRequest;
protected async _handle(payload: Payload): Promise<null> {
await NTQQApi.handleFriendRequest(parseInt(payload.flag), payload.approve)
return null;
}
}

View File

@@ -20,8 +20,11 @@ import SendLike from "./SendLike";
import SetGroupAddRequest from "./SetGroupAddRequest"; import SetGroupAddRequest from "./SetGroupAddRequest";
import SetGroupLeave from "./SetGroupLeave"; import SetGroupLeave from "./SetGroupLeave";
import GetGuildList from "./GetGuildList"; import GetGuildList from "./GetGuildList";
import Debug from "./Debug";
import SetFriendAddRequest from "./SetFriendAddRequest";
export const actionHandlers = [ export const actionHandlers = [
new Debug(),
new SendLike(), new SendLike(),
new GetMsg(), new GetMsg(),
new GetLoginInfo(), new GetLoginInfo(),
@@ -30,6 +33,7 @@ export const actionHandlers = [
new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(), new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(),
new DeleteMsg(), new DeleteMsg(),
new SetGroupAddRequest(), new SetGroupAddRequest(),
new SetFriendAddRequest(),
new SetGroupLeave(), new SetGroupLeave(),
new GetVersionInfo(), new GetVersionInfo(),
new CanSendRecord(), new CanSendRecord(),

View File

@@ -14,7 +14,7 @@ export interface InvalidCheckResult {
} }
export enum ActionName { export enum ActionName {
TestForwardMsg = "test_forward_msg", Debug = "llonebot_debug",
SendLike = "send_like", SendLike = "send_like",
GetLoginInfo = "get_login_info", GetLoginInfo = "get_login_info",
GetFriendList = "get_friend_list", GetFriendList = "get_friend_list",
@@ -28,6 +28,7 @@ export enum ActionName {
SendPrivateMsg = "send_private_msg", SendPrivateMsg = "send_private_msg",
DeleteMsg = "delete_msg", DeleteMsg = "delete_msg",
SetGroupAddRequest = "set_group_add_request", SetGroupAddRequest = "set_group_add_request",
SetFriendAddRequest = "set_friend_add_request",
SetGroupLeave = "set_group_leave", SetGroupLeave = "set_group_leave",
GetVersionInfo = "get_version_info", GetVersionInfo = "get_version_info",
GetStatus = "get_status", GetStatus = "get_status",

View File

@@ -7,27 +7,18 @@ import {
OB11MessageDataType, OB11MessageDataType,
OB11User OB11User
} from "./types"; } from "./types";
import { import {AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User} from '../ntqqapi/types';
AtType,
ChatType,
Friend,
Group,
GroupMember,
IMAGE_HTTP_HOST,
RawMessage,
SelfInfo,
User
} from '../ntqqapi/types';
import {getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data'; import {getFriend, getGroupMember, getHistoryMsgBySeq, 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 {EventType} from "./event/OB11BaseEvent"; import {EventType} from "./event/OB11BaseEvent";
import {encodeCQCode} from "./cqcode";
export class OB11Constructor { export class OB11Constructor {
static async message(msg: RawMessage): Promise<OB11Message> { static async message(msg: RawMessage): Promise<OB11Message> {
const {enableLocalFile2Url} = getConfigUtil().getConfig() const {enableLocalFile2Url, ob11: {messagePostFormat}} = getConfigUtil().getConfig()
const message_type = msg.chatType == ChatType.group ? "group" : "private"; const message_type = msg.chatType == ChatType.group ? "group" : "private";
const resMsg: OB11Message = { const resMsg: OB11Message = {
self_id: parseInt(selfInfo.uin), self_id: parseInt(selfInfo.uin),
@@ -44,7 +35,8 @@ export class OB11Constructor {
raw_message: "", raw_message: "",
font: 14, font: 14,
sub_type: "friend", sub_type: "friend",
message: [], message: messagePostFormat === 'string' ? '' : [],
message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
} }
if (msg.chatType == ChatType.group) { if (msg.chatType == ChatType.group) {
@@ -96,15 +88,12 @@ export class OB11Constructor {
continue; continue;
} }
message_data["data"]["text"] = text message_data["data"]["text"] = text
if (text){
resMsg.raw_message += text
}
} else if (element.picElement) { } else if (element.picElement) {
message_data["type"] = "image" message_data["type"] = "image"
message_data["data"]["file_id"] = element.picElement.fileUuid message_data["data"]["file_id"] = element.picElement.fileUuid
message_data["data"]["path"] = element.picElement.sourcePath message_data["data"]["path"] = element.picElement.sourcePath
message_data["data"]["file"] = element.picElement.sourcePath message_data["data"]["file"] = element.picElement.sourcePath
message_data["data"]["http_file"] = IMAGE_HTTP_HOST + element.picElement.originImageUrl message_data["data"]["url"] = IMAGE_HTTP_HOST + element.picElement.originImageUrl
try { try {
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
element.elementId, element.picElement.thumbPath.get(0), element.picElement.sourcePath) element.elementId, element.picElement.thumbPath.get(0), element.picElement.sourcePath)
@@ -141,8 +130,8 @@ export class OB11Constructor {
if (!enableLocalFile2Url) { if (!enableLocalFile2Url) {
message_data.data.file = "file://" + filePath message_data.data.file = "file://" + filePath
} else { // 不使用本地路径 } else { // 不使用本地路径
if (message_data.data.http_file && !message_data.data.http_file.startsWith(IMAGE_HTTP_HOST + "/download")) { if (message_data.data.url && !message_data.data.url.startsWith(IMAGE_HTTP_HOST + "/download")) {
message_data.data.file = message_data.data.http_file message_data.data.file = message_data.data.url
} else { } else {
let {err, data} = await file2base64(filePath); let {err, data} = await file2base64(filePath);
if (err) { if (err) {
@@ -153,8 +142,13 @@ export class OB11Constructor {
} }
} }
} }
if (message_data.type !== "unknown" && message_data.data) { if (message_data.type !== "unknown" && message_data.data) {
resMsg.message.push(message_data); if (messagePostFormat === 'string') {
const cqCode = encodeCQCode(message_data);
(resMsg.message as string) += cqCode;
resMsg.raw_message += cqCode;
} else (resMsg.message as OB11MessageData[]).push(message_data);
} }
} }
resMsg.raw_message = resMsg.raw_message.trim(); resMsg.raw_message = resMsg.raw_message.trim();

View File

@@ -29,7 +29,7 @@ function h(type: string, data: any) {
} }
} }
export function parseCQCode(source: string): OB11MessageData[] { export function decodeCQCode(source: string): OB11MessageData[] {
const elements: any[] = [] const elements: any[] = []
let result: ReturnType<typeof from> let result: ReturnType<typeof from>
while ((result = from(source))) { while ((result = from(source))) {
@@ -44,6 +44,28 @@ export function parseCQCode(source: string): OB11MessageData[] {
return elements return elements
} }
export function encodeCQCode(data: OB11MessageData) {
const CQCodeEscape = (text: string) => {
return text.replace(/\[/g, '&#91;')
.replace(/\]/g, '&#93;')
.replace(/\&/g, '&amp;')
.replace(/,/g, '&#44;');
};
if (data.type === 'text') {
return CQCodeEscape(data.data.text);
}
let result = '[CQ:' + data.type;
for (const name in data.data) {
const value = data.data[name];
result += `,${name}=${CQCodeEscape(value)}`;
}
result += ']';
return result;
}
// const result = parseCQCode("[CQ:at,qq=114514]早上好啊[CQ:image,file=http://baidu.com/1.jpg,type=show,id=40004]") // const result = parseCQCode("[CQ:at,qq=114514]早上好啊[CQ:image,file=http://baidu.com/1.jpg,type=show,id=40004]")
// const result = parseCQCode("好好好") // const result = parseCQCode("好好好")
// console.log(JSON.stringify(result)) // console.log(JSON.stringify(result))

View File

@@ -0,0 +1,11 @@
import {OB11BaseNoticeEvent} from "../notice/OB11BaseNoticeEvent";
import {EventType} from "../OB11BaseEvent";
export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {
post_type = EventType.REQUEST
user_id: number;
request_type: "friend" = "friend";
comment: string;
flag: string;
}

View File

@@ -29,7 +29,7 @@ export function postWsEvent(event: PostEventType) {
} }
} }
export function postEvent(msg: PostEventType, reportSelf=false) { export function postOB11Event(msg: PostEventType, reportSelf=false) {
const config = getConfigUtil().getConfig(); const config = getConfigUtil().getConfig();
// 判断msg是否是event // 判断msg是否是event
if (!config.reportSelfMessage && !reportSelf) { if (!config.reportSelfMessage && !reportSelf) {

View File

@@ -7,7 +7,7 @@ import {ActionName} from "../../action/types";
import {OB11Response} from "../../action/utils"; import {OB11Response} from "../../action/utils";
import BaseAction from "../../action/BaseAction"; import BaseAction from "../../action/BaseAction";
import {actionMap} from "../../action"; import {actionMap} from "../../action";
import {registerWsEventSender, unregisterWsEventSender} from "../postevent"; import {registerWsEventSender, unregisterWsEventSender} from "../postOB11Event";
import {wsReply} from "./reply"; import {wsReply} from "./reply";
export let rwsList: ReverseWebsocket[] = []; export let rwsList: ReverseWebsocket[] = [];

View File

@@ -2,7 +2,7 @@ import {WebSocket} from "ws";
import {getConfigUtil, log} from "../../../common/utils"; import {getConfigUtil, log} from "../../../common/utils";
import {actionMap} from "../../action"; import {actionMap} from "../../action";
import {OB11Response} from "../../action/utils"; import {OB11Response} from "../../action/utils";
import {postWsEvent, registerWsEventSender, unregisterWsEventSender} from "../postevent"; import {postWsEvent, registerWsEventSender, unregisterWsEventSender} from "../postOB11Event";
import {ActionName} from "../../action/types"; import {ActionName} from "../../action/types";
import BaseAction from "../../action/BaseAction"; import BaseAction from "../../action/BaseAction";
import {LifeCycleSubType, OB11LifeCycleEvent} from "../../event/meta/OB11LifeCycleEvent"; import {LifeCycleSubType, OB11LifeCycleEvent} from "../../event/meta/OB11LifeCycleEvent";

View File

@@ -1,6 +1,6 @@
import * as websocket from "ws"; import * as websocket from "ws";
import {OB11Response} from "../../action/utils"; import {OB11Response} from "../../action/utils";
import {PostEventType} from "../postevent"; import {PostEventType} from "../postOB11Event";
import {isNull, log} from "../../../common/utils"; import {isNull, log} from "../../../common/utils";
export function wsReply(wsClient: websocket.WebSocket, data: OB11Response | PostEventType) { export function wsReply(wsClient: websocket.WebSocket, data: OB11Response | PostEventType) {

View File

@@ -65,7 +65,8 @@ export interface OB11Message {
message_type: "private" | "group", message_type: "private" | "group",
sub_type?: "friend" | "group" | "normal", sub_type?: "friend" | "group" | "normal",
sender: OB11Sender, sender: OB11Sender,
message: OB11MessageData[], message: OB11MessageData[] | string,
message_format: 'array' | 'string',
raw_message: string, raw_message: string,
font: number, font: number,
post_type?: EventType, post_type?: EventType,
@@ -102,7 +103,7 @@ export interface OB11MessageText {
interface OB11MessageFileBase { interface OB11MessageFileBase {
data: { data: {
file: string, file: string,
http_file?: string; url?: string;
} }
} }

View File

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

View File

@@ -2,14 +2,17 @@
// 打开设置界面时触发 // 打开设置界面时触发
async function onSettingWindowCreated(view: Element) { async function onSettingWindowCreated(view: Element) {
window.llonebot.log("setting window created"); window.llonebot.log("setting window created");
const isEmpty = (value: any) => value === undefined || value === null || value === '';
let config = await window.llonebot.getConfig() let config = await window.llonebot.getConfig()
const httpClass = "http"; const httpClass = "http";
const httpPostClass = "http-post"; const httpPostClass = "http-post";
const wsClass = "ws"; const wsClass = "ws";
const reverseWSClass = "reverse-ws"; const reverseWSClass = "reverse-ws";
const llonebotError = await window.llonebot.getError();
window.llonebot.log("获取error" + JSON.stringify(llonebotError));
function createHttpHostEleStr(host: string) { function createHttpHostEleStr(host: string) {
let eleStr = ` let eleStr = `
<setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}"> <setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}">
@@ -47,6 +50,18 @@ async function onSettingWindowCreated(view: Element) {
let html = ` let html = `
<div class="config_view llonebot"> <div class="config_view llonebot">
<setting-section> <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-panel>
<setting-list class="wrap"> <setting-list class="wrap">
<setting-item data-direction="row" class="hostItem vertical-list-item"> <setting-item data-direction="row" class="hostItem vertical-list-item">
@@ -102,38 +117,65 @@ async function onSettingWindowCreated(view: Element) {
<setting-text>Access Token</setting-text> <setting-text>Access Token</setting-text>
<input id="token" type="text" placeholder="可为空" value="${config.token}"/> <input id="token" type="text" placeholder="可为空" value="${config.token}"/>
</setting-item> </setting-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>
<button id="save" class="q-button">保存</button> <button id="save" class="q-button">保存</button>
</setting-list> </setting-list>
</setting-panel> </setting-panel>
<setting-panel> <setting-panel>
<setting-item data-direction="row" class="hostItem vertical-list-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>
</div>
<setting-select id="messagePostFormat">
<setting-option data-value="array" ${config.ob11.messagePostFormat !== "string" ? "is-selected" : ""}>消息段</setting-option>
<setting-option data-value="string" ${config.ob11.messagePostFormat === "string" ? "is-selected" : ""}>CQ码</setting-option>
</setting-select>
</setting-item>
<setting-item data-direction="row" class="vertical-list-item">
<div> <div>
<div>上报文件不采用本地路径</div> <div>上报文件不采用本地路径</div>
<div class="tips">开启后,上报文件(图片语音等)为http链接或base64编码</div> <div class="tips">开启后,上报文件(图片语音等)为http链接或base64编码</div>
</div> </div>
<setting-switch id="switchFileUrl" ${config.enableLocalFile2Url ? "is-active" : ""}></setting-switch> <setting-switch id="switchFileUrl" ${config.enableLocalFile2Url ? "is-active" : ""}></setting-switch>
</setting-item> </setting-item>
<setting-item data-direction="row" class="hostItem vertical-list-item"> <setting-item data-direction="row" class="vertical-list-item">
<div> <div>
<div>debug模式</div> <div>debug模式</div>
<div class="tips">开启后上报消息添加raw字段附带原始消息</div> <div class="tips">开启后上报消息添加raw字段附带原始消息</div>
</div> </div>
<setting-switch id="debug" ${config.debug ? "is-active" : ""}></setting-switch> <setting-switch id="debug" ${config.debug ? "is-active" : ""}></setting-switch>
</setting-item> </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> <div>上报自身消息</div>
<div class="tips">慎用,不然会自己和自己聊个不停</div> <div class="tips">慎用,不然会自己和自己聊个不停</div>
</div> </div>
<setting-switch id="reportSelfMessage" ${config.reportSelfMessage ? "is-active" : ""}></setting-switch> <setting-switch id="reportSelfMessage" ${config.reportSelfMessage ? "is-active" : ""}></setting-switch>
</setting-item> </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> <div>日志</div>
<div class="tips">目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div> <div class="tips">目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
</div> </div>
<setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch> <setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch>
</setting-item> </setting-item>
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div>自动删除收到的文件</div>
<div class="tips">一分钟后会删除收到的图片语音</div>
</div>
<setting-switch id="autoDeleteFile" ${config.autoDeleteFile ? "is-active" : ""}></setting-switch>
</setting-item>
</setting-panel> </setting-panel>
</setting-section> </setting-section>
</div> </div>
@@ -155,6 +197,36 @@ async function onSettingWindowCreated(view: Element) {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html"); 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 = "") { function addHostEle(type: string, initValue: string = "") {
let addressEle, hostItemsEle; let addressEle, hostItemsEle;
@@ -174,6 +246,10 @@ async function onSettingWindowCreated(view: Element) {
doc.getElementById("addHttpHost").addEventListener("click", () => addHostEle("http")) doc.getElementById("addHttpHost").addEventListener("click", () => addHostEle("http"))
doc.getElementById("addWsHost").addEventListener("click", () => addHostEle("ws")) doc.getElementById("addWsHost").addEventListener("click", () => addHostEle("ws"))
doc.getElementById("messagePostFormat").addEventListener("selected", (e: CustomEvent) => {
config.ob11.messagePostFormat = e.detail && !isEmpty(e.detail.value) ? e.detail.value : 'array';
window.llonebot.setConfig(config);
})
function switchClick(eleId: string, configKey: string, _config = null) { function switchClick(eleId: string, configKey: string, _config = null) {
if (!_config) { if (!_config) {
@@ -211,6 +287,7 @@ async function onSettingWindowCreated(view: Element) {
switchClick("switchFileUrl", "enableLocalFile2Url"); switchClick("switchFileUrl", "enableLocalFile2Url");
switchClick("reportSelfMessage", "reportSelfMessage"); switchClick("reportSelfMessage", "reportSelfMessage");
switchClick("log", "log"); switchClick("log", "log");
switchClick("autoDeleteFile", "autoDeleteFile");
doc.getElementById("save")?.addEventListener("click", doc.getElementById("save")?.addEventListener("click",
() => { () => {
@@ -219,6 +296,7 @@ async function onSettingWindowCreated(view: Element) {
const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement; const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement;
const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("wsHost") as HTMLCollectionOf<HTMLInputElement>; const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("wsHost") as HTMLCollectionOf<HTMLInputElement>;
const tokenEle = document.getElementById("token") as HTMLInputElement; const tokenEle = document.getElementById("token") as HTMLInputElement;
const ffmpegPathEle = document.getElementById("ffmpegPath") as HTMLInputElement;
// 获取端口和host // 获取端口和host
const httpPort = httpPortEle.value const httpPort = httpPortEle.value
@@ -243,15 +321,26 @@ async function onSettingWindowCreated(view: Element) {
config.ob11.wsPort = parseInt(wsPort); config.ob11.wsPort = parseInt(wsPort);
config.ob11.wsHosts = wsHosts; config.ob11.wsHosts = wsHosts;
config.token = token; config.token = token;
config.ffmpeg = ffmpegPathEle.value.trim();
window.llonebot.setConfig(config); window.llonebot.setConfig(config);
setTimeout(()=>{
getError().then();
}, 1000);
alert("保存成功"); alert("保存成功");
}) })
doc.getElementById("selectFFMPEG")?.addEventListener("click", ()=>{
window.llonebot.selectFile().then(selectPath=>{
if (selectPath){
config.ffmpeg = (document.getElementById("ffmpegPath") as HTMLInputElement).value = selectPath;
// window.llonebot.setConfig(config);
}
});
})
doc.body.childNodes.forEach(node => { doc.body.childNodes.forEach(node => {
view.appendChild(node); view.appendChild(node);
}); });
} }

View File

@@ -1,4 +1,5 @@
// import path from "path"; // import path from "path";
const webpack = require('webpack');
const path = require('path'); const path = require('path');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
@@ -70,6 +71,9 @@ let config = {
} }
}) })
}), }),
new webpack.DefinePlugin({
'process.env.FLUENTFFMPEG_COV': false,
}),
], // devtool: 'source-map', ], // devtool: 'source-map',
} }