diff --git a/manifest.json b/manifest.json index 5163459..013019b 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 和 Satori 协议,用于 QQ 机器人开发", - "version": "4.0.0", + "version": "4.0.1", "icon": "./icon.webp", "authors": [ { diff --git a/src/main/store.ts b/src/main/store.ts index aa8d7c9..5d20476 100644 --- a/src/main/store.ts +++ b/src/main/store.ts @@ -62,9 +62,10 @@ class Store extends Service { } createMsgShortId(peer: Peer, msgId: string): number { + // OneBot 11 要求 message_id 为 int32 const cacheKey = `${msgId}|${peer.chatType}|${peer.peerUid}` const hash = createHash('md5').update(cacheKey).digest() - hash[0] &= 0x7f //设置第一个bit为0 保证shortId为正数 + hash[0] &= 0x7f //保证shortId为正数 const shortId = hash.readInt32BE() this.cache.set(cacheKey, shortId) this.ctx.database.upsert('message', [{ diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 67f48d9..26c9a55 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -16,7 +16,7 @@ import path from 'node:path' import { existsSync } from 'node:fs' import { ReceiveCmdS } from '../hook' import { RkeyManager } from '@/ntqqapi/helper/rkey' -import { OnRichMediaDownloadCompleteParams, Peer } from '@/ntqqapi/types/msg' +import { RichMediaDownloadCompleteNotify, Peer } from '@/ntqqapi/types/msg' import { calculateFileMD5 } from '@/common/utils/file' import { copyFile, stat, unlink } from 'node:fs/promises' import { Time } from 'cosmokit' @@ -113,7 +113,7 @@ export class NTQQFileApi extends Service { return sourcePath } } - const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>( + const data = await invoke<{ notifyInfo: RichMediaDownloadCompleteNotify }>( 'nodeIKernelMsgService/downloadRichMedia', [{ getReq: { @@ -185,7 +185,7 @@ export class NTQQFileApi extends Service { } async downloadFileForModelId(peer: Peer, fileModelId: string, timeout = 2 * Time.minute) { - const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>( + const data = await invoke<{ notifyInfo: RichMediaDownloadCompleteNotify }>( 'nodeIKernelRichMediaService/downloadFileForModelId', [{ peer, diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 75b1709..2206325 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -116,4 +116,14 @@ export class NTQQFriendApi extends Service { remarkParams: { uid, remark } }]) } + + async delBuddy(friendUid: string) { + return await invoke('nodeIKernelBuddyService/delBuddy', [{ + delInfo: { + friendUid, + tempBlock: false, + tempBothDel: true + } + }]) + } } diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index 9af3862..8e9b568 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -37,6 +37,7 @@ export class NTQQMsgApi extends Service { } async activateChatAndGetHistory(peer: Peer, cnt: number) { + // 消息从旧到新 return await invoke(NTMethod.ACTIVE_CHAT_HISTORY, [{ peer, cnt, msgId: '0', queryOrder: true }]) } diff --git a/src/ntqqapi/core.ts b/src/ntqqapi/core.ts index 7f0e0be..2e78049 100644 --- a/src/ntqqapi/core.ts +++ b/src/ntqqapi/core.ts @@ -124,10 +124,15 @@ class Core extends Service { activatedPeerUids.push(contact.id) const peer = { peerUid: contact.id, chatType: contact.chatType } if (contact.chatType === ChatType.TempC2CFromGroup) { - this.ctx.ntMsgApi.activateChatAndGetHistory(peer, 1).then(res => { - const lastTempMsg = res.msgList[0] - if (Date.now() / 1000 - Number(lastTempMsg?.msgTime) < 5) { - this.ctx.parallel('nt/message-created', lastTempMsg!) + this.ctx.ntMsgApi.activateChatAndGetHistory(peer, 2).then(res => { + for (const msg of res.msgList) { + if (Date.now() / 1000 - Number(msg.msgTime) > 3) { + continue + } + if (msg.senderUin && msg.senderUin !== '0') { + this.ctx.store.addMsgCache(msg) + } + this.ctx.parallel('nt/message-created', msg) } }) } else { diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index bb643fc..988b038 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -476,7 +476,7 @@ export interface MessageElement { actionBarElement?: unknown } -export interface OnRichMediaDownloadCompleteParams { +export interface RichMediaDownloadCompleteNotify { fileModelId: string msgElementId: string msgId: string diff --git a/src/onebot11/action/go-cqhttp/DeleteFriend.ts b/src/onebot11/action/go-cqhttp/DeleteFriend.ts new file mode 100644 index 0000000..5541ad1 --- /dev/null +++ b/src/onebot11/action/go-cqhttp/DeleteFriend.ts @@ -0,0 +1,21 @@ +import { BaseAction, Schema } from '../BaseAction' +import { ActionName } from '../types' + +interface Payload { + user_id: number | string +} + +export class DeleteFriend extends BaseAction { + actionName = ActionName.GoCQHTTP_DeleteFriend + payloadSchema = Schema.object({ + user_id: Schema.union([Number, String]).required() + }) + + protected async _handle(payload: Payload) { + const uin = payload.user_id.toString() + const uid = await this.ctx.ntUserApi.getUidByUin(uin) + if (!uid) throw new Error('无法获取用户信息') + await this.ctx.ntFriendApi.delBuddy(uid) + return null + } +} diff --git a/src/onebot11/action/index.ts b/src/onebot11/action/index.ts index a439a2c..4558006 100644 --- a/src/onebot11/action/index.ts +++ b/src/onebot11/action/index.ts @@ -72,6 +72,7 @@ import { UploadPrivateFile } from './go-cqhttp/UploadPrivateFile' import { GetGroupFileUrl } from './go-cqhttp/GetGroupFileUrl' import { GetGroupNotice } from './go-cqhttp/GetGroupNotice' import { GetRobotUinRange } from './llonebot/GetRobotUinRange' +import { DeleteFriend } from './go-cqhttp/DeleteFriend' export function initActionMap(adapter: Adapter) { const actionHandlers = [ @@ -149,6 +150,7 @@ export function initActionMap(adapter: Adapter) { new GetGroupFilesByFolder(adapter), new GetGroupFileUrl(adapter), new GetGroupNotice(adapter), + new DeleteFriend(adapter), ] const actionMap = new Map>() for (const action of actionHandlers) { diff --git a/src/onebot11/action/types.ts b/src/onebot11/action/types.ts index 7b9edb6..de10996 100644 --- a/src/onebot11/action/types.ts +++ b/src/onebot11/action/types.ts @@ -85,4 +85,5 @@ export enum ActionName { GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder', GoCQHTTP_GetGroupFileUrl = 'get_group_file_url', GoCQHTTP_GetGroupNotice = '_get_group_notice', + GoCQHTTP_DeleteFriend = 'delete_friend', } diff --git a/src/satori/utils.ts b/src/satori/utils.ts index 484a8c3..a64cee4 100644 --- a/src/satori/utils.ts +++ b/src/satori/utils.ts @@ -33,13 +33,6 @@ function decodeMessageUser(data: NT.RawMessage) { } } -function decodeMessageMember(user: Universal.User, data: NT.RawMessage) { - return { - user: user, - nick: data.sendMemberName || data.sendNickName - } -} - async function decodeElement(ctx: Context, data: NT.RawMessage, quoted = false) { const buffer: h[] = [] for (const v of data.elements) { @@ -169,12 +162,22 @@ export async function decodeMessage( message.user.nick = info.remark || info.nick } if (guildId) { + let nick = data.sendMemberName || data.sendNickName + if (!data.sendNickName) { + const info = await ctx.ntGroupApi.getGroupMember(guildId, data.senderUid) + message.user.name = info.nick + message.user.nick = info.remark || info.nick + nick = info.cardName || info.nick + } message.guild = { id: guildId, name: data.peerName, avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640` } - message.member = decodeMessageMember(message.user, data) + message.member = { + user: message.user, + nick + } } return message diff --git a/src/version.ts b/src/version.ts index 91acebf..270a9db 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '4.0.0' +export const version = '4.0.1'