diff --git a/manifest.json b/manifest.json index d03b050..e9c7d7a 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 协议,用于 QQ 机器人开发", - "version": "3.31.10", + "version": "3.32.0", "icon": "./icon.webp", "authors": [ { diff --git a/package.json b/package.json index 81a0bbb..cb75938 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "cosmokit": "^1.6.2", "express": "^4.19.2", "fast-xml-parser": "^4.5.0", - "file-type": "^19.4.1", + "file-type": "^19.5.0", "fluent-ffmpeg": "^2.1.3", "minato": "^3.5.1", "silk-wasm": "^3.6.1", diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts index 3fa61c6..fa7fd7d 100644 --- a/src/common/utils/file.ts +++ b/src/common/utils/file.ts @@ -4,6 +4,7 @@ import path from 'node:path' import { TEMP_DIR } from '../globalVars' import { randomUUID, createHash } from 'node:crypto' import { fileURLToPath } from 'node:url' +import { fileTypeFromFile } from 'file-type' export function isGIF(path: string) { const buffer = Buffer.alloc(4) @@ -116,7 +117,7 @@ type Uri2LocalRes = { isLocal: boolean } -export async function uri2local(uri: string, filename?: string): Promise { +export async function uri2local(uri: string, filename?: string, needExt?: boolean): Promise { const { type } = checkUriType(uri) if (type === FileUriType.FileURL) { @@ -139,8 +140,14 @@ export async function uri2local(uri: string, filename?: string): Promise { - const ntUserApi = ctx.get('ntUserApi') - if (ntUserApi && !selfInfo.nick) { - ntUserApi.getSelfNick(true) - } - }, 1000)*/ const target: Logger.Target = { colors: 0, record: (record: Logger.Record) => { + if (!enable) { + return + } const dateTime = new Date(record.timestamp).toLocaleString() const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : '' const content = `${dateTime} [${record.type}] ${userInfo} | ${record.name} ${record.content}\n\n` @@ -34,5 +29,8 @@ export default class Log { }, } Logger.targets.push(target) + ctx.on('llonebot/config-updated', input => { + enable = input.log! + }) } } \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 5555a49..de4aa71 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -202,7 +202,7 @@ function onLoad() { // 创建窗口时触发 function onBrowserWindowCreated(window: BrowserWindow) { - if (![2, 4].includes(window.id)) { + if (![2, 4, 6].includes(window.id)) { return } if (window.id === 2) { diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 0e7a8de..546c2ba 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -1,5 +1,5 @@ import { ReceiveCmdS } from '../hook' -import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GetFileListParam } from '../types' +import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GetFileListParam, PublishGroupBulletinReq } from '../types' import { invoke, NTClass, NTMethod } from '../ntcall' import { GeneralCallResult } from '../services' import { NTQQWindows } from './window' @@ -288,4 +288,16 @@ export class NTQQGroupApi extends Service { ) return data.fileInfo.item } + + async publishGroupBulletin(groupCode: string, req: PublishGroupBulletinReq) { + const ntUserApi = this.ctx.get('ntUserApi')! + const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')! + return await invoke('nodeIKernelGroupService/publishGroupBulletin', [{ groupCode, psKey, req }, null]) + } + + async uploadGroupBulletinPic(groupCode: string, path: string) { + const ntUserApi = this.ctx.get('ntUserApi')! + const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')! + return await invoke('nodeIKernelGroupService/uploadGroupBulletinPic', [{ groupCode, psKey, path }, null]) + } } diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index bd60398..3698ac8 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -252,4 +252,8 @@ export class NTQQMsgApi extends Service { } }, null]) } + + async setMsgRead(peer: Peer) { + return await invoke('nodeIKernelMsgService/setMsgRead', [{ peer }, null]) + } } diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index cb4791d..3fa39cf 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -109,6 +109,10 @@ export class NTQQUserApi extends Service { return cookies } + async getPSkey(domains: string[]) { + return await invoke('nodeIKernelTipOffService/getPskey', [{ domains, isForNewPCQQ: true }, null]) + } + genBkn(sKey: string) { sKey = sKey || '' let hash = 5381 diff --git a/src/ntqqapi/core.ts b/src/ntqqapi/core.ts index 4f38fce..156c7be 100644 --- a/src/ntqqapi/core.ts +++ b/src/ntqqapi/core.ts @@ -30,7 +30,7 @@ declare module 'cordis' { 'nt/message-sent': (input: RawMessage[]) => void 'nt/group-notify': (input: GroupNotify[]) => void 'nt/friend-request': (input: FriendRequest[]) => void - 'nt/group-member-info-updated': (input: { groupCode: string; members: GroupMember[] }) => void + 'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void } } diff --git a/src/ntqqapi/entities.ts b/src/ntqqapi/entities.ts index 451a6f3..aec9761 100644 --- a/src/ntqqapi/entities.ts +++ b/src/ntqqapi/entities.ts @@ -99,19 +99,20 @@ export namespace SendElementEntities { } } - export async function file(ctx: Context, filePath: string, fileName = '', folderId = ''): Promise { - const { fileName: _fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(filePath, ElementType.FILE) - if (fileSize === 0) { - throw '文件异常,大小为 0' + export async function file(ctx: Context, filePath: string, fileName: string, folderId = ''): Promise { + const fileSize = (await stat(filePath)).size.toString() + if (fileSize === '0') { + ctx.logger.warn(`文件${fileName}异常,大小为 0`) + throw new Error('文件异常,大小为 0') } const element: SendFileElement = { elementType: ElementType.FILE, elementId: '', fileElement: { - fileName: fileName || _fileName, - folderId: folderId, - filePath: path!, - fileSize: fileSize.toString(), + fileName, + folderId, + filePath, + fileSize, }, } return element diff --git a/src/ntqqapi/services/NodeIKernelGroupService.ts b/src/ntqqapi/services/NodeIKernelGroupService.ts index 72bb096..0f8ef1e 100644 --- a/src/ntqqapi/services/NodeIKernelGroupService.ts +++ b/src/ntqqapi/services/NodeIKernelGroupService.ts @@ -45,7 +45,7 @@ export interface NodeIKernelGroupService { errMsg: string, uids: Map }> - + //26702(其实更早 但是我不知道) checkGroupMemberCache(arrayList: Array): Promise @@ -202,11 +202,12 @@ export interface NodeIKernelGroupService { publishInstructionForNewcomers(groupCode: string, arg: unknown): void - uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise diff --git a/src/ntqqapi/types/group.ts b/src/ntqqapi/types/group.ts index b7d860b..9f93304 100644 --- a/src/ntqqapi/types/group.ts +++ b/src/ntqqapi/types/group.ts @@ -65,3 +65,15 @@ export interface GroupMember { joinTime: string lastSpeakTime: string } + +export interface PublishGroupBulletinReq { + text: string + picInfo?: { + id: string + width: number + height: number + } + oldFeedsId: '' + pinned: number + confirmRequired: number +} \ No newline at end of file diff --git a/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts b/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts index fef4d2d..6fd9490 100644 --- a/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts +++ b/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts @@ -1,14 +1,23 @@ import BaseAction from '../BaseAction' import { ActionName } from '../types' +import { MessageUnique } from '@/common/utils/messageUnique' interface Payload { - message_id: number + message_id: number | string } export class MarkMsgAsRead extends BaseAction { actionName = ActionName.GoCQHTTP_MarkMsgAsRead - protected async _handle() { + protected async _handle(payload: Payload) { + if (!payload.message_id) { + throw new Error('参数 message_id 不能为空') + } + const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) + if (!msg) { + throw new Error('msg not found') + } + await this.ctx.ntMsgApi.setMsgRead(msg.Peer) return null } } diff --git a/src/onebot11/action/go-cqhttp/SendGroupNotice.ts b/src/onebot11/action/go-cqhttp/SendGroupNotice.ts index 1e54a82..9fe8995 100644 --- a/src/onebot11/action/go-cqhttp/SendGroupNotice.ts +++ b/src/onebot11/action/go-cqhttp/SendGroupNotice.ts @@ -1,5 +1,7 @@ import BaseAction from '../BaseAction' import { ActionName } from '../types' +import { unlink } from 'fs/promises' +import { checkFileReceived, uri2local } from '@/common/utils/file' interface Payload { group_id: number | string @@ -13,24 +15,39 @@ export class SendGroupNotice extends BaseAction { actionName = ActionName.GoCQHTTP_SendGroupNotice async _handle(payload: Payload) { - const type = 1 - const isShowEditCard = 0 - const tipWindowType = 0 + if(!payload.content){ + throw new Error('参数 content 不能为空') + } + const groupCode = payload.group_id.toString() const pinned = Number(payload.pinned ?? 0) const confirmRequired = Number(payload.confirm_required ?? 1) - const result = await this.ctx.ntWebApi.setGroupNotice({ - groupCode: payload.group_id.toString(), - content: payload.content, + let picInfo: { id: string, width: number, height: number } | undefined + if (payload.image) { + const { path, isLocal, success, errMsg } = await uri2local(payload.image, undefined, true) + if (!success) { + throw new Error(`设置群公告失败, 错误信息: uri2local: ${errMsg}`) + } + await checkFileReceived(path, 5000) // 文件不存在QQ会崩溃,需要提前判断 + const result = await this.ctx.ntGroupApi.uploadGroupBulletinPic(groupCode, path) + if (result.errCode !== 0) { + throw new Error(`设置群公告失败, 错误信息: uploadGroupBulletinPic: ${result.errMsg}`) + } + if (!isLocal) { + unlink(path) + } + picInfo = result.picInfo + } + + const res = await this.ctx.ntGroupApi.publishGroupBulletin(groupCode, { + text: encodeURIComponent(payload.content), + oldFeedsId: '', pinned, - type, - isShowEditCard, - tipWindowType, confirmRequired, - picId: '' + picInfo }) - if (result.ec !== 0) { - throw new Error(`设置群公告失败, 错误信息: ${result.em}`) + if (res.result !== 0) { + throw new Error(`设置群公告失败, 错误信息: ${res.errMsg}`) } return null } diff --git a/src/onebot11/action/go-cqhttp/UploadFile.ts b/src/onebot11/action/go-cqhttp/UploadFile.ts index 3502d2f..e64c1ed 100644 --- a/src/onebot11/action/go-cqhttp/UploadFile.ts +++ b/src/onebot11/action/go-cqhttp/UploadFile.ts @@ -1,8 +1,6 @@ -import fs from 'node:fs' import BaseAction from '../BaseAction' import { ActionName } from '../types' import { SendElementEntities } from '@/ntqqapi/entities' -import { SendFileElement } from '@/ntqqapi/types' import { uri2local } from '@/common/utils' import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage' @@ -18,15 +16,11 @@ export class UploadGroupFile extends BaseAction { actionName = ActionName.GoCQHTTP_UploadGroupFile protected async _handle(payload: UploadGroupFilePayload): Promise { - let file = payload.file - if (fs.existsSync(file)) { - file = `file://${file}` + const { success, errMsg, path, fileName } = await uri2local(payload.file) + if (!success) { + throw new Error(errMsg) } - const downloadResult = await uri2local(file) - if (!downloadResult.success) { - throw new Error(downloadResult.errMsg) - } - const sendFileEle = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name, payload.folder_id) + const sendFileEle = await SendElementEntities.file(this.ctx, path, payload.name || fileName, payload.folder_id) const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group) await sendMsg(this.ctx, peer, [sendFileEle], []) return null @@ -43,16 +37,12 @@ export class UploadPrivateFile extends BaseAction { + const { success, errMsg, path, fileName } = await uri2local(payload.file) + if (!success) { + throw new Error(errMsg) + } + const sendFileEle = await SendElementEntities.file(this.ctx, path, payload.name || fileName) const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private) - let file = payload.file - if (fs.existsSync(file)) { - file = `file://${file}` - } - const downloadResult = await uri2local(file) - if (!downloadResult.success) { - throw new Error(downloadResult.errMsg) - } - const sendFileEle: SendFileElement = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name) await sendMsg(this.ctx, peer, [sendFileEle], []) return null } diff --git a/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts index 8a74c66..4a36195 100644 --- a/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts +++ b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts @@ -4,7 +4,7 @@ export type GroupDecreaseSubType = 'leave' | 'kick' | 'kick_me' export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent { notice_type = 'group_decrease' - sub_type: GroupDecreaseSubType = 'leave' // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me") + sub_type: GroupDecreaseSubType = 'leave' operator_id: number group_id: number user_id: number diff --git a/src/version.ts b/src/version.ts index 79bfc7b..daf7c9a 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.31.10' +export const version = '3.32.0'