fix: support get_status online

feat: seconds of auto delete file
refactor: file report
This commit is contained in:
linyuchen 2024-03-01 21:43:05 +08:00
parent fdaf0e5269
commit 3d0b90db35
15 changed files with 245 additions and 69 deletions

View File

@ -41,6 +41,7 @@ export class ConfigUtil {
log: false,
reportSelfMessage: false,
autoDeleteFile: false,
autoDeleteFileSecond: 60,
};
if (!fs.existsSync(this.configPath)) {

View File

@ -1,7 +1,22 @@
import {NTQQApi} from '../ntqqapi/ntcall';
import {Friend, FriendRequest, Group, GroupMember, GroupNotify, RawMessage, SelfInfo} from "../ntqqapi/types";
import {LLOneBotError} from "./types";
import {
FileElement,
Friend,
FriendRequest,
Group,
GroupMember,
GroupNotify,
PicElement, PttElement,
RawMessage,
SelfInfo, VideoElement
} from "../ntqqapi/types";
import {FileCache, LLOneBotError} from "./types";
export let selfInfo: SelfInfo = {
uid: "",
uin: "",
nick: "",
online: true,
}
export let groups: Group[] = []
export let friends: Friend[] = []
export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage
@ -13,6 +28,8 @@ export let llonebotError: LLOneBotError = {
}
let globalMsgId = Math.floor(Date.now() / 1000);
export let fileCache: Map<string, FileCache> = new Map();
export function addHistoryMsg(msg: RawMessage): boolean {
let existMsg = msgHistory[msg.msgId]
if (existMsg) {
@ -74,11 +91,7 @@ export async function getGroupMember(groupQQ: string | number, memberQQ: string
}
}
export let selfInfo: SelfInfo = {
uid: "",
uin: "",
nick: "",
}
export function getHistoryMsgBySeq(seq: string) {

View File

@ -1,3 +1,5 @@
import {FileElement, PicElement, PttElement, VideoElement} from "../ntqqapi/types";
export interface OB11Config {
httpPort: number
httpHosts: string[]
@ -19,10 +21,20 @@ export interface Config {
reportSelfMessage?: boolean
log?: boolean
autoDeleteFile?: boolean
autoDeleteFileSecond?: number
ffmpeg?: string // ffmpeg路径
}
export type LLOneBotError = {
ffmpegError?: string
otherError?: string
}
export interface FileCache{
fileName: string,
filePath: string,
fileSize: string,
url?: string,
downloadFunc?: () => Promise<void>;
}

View File

@ -2,7 +2,7 @@ import {BrowserWindow} from 'electron';
import {getConfigUtil, 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 {addHistoryMsg, friends, groups, msgHistory, selfInfo} from "../common/data";
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
import {v4 as uuidv4} from "uuid"
@ -24,7 +24,8 @@ export enum ReceiveCmd {
MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete",
UNREAD_GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated",
GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupSingleScreenNotifies",
FRIEND_REQUEST = "nodeIKernelBuddyListener/onBuddyReqChange"
FRIEND_REQUEST = "nodeIKernelBuddyListener/onBuddyReqChange",
SELF_STATUS = "nodeIKernelProfileListener/onSelfStatusChanged",
}
interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
@ -230,7 +231,7 @@ registerReceiveHook<{
})
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
const {autoDeleteFile} = getConfigUtil().getConfig();
const {autoDeleteFile, autoDeleteFileSecond} = getConfigUtil().getConfig();
for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message)
addHistoryMsg(message)
@ -254,7 +255,7 @@ registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload
});
}
}
}, 60 * 1000)
}, autoDeleteFileSecond * 1000)
}
}
const msgIds = Object.keys(msgHistory);
@ -277,3 +278,6 @@ registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, ({msgRe
}
})
registerReceiveHook<{info: {status: number}}>(ReceiveCmd.SELF_STATUS, (info)=>{
selfInfo.online = info.info.status !== 20;
})

View File

