diff --git a/src/common/config.ts b/src/common/config.ts index 4927292..5ee8fae 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -41,6 +41,7 @@ export class ConfigUtil { log: false, reportSelfMessage: false, autoDeleteFile: false, + autoDeleteFileSecond: 60, }; if (!fs.existsSync(this.configPath)) { diff --git a/src/common/data.ts b/src/common/data.ts index 4d29b65..bf3e2b9 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -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 = {} // msgId: RawMessage @@ -13,6 +28,8 @@ export let llonebotError: LLOneBotError = { } let globalMsgId = Math.floor(Date.now() / 1000); +export let fileCache: Map = 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) { diff --git a/src/common/types.ts b/src/common/types.ts index 5d86a9e..91f83ab 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -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; } \ No newline at end of file diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 48f8047..d0e8b6f 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -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 extends Array { @@ -230,7 +231,7 @@ registerReceiveHook<{ }) registerReceiveHook<{ msgList: Array }>(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 }>(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; +}) \ No newline at end of file diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts index 39a675a..dd6f296 100644 --- a/src/ntqqapi/types.ts +++ b/src/ntqqapi/types.ts @@ -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, "transferStatus": 0, // 未知 "progress": 0, // 下载进度? "invalidState": 0, // 未知 diff --git a/src/onebot11/action/GetFile.ts b/src/onebot11/action/GetFile.ts new file mode 100644 index 0000000..703e383 --- /dev/null +++ b/src/onebot11/action/GetFile.ts @@ -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{ + protected async _handle(payload: GetFilePayload): Promise { + 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 + } +} \ No newline at end of file diff --git a/src/onebot11/action/GetImage.ts b/src/onebot11/action/GetImage.ts new file mode 100644 index 0000000..71c873b --- /dev/null +++ b/src/onebot11/action/GetImage.ts @@ -0,0 +1,7 @@ +import {GetFileBase} from "./GetFile"; +import {ActionName} from "./types"; + + +export default class GetImage extends GetFileBase{ + actionName = ActionName.GetImage +} \ No newline at end of file diff --git a/src/onebot11/action/GetRecord.ts b/src/onebot11/action/GetRecord.ts new file mode 100644 index 0000000..c1dfa3d --- /dev/null +++ b/src/onebot11/action/GetRecord.ts @@ -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 { + let res = super._handle(payload); + return res; + } +} \ No newline at end of file diff --git a/src/onebot11/action/GetStatus.ts b/src/onebot11/action/GetStatus.ts index c3f038c..9ea548d 100644 --- a/src/onebot11/action/GetStatus.ts +++ b/src/onebot11/action/GetStatus.ts @@ -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 { actionName = ActionName.GetStatus protected async _handle(payload: any): Promise { return { - online: null, + online: selfInfo.online, good: true } } diff --git a/src/onebot11/action/SendMsg.ts b/src/onebot11/action/SendMsg.ts index 6f56e40..03c9c1e 100644 --- a/src/onebot11/action/SendMsg.ts +++ b/src/onebot11/action/SendMsg.ts @@ -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 { 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)) } diff --git a/src/onebot11/action/index.ts b/src/onebot11/action/index.ts index 8928a53..1de37d3 100644 --- a/src/onebot11/action/index.ts +++ b/src/onebot11/action/index.ts @@ -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(), diff --git a/src/onebot11/action/types.ts b/src/onebot11/action/types.ts index d0c6819..a18515d 100644 --- a/src/onebot11/action/types.ts +++ b/src/onebot11/action/types.ts @@ -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", diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index 1ffcf35..1197643 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -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(); diff --git a/src/onebot11/utils.ts b/src/onebot11/utils.ts index b7966f8..0cf7bff 100644 --- a/src/onebot11/utils.ts +++ b/src/onebot11/utils.ts @@ -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"; diff --git a/src/renderer.ts b/src/renderer.ts index 7910a6e..b26acfd 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -143,8 +143,8 @@ async function onSettingWindowCreated(view: Element) {
-
上报文件不采用本地路径
-
开启后,上报文件(图片语音等)为http链接或base64编码
+
获取文件使用base64编码
+
开启后,调用/get_image、/get_record时,获取不到url时添加一个base64字段
@@ -172,7 +172,12 @@ async function onSettingWindowCreated(view: Element) {
自动删除收到的文件
-
一分钟后会删除收到的图片语音
+
+ 收到文件 + 秒后自动删除 +
@@ -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); });