diff --git a/manifest.json b/manifest.json index b9fccf0..3009867 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 协议,用以 QQ 机器人开发", - "version": "3.29.2", + "version": "3.29.3", "icon": "./icon.webp", "authors": [ { diff --git a/package.json b/package.json index f963f1e..c269e75 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,15 @@ "author": "", "license": "MIT", "dependencies": { - "@minatojs/driver-sqlite": "^4.4.1", + "@minatojs/driver-sqlite": "^4.5.0", "compressing": "^1.10.1", - "cordis": "^3.17.9", + "cordis": "^3.18.0", "cors": "^2.8.5", "express": "^4.19.2", "fast-xml-parser": "^4.4.1", - "file-type": "^19.4.0", + "file-type": "^19.4.1", "fluent-ffmpeg": "^2.1.3", - "minato": "^3.4.3", + "minato": "^3.5.0", "silk-wasm": "^3.6.1", "ws": "^8.18.0" }, @@ -37,7 +37,7 @@ "electron": "^29.1.4", "electron-vite": "^2.3.0", "typescript": "^5.5.4", - "vite": "^5.4.0", + "vite": "^5.4.1", "vite-plugin-cp": "^4.0.8" }, "packageManager": "yarn@4.4.0" diff --git a/src/common/data.ts b/src/common/data.ts index a875fc4..60b031e 100644 --- a/src/common/data.ts +++ b/src/common/data.ts @@ -1,6 +1,5 @@ import { type Friend, - type Group, type GroupMember, type SelfInfo, } from '../ntqqapi/types' @@ -11,8 +10,8 @@ import { isNumeric } from './utils/helper' import { NTQQFriendApi, NTQQUserApi } from '../ntqqapi/api' import { RawMessage } from '../ntqqapi/types' import { getConfigUtil } from './config' +import { getBuildVersion } from './utils/QQBasicInfo' -export let groups: Group[] = [] export let friends: Friend[] = [] export const llonebotError: LLOneBotError = { ffmpegError: '', @@ -24,10 +23,10 @@ export const llonebotError: LLOneBotError = { export const groupMembers: Map> = new Map>() export async function getFriend(uinOrUid: string): Promise { - let filterKey = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid' - let filterValue = uinOrUid + const filterKey: 'uin' | 'uid' = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid' + const filterValue = uinOrUid let friend = friends.find((friend) => friend[filterKey] === filterValue.toString()) - if (!friend) { + if (!friend && getBuildVersion() < 26702) { try { const _friends = await NTQQFriendApi.getFriends(true) friend = _friends.find((friend) => friend[filterKey] === filterValue.toString()) @@ -41,39 +40,15 @@ export async function getFriend(uinOrUid: string): Promise { return friend } -export async function getGroup(qq: string): Promise { - let group = groups.find((group) => group.groupCode === qq.toString()) - if (!group) { - try { - const _groups = await NTQQGroupApi.getGroups(true) - group = _groups.find((group) => group.groupCode === qq.toString()) - if (group) { - groups.push(group) - } - } catch (e) { - } - } - return group -} - -export function deleteGroup(groupCode: string) { - const groupIndex = groups.findIndex((group) => group.groupCode === groupCode.toString()) - // log(groups, groupCode, groupIndex); - if (groupIndex !== -1) { - log('删除群', groupCode) - groups.splice(groupIndex, 1) - } -} - -export async function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number) { - groupQQ = groupQQ.toString() - memberUinOrUid = memberUinOrUid.toString() - let members = groupMembers.get(groupQQ) +export async function getGroupMember(groupCode: string | number, memberUinOrUid: string | number) { + const groupCodeStr = groupCode.toString() + const memberUinOrUidStr = memberUinOrUid.toString() + let members = groupMembers.get(groupCodeStr) if (!members) { try { - members = await NTQQGroupApi.getGroupMembers(groupQQ) + members = await NTQQGroupApi.getGroupMembers(groupCodeStr) // 更新群成员列表 - groupMembers.set(groupQQ, members) + groupMembers.set(groupCodeStr, members) } catch (e) { return null @@ -81,16 +56,16 @@ export async function getGroupMember(groupQQ: string | number, memberUinOrUid: s } const getMember = () => { let member: GroupMember | undefined = undefined - if (isNumeric(memberUinOrUid)) { - member = Array.from(members!.values()).find(member => member.uin === memberUinOrUid) + if (isNumeric(memberUinOrUidStr)) { + member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr) } else { - member = members!.get(memberUinOrUid) + member = members!.get(memberUinOrUidStr) } return member } let member = getMember() if (!member) { - members = await NTQQGroupApi.getGroupMembers(groupQQ) + members = await NTQQGroupApi.getGroupMembers(groupCodeStr) member = getMember() } return member diff --git a/src/common/types.ts b/src/common/types.ts index e7612b4..94987de 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -50,3 +50,15 @@ export interface FileCache { elementId: string elementType: number } + +export interface FileCacheV2 { + fileName: string + fileSize: string + fileUuid: string + msgId: string + msgTime: number + peerUid: string + chatType: number + elementId: string + elementType: number +} \ No newline at end of file diff --git a/src/common/utils/MessageUnique.ts b/src/common/utils/MessageUnique.ts index 9fd3873..27b882e 100644 --- a/src/common/utils/MessageUnique.ts +++ b/src/common/utils/MessageUnique.ts @@ -7,7 +7,7 @@ import SQLite from '@minatojs/driver-sqlite' import fsPromise from 'node:fs/promises' import fs from 'node:fs' import path from 'node:path' -import { FileCache } from '../types' +import { FileCacheV2 } from '../types' interface SQLiteTables extends Tables { message: { @@ -16,7 +16,7 @@ interface SQLiteTables extends Tables { chatType: number peerUid: string } - file: FileCache + file_v2: FileCacheV2 } interface MsgIdAndPeerByShortId { @@ -52,16 +52,19 @@ class MessageUniqueWrapper { }, { primary: 'shortId' }) - database.extend('file', { + database.extend('file_v2', { fileName: 'string', fileSize: 'string', + fileUuid: 'string(128)', msgId: 'string(24)', + msgTime: 'unsigned(10)', peerUid: 'string(24)', chatType: 'unsigned', elementId: 'string(24)', elementType: 'unsigned', }, { - primary: 'fileName' + primary: 'fileUuid', + indexes: ['fileName'] }) this.db = database } @@ -142,12 +145,18 @@ class MessageUniqueWrapper { this.msgDataMap.resize(maxSize) } - addFileCache(data: FileCache) { - return this.db?.upsert('file', [data], 'fileName') + addFileCache(data: FileCacheV2) { + return this.db?.upsert('file_v2', [data], 'fileUuid') } - getFileCache(fileName: string) { - return this.db?.get('file', { fileName }) + getFileCacheByName(fileName: string) { + return this.db?.get('file_v2', { fileName }, { + sort: { msgTime: 'desc' } + }) + } + + getFileCacheById(fileUuid: string) { + return this.db?.get('file_v2', { fileUuid }) } } diff --git a/src/main/main.ts b/src/main/main.ts index 7d758be..5bf644f 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -16,7 +16,6 @@ import { import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer' import { DATA_DIR, TEMP_DIR } from '../common/utils' import { - getGroupMember, llonebotError, setSelfInfo, getSelfInfo, @@ -28,7 +27,7 @@ import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, import { OB11Constructor } from '../onebot11/constructor' import { FriendRequestNotify, - GroupNotifies, + GroupNotify, GroupNotifyTypes, RawMessage, BuddyReqType, @@ -245,6 +244,7 @@ function onLoad() { log('report self message error: ', e.stack.toString()) } }) + const processedGroupNotify: string[] = [] registerReceiveHook<{ doubt: boolean oldestUnreadSeq: string @@ -252,48 +252,43 @@ function onLoad() { }>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => { if (payload.unreadCount) { // log("开始获取群通知详情") - let notify: GroupNotifies + let notifies: GroupNotify[] try { - notify = await NTQQGroupApi.getGroupNotifies() + notifies = (await NTQQGroupApi.getSingleScreenNotifies(14)).slice(0, payload.unreadCount) } catch (e) { // log("获取群通知详情失败", e); return } - const notifies = notify.notifies.slice(0, payload.unreadCount) - // log("获取群通知详情完成", notifies, payload); - for (const notify of notifies) { try { notify.time = Date.now() const notifyTime = parseInt(notify.seq) / 1000 - if (notifyTime < startTime) { + const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type + if (notifyTime < startTime || processedGroupNotify.includes(flag)) { continue } - log('收到群通知', notify) - const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type + processedGroupNotify.push(flag) if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) { log('有成员退出通知', notify) - try { - const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid) - let operatorId = member1.uin - let subType: GroupDecreaseSubType = 'leave' - if (notify.user2.uid) { - // 是被踢的 - const member2 = await getGroupMember(notify.group.groupCode, notify.user2.uid) - operatorId = member2?.uin! - subType = 'kick' + const member1Uin = (await NTQQUserApi.getUinByUid(notify.user1.uid))! + let operatorId = member1Uin + let subType: GroupDecreaseSubType = 'leave' + if (notify.user2.uid) { + // 是被踢的 + const member2Uin = await NTQQUserApi.getUinByUid(notify.user2.uid) + if (member2Uin) { + operatorId = member2Uin } - let groupDecreaseEvent = new OB11GroupDecreaseEvent( - parseInt(notify.group.groupCode), - parseInt(member1.uin), - parseInt(operatorId), - subType, - ) - postOb11Event(groupDecreaseEvent, true) - } catch (e: any) { - log('获取群通知的成员信息失败', notify, e.stack.toString()) + subType = 'kick' } + const groupDecreaseEvent = new OB11GroupDecreaseEvent( + parseInt(notify.group.groupCode), + parseInt(member1Uin), + parseInt(operatorId), + subType, + ) + postOb11Event(groupDecreaseEvent, true) } else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) { log('有加群请求') diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index cb51f3e..fcab865 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -8,7 +8,7 @@ import { CacheClassFuncAsyncExtend } from '@/common/utils/helper' import { LimitedHashTable } from '@/common/utils/table' export class NTQQFriendApi { - /** >=26702 应使用 getBuddyV2 */ + /** 大于或等于 26702 应使用 getBuddyV2 */ static async getFriends(forced = false) { const data = await callNTQQApi<{ data: { diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index a1d6b2f..2e39155 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -1,10 +1,11 @@ import { ReceiveCmdS } from '../hook' -import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes } from '../types' +import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify } from '../types' import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { NTQQWindowApi, NTQQWindows } from './window' import { getSession } from '../wrapper' import { NTEventDispatch } from '@/common/utils/EventTask' import { NodeIKernelGroupListener } from '../listeners' +import { NodeIKernelGroupService } from '../services' export class NTQQGroupApi { static async activateMemberListChange() { @@ -45,12 +46,29 @@ export class NTQQGroupApi { 'NodeIKernelGroupListener/onGroupListUpdate', 1, 5000, - (updateType) => true, + () => true, forced ) return groupList } + static async getGroupMemberV2(GroupCode: string, uid: string, forced = false) { + type ListenerType = NodeIKernelGroupListener['onMemberInfoChange'] + type EventType = NodeIKernelGroupService['getMemberInfo'] + const [, , , _members] = await NTEventDispatch.CallNormalEvent + ( + 'NodeIKernelGroupService/getMemberInfo', + 'NodeIKernelGroupListener/onMemberInfoChange', + 1, + 5000, + (groupCode: string, changeType: number, members: Map) => { + return groupCode == GroupCode && members.has(uid) + }, + GroupCode, [uid], forced, + ) + return _members.get(uid) + } + static async getGroupMembers(groupQQ: string, num = 3000): Promise> { const session = getSession() const groupService = session?.getGroupService() @@ -100,6 +118,36 @@ export class NTQQGroupApi { ) } + static async getSingleScreenNotifies(num: number) { + const [_retData, _doubt, _seq, notifies] = await NTEventDispatch.CallNormalEvent + <(arg1: boolean, arg2: string, arg3: number) => Promise, (doubt: boolean, seq: string, notifies: GroupNotify[]) => void> + ( + 'NodeIKernelGroupService/getSingleScreenNotifies', + 'NodeIKernelGroupListener/onGroupSingleScreenNotifies', + 1, + 5000, + () => true, + false, + '', + num, + ) + return notifies + } + + static async delGroupFile(groupCode: string, files: string[]) { + const session = getSession() + return session?.getRichMediaService().deleteGroupFile(groupCode, [102], files)! + } + + static DelGroupFile = NTQQGroupApi.delGroupFile + + static async delGroupFileFolder(groupCode: string, folderId: string) { + const session = getSession() + return session?.getRichMediaService().deleteGroupFolder(groupCode, folderId)! + } + + static DelGroupFileFolder = NTQQGroupApi.delGroupFileFolder + static async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) { const flagitem = flag.split('|') const groupCode = flagitem[0] diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 6acd5e3..aa107cc 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -4,27 +4,20 @@ import { NTQQMsgApi } from './api/msg' import { CategoryFriend, ChatType, - FriendV2, - Group, GroupMember, GroupMemberRole, RawMessage, SimpleInfo, User, } from './types' import { - deleteGroup, friends, getFriend, getGroupMember, - groups, - getSelfUin, setSelfInfo } from '@/common/data' -import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' import { postOb11Event } from '../onebot11/server/post-ob11-event' import { getConfigUtil, HOOK_LOG } from '@/common/config' import fs from 'node:fs' -import { NTQQGroupApi } from './api/group' import { log } from '@/common/utils' import { randomUUID } from 'node:crypto' import { MessageUnique } from '../common/utils/MessageUnique' @@ -242,9 +235,9 @@ export function removeReceiveHook(id: string) { receiveHooks.splice(index, 1) } -let activatedGroups: string[] = [] +//let activatedGroups: string[] = [] -async function updateGroups(_groups: Group[], needUpdate: boolean = true) { +/*async function updateGroups(_groups: Group[], needUpdate: boolean = true) { for (let group of _groups) { log('update group', group.groupCode) if (group.privilegeFlag === 0) { @@ -269,9 +262,9 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) { } } } -} +}*/ -async function processGroupEvent(payload: { groupList: Group[] }) { +/*async function processGroupEvent(payload: { groupList: Group[] }) { try { const newGroupList = payload.groupList for (const group of newGroupList) { @@ -322,12 +315,12 @@ async function processGroupEvent(payload: { groupList: Group[] }) { updateGroups(payload.groupList).then() log('更新群信息错误', e.stack.toString()) } -} +}*/ export async function startHook() { // 群列表变动 - registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => { + /*registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => { // updateType 3是群列表变动,2是群成员变动 // log("群列表变动", payload.updateType, payload.groupList) if (payload.updateType != 2) { @@ -350,7 +343,7 @@ export async function startHook() { processGroupEvent(payload).then() } } - }) + })*/ registerReceiveHook<{ groupCode: string diff --git a/src/ntqqapi/types/group.ts b/src/ntqqapi/types/group.ts index 4ba3e07..6512575 100644 --- a/src/ntqqapi/types/group.ts +++ b/src/ntqqapi/types/group.ts @@ -45,7 +45,7 @@ export enum GroupMemberRole { } export interface GroupMember { - memberSpecialTitle: string + memberSpecialTitle?: string avatarPath: string cardName: string cardType: number @@ -60,4 +60,7 @@ export interface GroupMember { isRobot: boolean sex?: Sex qqLevel?: QQLevel + isChangeRole: boolean + joinTime: string + lastSpeakTime: string } diff --git a/src/onebot11/action/file/GetFile.ts b/src/onebot11/action/file/GetFile.ts index 11fff91..fbbda7d 100644 --- a/src/onebot11/action/file/GetFile.ts +++ b/src/onebot11/action/file/GetFile.ts @@ -23,66 +23,12 @@ export abstract class GetFileBase extends BaseAction { const { enableLocalFile2Url } = getConfigUtil().getConfig() - let UuidData: { - high: string - low: string - } | undefined - try { - UuidData = UUIDConverter.decode(payload.file) - if (UuidData) { - const peerUin = UuidData.high - const msgId = UuidData.low - const isGroup: boolean = !!(await NTQQGroupApi.getGroups(false)).find(e => e.groupCode == peerUin) - let peer: Peer | undefined - //识别Peer - if (isGroup) { - peer = { chatType: ChatType.group, peerUid: peerUin } - } - const PeerUid = await NTQQUserApi.getUidByUinV2(peerUin) - if (PeerUid) { - const isBuddy = await NTQQFriendApi.isBuddy(PeerUid) - if (isBuddy) { - peer = { chatType: ChatType.friend, peerUid: PeerUid } - } else { - peer = { chatType: ChatType.temp, peerUid: PeerUid } - } - } - if (!peer) { - throw new Error('chattype not support') - } - const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]) - if (msgList.msgList.length === 0) { - throw new Error('msg not found') - } - const msg = msgList.msgList[0] - const findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT) - if (!findEle) { - throw new Error('element not found') - } - const downloadPath = await NTQQFileApi.downloadMedia(msgId, msg.chatType, msg.peerUid, findEle.elementId, '', '') - const fileSize = findEle?.videoElement?.fileSize || findEle?.fileElement?.fileSize || findEle?.pttElement?.fileSize || '0' - const fileName = findEle?.videoElement?.fileName || findEle?.fileElement?.fileName || findEle?.pttElement?.fileName || '' - const res: GetFileResponse = { - file: downloadPath, - url: downloadPath, - file_size: fileSize, - file_name: fileName, - } - if (enableLocalFile2Url && downloadPath) { - try { - res.base64 = await fsPromise.readFile(downloadPath, 'base64') - } catch (e) { - throw new Error('文件下载失败. ' + e) - } - } - //不手动删除?文件持久化了 - return res - } - } catch { + let fileCache = await MessageUnique.getFileCacheById(String(payload.file)) + if (!fileCache?.length) { + fileCache = await MessageUnique.getFileCacheByName(String(payload.file)) } - const fileCache = await MessageUnique.getFileCache(String(payload.file)) if (fileCache?.length) { const downloadPath = await NTQQFileApi.downloadMedia( fileCache[0].msgId, diff --git a/src/onebot11/action/go-cqhttp/DelGroupFile.ts b/src/onebot11/action/go-cqhttp/DelGroupFile.ts new file mode 100644 index 0000000..eddca67 --- /dev/null +++ b/src/onebot11/action/go-cqhttp/DelGroupFile.ts @@ -0,0 +1,17 @@ +import BaseAction from '../BaseAction' +import { ActionName } from '../types' +import { NTQQGroupApi } from '@/ntqqapi/api' + +interface Payload { + group_id: string | number + file_id: string + busid?: 102 +} + +export class GoCQHTTPDelGroupFile extends BaseAction { + actionName = ActionName.GoCQHTTP_DelGroupFile + + async _handle(payload: Payload) { + await NTQQGroupApi.delGroupFile(payload.group_id.toString(), [payload.file_id]) + } +} \ No newline at end of file diff --git a/src/onebot11/action/go-cqhttp/UploadFile.ts b/src/onebot11/action/go-cqhttp/UploadFile.ts index 08a0b69..c7cdd99 100644 --- a/src/onebot11/action/go-cqhttp/UploadFile.ts +++ b/src/onebot11/action/go-cqhttp/UploadFile.ts @@ -1,6 +1,5 @@ import fs from 'node:fs' import BaseAction from '../BaseAction' -import { getGroup } from '@/common/data' import { ActionName } from '../types' import { SendMsgElementConstructor } from '@/ntqqapi/constructor' import { ChatType, SendFileElement } from '@/ntqqapi/types' @@ -22,10 +21,6 @@ export class GoCQHTTPUploadGroupFile extends BaseAction { actionName = ActionName.GoCQHTTP_UploadGroupFile protected async _handle(payload: Payload): Promise { - const group = await getGroup(payload.group_id?.toString()!) - if (!group) { - throw new Error(`群组${payload.group_id}不存在`) - } let file = payload.file if (fs.existsSync(file)) { file = `file://${file}` @@ -34,8 +29,11 @@ export class GoCQHTTPUploadGroupFile extends BaseAction { if (!downloadResult.success) { throw new Error(downloadResult.errMsg) } - const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id) - await sendMsg({ chatType: ChatType.group, peerUid: group.groupCode }, [sendFileEle], [], true) + const sendFileEle = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id) + await sendMsg({ + chatType: ChatType.group, + peerUid: payload.group_id?.toString()!, + }, [sendFileEle], [], true) return null } } diff --git a/src/onebot11/action/group/GetGroupInfo.ts b/src/onebot11/action/group/GetGroupInfo.ts index 8bfc219..d251514 100644 --- a/src/onebot11/action/group/GetGroupInfo.ts +++ b/src/onebot11/action/group/GetGroupInfo.ts @@ -1,18 +1,18 @@ import { OB11Group } from '../../types' -import { getGroup } from '../../../common/data' import { OB11Constructor } from '../../constructor' import BaseAction from '../BaseAction' import { ActionName } from '../types' +import { NTQQGroupApi } from '@/ntqqapi/api' -interface PayloadType { - group_id: number +interface Payload { + group_id: number | string } -class GetGroupInfo extends BaseAction { +class GetGroupInfo extends BaseAction { actionName = ActionName.GetGroupInfo - protected async _handle(payload: PayloadType) { - const group = await getGroup(payload.group_id.toString()) + protected async _handle(payload: Payload) { + const group = (await NTQQGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString()) if (group) { return OB11Constructor.group(group) } else { diff --git a/src/onebot11/action/group/GetGroupList.ts b/src/onebot11/action/group/GetGroupList.ts index 8d1e0be..b071085 100644 --- a/src/onebot11/action/group/GetGroupList.ts +++ b/src/onebot11/action/group/GetGroupList.ts @@ -1,10 +1,8 @@ import { OB11Group } from '../../types' import { OB11Constructor } from '../../constructor' -import { groups } from '../../../common/data' import BaseAction from '../BaseAction' import { ActionName } from '../types' import { NTQQGroupApi } from '../../../ntqqapi/api' -import { log } from '../../../common/utils' interface Payload { no_cache: boolean | string @@ -14,14 +12,8 @@ class GetGroupList extends BaseAction { actionName = ActionName.GetGroupList protected async _handle(payload: Payload) { - if (groups.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') { - try { - const groups = await NTQQGroupApi.getGroups(true) - log('强制刷新群列表, 数量:', groups.length) - return OB11Constructor.groups(groups) - } catch (e) {} - } - return OB11Constructor.groups(groups) + const groupList = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload.no_cache === 'true') + return OB11Constructor.groups(groupList) } } diff --git a/src/onebot11/action/group/GetGroupMemberInfo.ts b/src/onebot11/action/group/GetGroupMemberInfo.ts index cdd4a7d..2273f23 100644 --- a/src/onebot11/action/group/GetGroupMemberInfo.ts +++ b/src/onebot11/action/group/GetGroupMemberInfo.ts @@ -24,7 +24,11 @@ class GetGroupMemberInfo extends BaseAction { log('群成员详细信息结果', info) Object.assign(member, info) } - return OB11Constructor.groupMember(payload.group_id.toString(), member) + const ret = OB11Constructor.groupMember(payload.group_id.toString(), member) + const date = Math.round(Date.now() / 1000) + ret.last_sent_time = Number(member.lastSpeakTime || date) + ret.join_time = Number(member.joinTime || date) + return ret } else { throw `群成员${payload.user_id}不存在` } diff --git a/src/onebot11/action/group/GetGroupMemberList.ts b/src/onebot11/action/group/GetGroupMemberList.ts index 86e4456..9edd6f4 100644 --- a/src/onebot11/action/group/GetGroupMemberList.ts +++ b/src/onebot11/action/group/GetGroupMemberList.ts @@ -1,31 +1,58 @@ import { OB11GroupMember } from '../../types' -import { getGroup } from '../../../common/data' import { OB11Constructor } from '../../constructor' import BaseAction from '../BaseAction' import { ActionName } from '../types' -import { NTQQGroupApi } from '../../../ntqqapi/api/group' -import { log } from '../../../common/utils' +import { NTQQGroupApi, WebApi } from '@/ntqqapi/api' +import { getSelfUid } from '@/common/data' -export interface PayloadType { - group_id: number +interface Payload { + group_id: number | string no_cache: boolean | string } -class GetGroupMemberList extends BaseAction { +class GetGroupMemberList extends BaseAction { actionName = ActionName.GetGroupMemberList - protected async _handle(payload: PayloadType) { - const group = await getGroup(payload.group_id.toString()) - if (group) { - if (!group.members?.length || payload.no_cache === true || payload.no_cache === 'true') { - const members = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) - group.members = Array.from(members.values()) - log('强制刷新群成员列表, 数量: ', group.members.length) - } - return OB11Constructor.groupMembers(group) - } else { - throw `群${payload.group_id}不存在` + protected async _handle(payload: Payload) { + const groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) + const groupMembersArr = Array.from(groupMembers.values()) + + let _groupMembers = groupMembersArr.map(item => { + return OB11Constructor.groupMember(payload.group_id.toString(), item) + }) + + const MemberMap: Map = new Map() + const date = Math.round(Date.now() / 1000) + + for (let i = 0, len = _groupMembers.length; i < len; i++) { + // 保证基础数据有这个 同时避免群管插件过于依赖这个杀了 + _groupMembers[i].join_time = date + _groupMembers[i].last_sent_time = date + MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]) } + + const selfRole = groupMembers.get(getSelfUid())?.role + const isPrivilege = selfRole === 3 || selfRole === 4 + + if (isPrivilege) { + const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()) + for (let i = 0, len = webGroupMembers.length; i < len; i++) { + if (!webGroupMembers[i]?.uin) { + continue + } + const MemberData = MemberMap.get(webGroupMembers[i]?.uin) + if (MemberData) { + MemberData.join_time = webGroupMembers[i]?.join_time + MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time + MemberData.qage = webGroupMembers[i]?.qage + MemberData.level = webGroupMembers[i]?.lv.level.toString() + MemberMap.set(webGroupMembers[i]?.uin, MemberData) + } + } + } + + _groupMembers = Array.from(MemberMap.values()) + return _groupMembers } } diff --git a/src/onebot11/action/index.ts b/src/onebot11/action/index.ts index a0a8b02..6b6d8d9 100644 --- a/src/onebot11/action/index.ts +++ b/src/onebot11/action/index.ts @@ -1,6 +1,6 @@ import GetMsg from './msg/GetMsg' import GetLoginInfo from './system/GetLoginInfo' -import { GetFriendList, GetFriendWithCategory} from './user/GetFriendList' +import { GetFriendList, GetFriendWithCategory } from './user/GetFriendList' import GetGroupList from './group/GetGroupList' import GetGroupInfo from './group/GetGroupInfo' import GetGroupMemberList from './group/GetGroupMemberList' @@ -53,6 +53,7 @@ import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation' import GoCQHTTPSetEssenceMsg from './go-cqhttp/SetEssenceMsg' import GoCQHTTPDelEssenceMsg from './go-cqhttp/DelEssenceMsg' import GetEvent from './llonebot/GetEvent' +import { GoCQHTTPDelGroupFile } from './go-cqhttp/DelGroupFile' export const actionHandlers = [ @@ -113,7 +114,8 @@ export const actionHandlers = [ new GoCQHTTGetForwardMsgAction(), new GoCQHTTHandleQuickOperation(), new GoCQHTTPSetEssenceMsg(), - new GoCQHTTPDelEssenceMsg() + new GoCQHTTPDelEssenceMsg(), + new GoCQHTTPDelGroupFile() ] function initActionMap() { diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index a08c3d5..f7f9b7d 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -6,7 +6,7 @@ import { RawMessage, SendMessageElement, } from '@/ntqqapi/types' -import { getGroup, getGroupMember, getSelfUid, getSelfUin } from '@/common/data' +import { getGroupMember, getSelfUid, getSelfUin } from '@/common/data' import { OB11MessageCustomMusic, OB11MessageData, @@ -305,10 +305,9 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode) // This redundant design of Ob11 here should be blamed. if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) { - const group = (await getGroup(payload.group_id))! // checked before return { chatType: ChatType.group, - peerUid: group.groupCode + peerUid: payload.group_id.toString(), } } if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { @@ -318,7 +317,7 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode) return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid: Uid!, - guildId: payload.group_id || ''//临时主动发起时需要传入群号 + guildId: payload.group_id?.toString() || '' //临时主动发起时需要传入群号 } } throw '请指定 group_id 或 user_id' @@ -343,12 +342,6 @@ export class SendMsg extends BaseAction { message: '音乐消息不可以和其他消息混在一起发送', } } - if (payload.message_type !== 'private' && payload.group_id && !(await getGroup(payload.group_id))) { - return { - valid: false, - message: `群${payload.group_id}不存在`, - } - } if (payload.user_id && payload.message_type !== 'group') { const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString()) const isBuddy = await NTQQFriendApi.isBuddy(uid!) diff --git a/src/onebot11/action/types.ts b/src/onebot11/action/types.ts index 88cdd0f..71730ea 100644 --- a/src/onebot11/action/types.ts +++ b/src/onebot11/action/types.ts @@ -73,4 +73,5 @@ export enum ActionName { GetGroupHonorInfo = "get_group_honor_info", GoCQHTTP_SetEssenceMsg = 'set_essence_msg', GoCQHTTP_DelEssenceMsg = 'delete_essence_msg', + GoCQHTTP_DelGroupFile = 'delete_group_file', } diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts index c94335f..2c399cb 100644 --- a/src/onebot11/constructor.ts +++ b/src/onebot11/constructor.ts @@ -17,7 +17,6 @@ import { Group, Peer, GroupMember, - PicType, RawMessage, SelfInfo, Sex, @@ -26,11 +25,10 @@ import { FriendV2, ChatType2 } from '../ntqqapi/types' -import { deleteGroup, getGroupMember, getSelfUin } from '../common/data' +import { getGroupMember, getSelfUin } from '../common/data' import { EventType } from './event/OB11BaseEvent' import { encodeCQCode } from './cqcode' import { MessageUnique } from '../common/utils/MessageUnique' -import { UUIDConverter } from '../common/utils/helper' import { OB11GroupIncreaseEvent } from './event/notice/OB11GroupIncreaseEvent' import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent' import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent' @@ -192,41 +190,61 @@ export class OB11Constructor { }*/ message_data['data']['file'] = picElement.fileName message_data['data']['subType'] = picElement.picSubType - message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId) + //message_data['data']['file_id'] = picElement.fileUuid message_data['data']['url'] = await NTQQFileApi.getImageUrl(picElement) message_data['data']['file_size'] = picElement.fileSize MessageUnique.addFileCache({ peerUid: msg.peerUid, msgId: msg.msgId, + msgTime: +msg.msgTime, chatType: msg.chatType, elementId: element.elementId, elementType: element.elementType, fileName: picElement.fileName, fileSize: String(picElement.fileSize || '0'), + fileUuid: picElement.fileUuid }) } - else if (element.videoElement || element.fileElement) { - const videoOrFileElement = element.videoElement || element.fileElement - message_data['type'] = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file - message_data['data']['file'] = videoOrFileElement.fileName - message_data['data']['path'] = videoOrFileElement.filePath - message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId) - message_data['data']['file_size'] = videoOrFileElement.fileSize - if (element.videoElement) { - message_data['data']['url'] = await NTQQFileApi.getVideoUrl({ - chatType: msg.chatType, - peerUid: msg.peerUid, - }, msg.msgId, element.elementId, - ) - } + else if (element.videoElement) { + message_data['type'] = OB11MessageDataType.video + const { videoElement } = element + message_data['data']['file'] = videoElement.fileName + message_data['data']['path'] = videoElement.filePath + //message_data['data']['file_id'] = videoElement.fileUuid + message_data['data']['file_size'] = videoElement.fileSize + message_data['data']['url'] = await NTQQFileApi.getVideoUrl({ + chatType: msg.chatType, + peerUid: msg.peerUid, + }, msg.msgId, element.elementId) MessageUnique.addFileCache({ peerUid: msg.peerUid, msgId: msg.msgId, + msgTime: +msg.msgTime, chatType: msg.chatType, elementId: element.elementId, elementType: element.elementType, - fileName: videoOrFileElement.fileName, - fileSize: String(videoOrFileElement.fileSize || '0') + fileName: videoElement.fileName, + fileSize: String(videoElement.fileSize || '0'), + fileUuid: videoElement.fileUuid! + }) + } + else if (element.fileElement) { + message_data['type'] = OB11MessageDataType.file + const { fileElement } = element + message_data['data']['file'] = fileElement.fileName + message_data['data']['path'] = fileElement.filePath + message_data['data']['file_id'] = fileElement.fileUuid + message_data['data']['file_size'] = fileElement.fileSize + MessageUnique.addFileCache({ + peerUid: msg.peerUid, + msgId: msg.msgId, + msgTime: +msg.msgTime, + chatType: msg.chatType, + elementId: element.elementId, + elementType: element.elementType, + fileName: fileElement.fileName, + fileSize: String(fileElement.fileSize || '0'), + fileUuid: fileElement.fileUuid! }) } else if (element.pttElement) { @@ -234,16 +252,18 @@ export class OB11Constructor { const { pttElement } = element message_data['data']['file'] = pttElement.fileName message_data['data']['path'] = pttElement.filePath - message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId) + //message_data['data']['file_id'] = pttElement.fileUuid message_data['data']['file_size'] = pttElement.fileSize MessageUnique.addFileCache({ peerUid: msg.peerUid, msgId: msg.msgId, + msgTime: +msg.msgTime, chatType: msg.chatType, elementId: element.elementId, elementType: element.elementType, fileName: pttElement.fileName, - fileSize: String(pttElement.fileSize || '0') + fileSize: String(pttElement.fileSize || '0'), + fileUuid: pttElement.fileUuid }) } else if (element.arkElement) { @@ -358,7 +378,7 @@ export class OB11Constructor { const groupElement = grayTipElement?.groupElement if (groupElement) { // log("收到群提示消息", groupElement) - if (groupElement.type == TipGroupElementType.memberIncrease) { + if (groupElement.type === TipGroupElementType.memberIncrease) { log('收到群成员增加消息', groupElement) await sleep(1000) const member = await getGroupMember(msg.peerUid, groupElement.memberUid) @@ -406,25 +426,26 @@ export class OB11Constructor { ) } } - else if (groupElement.type == TipGroupElementType.kicked) { + else if (groupElement.type === TipGroupElementType.kicked) { log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement) - deleteGroup(msg.peerUid) NTQQGroupApi.quitGroup(msg.peerUid).then() - const selfUin = getSelfUin() try { - const adminUin = - (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || - (await NTQQUserApi.getUserDetailInfo(groupElement.adminUid))?.uin + const adminUin = (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await NTQQUserApi.getUidByUin(groupElement.adminUid)) if (adminUin) { return new OB11GroupDecreaseEvent( parseInt(msg.peerUid), - parseInt(selfUin), + parseInt(getSelfUin()), parseInt(adminUin), - 'kick_me', + 'kick_me' ) } } catch (e) { - return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfUin), 0, 'leave') + return new OB11GroupDecreaseEvent( + parseInt(msg.peerUid), + parseInt(getSelfUin()), + 0, + 'leave' + ) } } } @@ -677,7 +698,7 @@ export class OB11Constructor { sex: OB11Constructor.sex(member.sex!), age: 0, area: '', - level: 0, + level: '0', qq_level: (member.qqLevel && calcQQLevel(member.qqLevel)) || 0, join_time: 0, // 暂时没法获取 last_sent_time: 0, // 暂时没法获取 diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 789e64f..a771f42 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -36,7 +36,7 @@ export interface OB11GroupMember { age?: number join_time?: number last_sent_time?: number - level?: number + level?: string qq_level?: number role?: OB11GroupMemberRole title?: string @@ -48,6 +48,7 @@ export interface OB11GroupMember { shut_up_timestamp?: number // 以下为扩展字段 is_robot?: boolean + qage?: number } export interface OB11Group { diff --git a/src/version.ts b/src/version.ts index d453805..34cd654 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.29.2' +export const version = '3.29.3'