@ -8,7 +8,7 @@ export interface User {
}
export interface SelfInfo extends User {
online?: boolean;
}
export interface Friend extends User {
@ -249,7 +249,7 @@ export interface VideoElement {
"thumbHeight": number,
"busiType": 0, // 未知
"subBusiType": 0, // 未知
"thumbPath": {},
"thumbPath": Map<number,any>,
"transferStatus": 0, // 未知
"progress": 0, // 下载进度?
"invalidState": 0, // 未知

View File

@ -0,0 +1,41 @@
import BaseAction from "./BaseAction";
import {fileCache} from "../../common/data";
import {getConfigUtil} from "../../common/utils";
import fs from "fs/promises";
export interface GetFilePayload{
file: string // 文件名
}
export interface GetFileResponse{
file?: string // path
url?: string
file_size?: string
file_name?: string
base64?: string
}
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse>{
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const cache = fileCache.get(payload.file)
if (!cache) {
throw new Error('file not found')
}
if (cache.downloadFunc) {
await cache.downloadFunc()
}
let res : GetFileResponse= {
file: cache.filePath,
url: cache.url,
file_size: cache.fileSize,
file_name: cache.fileName
}
if (getConfigUtil().getConfig().enableLocalFile2Url) {
if (!cache.url) {
res.base64 = await fs.readFile(cache.filePath, 'base64')
}
}
return res
}
}

View File

@ -0,0 +1,7 @@
import {GetFileBase} from "./GetFile";
import {ActionName} from "./types";
export default class GetImage extends GetFileBase{
actionName = ActionName.GetImage
}

View File

@ -0,0 +1,15 @@
import {GetFileBase, GetFilePayload, GetFileResponse} from "./GetFile";
import {ActionName} from "./types";
interface Payload extends GetFilePayload{
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
}
export default class GetRecord extends GetFileBase{
actionName = ActionName.GetRecord
protected async _handle(payload: Payload): Promise<GetFileResponse> {
let res = super._handle(payload);
return res;
}
}

View File

@ -1,13 +1,14 @@
import BaseAction from "./BaseAction";
import {OB11Status} from "../types";
import {ActionName} from "./types";
import {selfInfo} from "../../common/data";
export default class GetStatus extends BaseAction<any, OB11Status> {
actionName = ActionName.GetStatus
protected async _handle(payload: any): Promise<OB11Status> {
return {
online: null,
online: selfInfo.online,
good: true
}
}

View File

@ -1,5 +1,13 @@
import {AtType, ChatType, Group, RawMessage, SendMessageElement} from "../../ntqqapi/types";
import {addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getUidByUin, selfInfo,} from "../../common/data";
import {
addHistoryMsg,
friends,
getGroup,
getGroupMember,
getHistoryMsgByShortId,
getUidByUin,
selfInfo,
} from "../../common/data";
import {OB11MessageData, OB11MessageDataType, OB11MessageMixType, OB11MessageNode, OB11PostSendMsg} from '../types';
import {NTQQApi, Peer} from "../../ntqqapi/ntcall";
import {SendMsgElementConstructor} from "../../ntqqapi/constructor";
@ -243,7 +251,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (atQQ === "all") {
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
} else {
const atMember = group?.members.find(m => m.uin == atQQ)
// const atMember = group?.members.find(m => m.uin == atQQ)
const atMember = await getGroupMember(group?.groupCode, atQQ);
if (atMember) {
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
}

View File

@ -28,6 +28,8 @@ import SetGroupBan from "./SetGroupBan";
import SetGroupKick from "./SetGroupKick";
import SetGroupAdmin from "./SetGroupAdmin";
import SetGroupCard from "./SetGroupCard";
import GetImage from "./GetImage";
import GetRecord from "./GetRecord";
export const actionHandlers = [
new Debug(),
@ -51,6 +53,8 @@ export const actionHandlers = [
new SetGroupAdmin(),
new SetGroupName(),
new SetGroupCard(),
new GetImage(),
new GetRecord(),
//以下为go-cqhttp api
new GoCQHTTPSendGroupForwardMsg(),

View File

@ -40,6 +40,8 @@ export enum ActionName {
SetGroupAdmin = "set_group_admin",
SetGroupCard = "set_group_card",
SetGroupName = "set_group_name",
GetImage = "get_image",
GetRecord = "get_record",
// 以下为go-cqhttp api
GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg",
GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg",

View File

@ -8,7 +8,7 @@ import {
OB11User
} from "./types";
import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
import { getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo } from '../common/data';
import {fileCache, getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data';
import { file2base64, getConfigUtil, log } from "../common/utils";
import { NTQQApi } from "../ntqqapi/ntcall";
import { EventType } from "./event/OB11BaseEvent";
@ -98,31 +98,65 @@ export class OB11Constructor {
}
} else if (element.picElement) {
message_data["type"] = "image"
message_data["data"]["file_id"] = element.picElement.fileUuid
message_data["data"]["path"] = element.picElement.sourcePath
message_data["data"]["file"] = element.picElement.sourcePath
// message_data["data"]["file"] = element.picElement.sourcePath
message_data["data"]["file"] = element.picElement.fileName
// message_data["data"]["path"] = element.picElement.sourcePath
message_data["data"]["url"] = IMAGE_HTTP_HOST + element.picElement.originImageUrl
try {
// message_data["data"]["file_id"] = element.picElement.fileUuid
message_data["data"]["file_size"] = element.picElement.fileSize
fileCache.set(element.picElement.fileName, {
fileName: element.picElement.fileName,
filePath: element.picElement.sourcePath,
fileSize: element.picElement.fileSize.toString(),
url: IMAGE_HTTP_HOST + element.picElement.originImageUrl,
downloadFunc: async () => {
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
element.elementId, element.picElement.thumbPath.get(0), element.picElement.sourcePath)
} catch (e) {
}
}})
// 不在自动下载图片
} else if (element.videoElement) {
message_data["type"] = OB11MessageDataType.video;
message_data["data"]["file"] = element.videoElement.filePath
message_data["data"]["file_id"] = element.videoElement.fileUuid
message_data["data"]["file"] = element.videoElement.fileName
message_data["data"]["path"] = element.videoElement.filePath
// message_data["data"]["file_id"] = element.videoElement.fileUuid
message_data["data"]["file_size"] = element.videoElement.fileSize
fileCache.set(element.videoElement.fileName, {
fileName: element.videoElement.fileName,
filePath: element.videoElement.filePath,
fileSize: element.videoElement.fileSize,
downloadFunc: async () => {
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
element.elementId, element.videoElement.thumbPath.get(0), element.videoElement.filePath)
}})
// 怎么拿到url呢
} else if (element.fileElement) {
message_data["type"] = OB11MessageDataType.file;
message_data["data"]["file"] = element.fileElement.filePath
message_data["data"]["file_id"] = element.fileElement.fileUuid
message_data["data"]["file"] = element.fileElement.fileName
// message_data["data"]["path"] = element.fileElement.filePath
// message_data["data"]["file_id"] = element.fileElement.fileUuid
message_data["data"]["file_size"] = element.fileElement.fileSize
fileCache.set(element.fileElement.fileName, {
fileName: element.fileElement.fileName,
filePath: element.fileElement.filePath,
fileSize: element.fileElement.fileSize,
downloadFunc: async () => {
await NTQQApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
element.elementId, null, element.fileElement.filePath)
}})
// 怎么拿到url呢
}
else if (element.pttElement) {
message_data["type"] = OB11MessageDataType.voice;
message_data["data"]["file"] = element.pttElement.filePath
message_data["data"]["file_id"] = element.pttElement.fileUuid
message_data["data"]["file"] = element.pttElement.fileName
message_data["data"]["path"] = element.pttElement.filePath
// message_data["data"]["file_id"] = element.pttElement.fileUuid
message_data["data"]["file_size"] = element.pttElement.fileSize
fileCache.set(element.pttElement.fileName, {
fileName: element.pttElement.fileName,
filePath: element.pttElement.filePath,
fileSize: element.pttElement.fileSize,
})
// log("收到语音消息", msg)
// window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => {
@ -138,35 +172,36 @@ export class OB11Constructor {
message_data["data"]["id"] = element.faceElement.faceIndex.toString();
}
if (message_data.data.file) {
let filePath: string = message_data.data.file;
if (!enableLocalFile2Url) {
message_data.data.file = "file://" + filePath
} else { // 不使用本地路径
const ignoreTypes = [OB11MessageDataType.file, OB11MessageDataType.video]
if (!ignoreTypes.includes(message_data.type)) {
if (message_data.data.url && !message_data.data.url.startsWith(IMAGE_HTTP_HOST + "/download")) {
message_data.data.file = message_data.data.url
} else {
let { err, data } = await file2base64(filePath);
if (err) {
log("文件转base64失败", filePath, err)
} else {
message_data.data.file = "base64://" + data
}
}
} else {
message_data.data.file = "file://" + filePath
}
}
}
// if (message_data.data.file) {
// let filePath: string = message_data.data.file;
// if (!enableLocalFile2Url) {
// message_data.data.file = "file://" + filePath
// } else { // 不使用本地路径
// const ignoreTypes = [OB11MessageDataType.file, OB11MessageDataType.video]
// if (!ignoreTypes.includes(message_data.type)) {
// if (message_data.data.url && !message_data.data.url.startsWith(IMAGE_HTTP_HOST + "/download")) {
// message_data.data.file = message_data.data.url
// } else {
// let { err, data } = await file2base64(filePath);
// if (err) {
// log("文件转base64失败", filePath, err)
// } else {
// message_data.data.file = "base64://" + data
// }
// }
// } else {
// message_data.data.file = "file://" + filePath
// }
// }
// }
if (message_data.type !== "unknown" && message_data.data) {
const cqCode = encodeCQCode(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 += cqCode;
}
}
resMsg.raw_message = resMsg.raw_message.trim();

View File

@ -1,11 +1,12 @@
import {CONFIG_DIR, isGIF} from "../common/utils";
import {v4 as uuidv4} from "uuid";
import * as path from 'path';
import {fileCache} from "../common/data";
const fs = require("fs").promises;
export async function uri2local(uri: string, fileName: string=null){
if (!fileName){
export async function uri2local(uri: string, fileName: string = null) {
if (!fileName) {
fileName = uuidv4();
}
let filePath = path.join(CONFIG_DIR, fileName)
@ -43,21 +44,33 @@ export async function uri2local(uri: string, fileName: string=null){
res.errMsg = `${url}下载失败,` + e.toString()
return res
}
} else if (url.protocol === "file:"){
// await fs.copyFile(url.pathname, filePath);
let pathname = decodeURIComponent(url.pathname)
if (process.platform === "win32"){
filePath = pathname.slice(1)
} else {
let pathname: string;
if (url.protocol === "file:") {
// await fs.copyFile(url.pathname, filePath);
pathname = decodeURIComponent(url.pathname)
if (process.platform === "win32") {
filePath = pathname.slice(1)
} else {
filePath = pathname
}
}
else{
filePath = pathname
const cache = fileCache.get(uri)
if (cache) {
filePath = cache.filePath
}
else{
filePath = uri;
}
}
res.isLocal = true
}
else{
res.errMsg = `不支持的file协议,` + url.protocol
return res
}
// else{
// res.errMsg = `不支持的file协议,` + url.protocol
// return res
// }
if (isGIF(filePath) && !res.isLocal) {
await fs.rename(filePath, filePath + ".gif");
filePath += ".gif";

View File

@ -143,8 +143,8 @@ async function onSettingWindowCreated(view: Element) {
</setting-item>
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div></div>
<div class="tips">()http链接或base64编码</div>
<div>使base64编码</div>
<div class="tips">/get_image/get_record时url时添加一个base64字段</div>
</div>
<setting-switch id="switchFileUrl" ${config.enableLocalFile2Url ? "is-active" : ""}></setting-switch>
</setting-item>
@ -172,7 +172,12 @@ async function onSettingWindowCreated(view: Element) {
<setting-item data-direction="row" class="vertical-list-item">
<div>
<div></div>
<div class="tips"></div>
<div class="tips">
<input id="autoDeleteMin"
min="1" style="width: 50px"
value="${config.autoDeleteFileSecond || 60}" type="number"/>
</div>
</div>
<setting-switch id="autoDeleteFile" ${config.autoDeleteFile ? "is-active" : ""}></setting-switch>
</setting-item>
@ -338,6 +343,20 @@ async function onSettingWindowCreated(view: Element) {
});
})
// 自动保存删除文件延时时间
const autoDeleteMinEle = doc.getElementById("autoDeleteMin") as HTMLInputElement;
let st = null;
autoDeleteMinEle.addEventListener("change", ()=>{
if (st){
clearTimeout(st)
}
st = setTimeout(()=>{
console.log("auto delete file minute change");
config.autoDeleteFileSecond = parseInt(autoDeleteMinEle.value) || 1;
window.llonebot.setConfig(config);
}, 1000)
})
doc.body.childNodes.forEach(node => {
view.appendChild(node);
});