mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall'
|
||
import { ChatType, RawMessage, SendMessageElement, Peer } from '../types'
|
||
import { dbUtil } from '../../common/db'
|
||
import { selfInfo } from '../../common/data'
|
||
import { ReceiveCmdS, registerReceiveHook } from '../hook'
|
||
import { log } from '../../common/utils/log'
|
||
import { sleep } from '../../common/utils/helper'
|
||
import { isQQ998, getBuildVersion } from '../../common/utils'
|
||
import { getSession } from '@/ntqqapi/wrapper'
|
||
import { NTEventDispatch } from '@/common/utils/EventTask'
|
||
|
||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunc
|
||
|
||
export let sentMessages: Record<string, RawMessage> = {} // msgId: RawMessage
|
||
|
||
async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 10000) {
|
||
// 等待上一个相同的peer发送完
|
||
const peerUid = peer.peerUid
|
||
let checkLastSendUsingTime = 0
|
||
const waitLastSend = async () => {
|
||
if (checkLastSendUsingTime > timeout) {
|
||
throw '发送超时'
|
||
}
|
||
let lastSending = sendMessagePool[peer.peerUid]
|
||
if (lastSending) {
|
||
// log("有正在发送的消息,等待中...")
|
||
await sleep(500)
|
||
checkLastSendUsingTime += 500
|
||
return await waitLastSend()
|
||
}
|
||
else {
|
||
return
|
||
}
|
||
}
|
||
await waitLastSend()
|
||
|
||
let sentMessage: RawMessage | null = null
|
||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||
delete sendMessagePool[peerUid]
|
||
sentMessage = rawMessage
|
||
sentMessages[rawMessage.msgId] = rawMessage
|
||
}
|
||
|
||
let checkSendCompleteUsingTime = 0
|
||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||
if (sentMessage) {
|
||
if (waitComplete) {
|
||
if (sentMessage.sendStatus == 2) {
|
||
delete sentMessages[sentMessage.msgId]
|
||
return sentMessage
|
||
}
|
||
}
|
||
else {
|
||
delete sentMessages[sentMessage.msgId]
|
||
return sentMessage
|
||
}
|
||
// log(`给${peerUid}发送消息成功`)
|
||
}
|
||
checkSendCompleteUsingTime += 500
|
||
if (checkSendCompleteUsingTime > timeout) {
|
||
throw '发送超时'
|
||
}
|
||
await sleep(500)
|
||
return await checkSendComplete()
|
||
}
|
||
return checkSendComplete()
|
||
}
|
||
|
||
export class NTQQMsgApi {
|
||
static enterOrExitAIO(peer: Peer, enter: boolean) {
|
||
return callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.ENTER_OR_EXIT_AIO,
|
||
args: [
|
||
{
|
||
"info_list": [
|
||
{
|
||
peer,
|
||
"option": enter ? 1 : 2
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"send": true
|
||
},
|
||
],
|
||
})
|
||
}
|
||
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||
emojiId = emojiId.toString()
|
||
return await callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.EMOJI_LIKE,
|
||
args: [
|
||
{
|
||
peer,
|
||
msgSeq,
|
||
emojiId,
|
||
emojiType: emojiId.length > 3 ? '2' : '1',
|
||
setEmoji: set,
|
||
},
|
||
null,
|
||
],
|
||
})
|
||
}
|
||
|
||
static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
|
||
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
||
methodName: NTQQApiMethod.GET_MULTI_MSG,
|
||
args: [
|
||
{
|
||
peer,
|
||
rootMsgId,
|
||
parentMsgId,
|
||
},
|
||
null,
|
||
],
|
||
})
|
||
}
|
||
|
||
static async getMsgBoxInfo(peer: Peer) {
|
||
return await callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.GET_MSG_BOX_INFO,
|
||
args: [
|
||
{
|
||
contacts: [
|
||
peer
|
||
],
|
||
},
|
||
null,
|
||
],
|
||
})
|
||
}
|
||
|
||
static async activateChat(peer: Peer) {
|
||
// await this.fetchRecentContact();
|
||
// await sleep(500);
|
||
return await callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.ACTIVE_CHAT_PREVIEW,
|
||
args: [{ peer, cnt: 20 }, null],
|
||
})
|
||
}
|
||
|
||
static async activateChatAndGetHistory(peer: Peer) {
|
||
// await this.fetchRecentContact();
|
||
// await sleep(500);
|
||
return await callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.ACTIVE_CHAT_HISTORY,
|
||
// 参数似乎不是这样
|
||
args: [{ peer, cnt: 20 }, null],
|
||
})
|
||
}
|
||
|
||
static async getMsgHistory(peer: Peer, msgId: string, count: number) {
|
||
// 消息时间从旧到新
|
||
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
|
||
methodName: isQQ998 ? NTQQApiMethod.ACTIVE_CHAT_HISTORY : NTQQApiMethod.HISTORY_MSG,
|
||
args: [
|
||
{
|
||
peer,
|
||
msgId,
|
||
cnt: count,
|
||
queryOrder: true,
|
||
},
|
||
null,
|
||
],
|
||
})
|
||
}
|
||
|
||
static async fetchRecentContact() {
|
||
await callNTQQApi({
|
||
methodName: NTQQApiMethod.RECENT_CONTACT,
|
||
args: [
|
||
{
|
||
fetchParam: {
|
||
anchorPointContact: {
|
||
contactId: '',
|
||
sortField: '',
|
||
pos: 0,
|
||
},
|
||
relativeMoveCount: 0,
|
||
listType: 2, // 1普通消息,2群助手内的消息
|
||
count: 200,
|
||
fetchOld: true,
|
||
},
|
||
},
|
||
],
|
||
})
|
||
}
|
||
|
||
static async recallMsg(peer: Peer, msgIds: string[]) {
|
||
return await callNTQQApi({
|
||
methodName: NTQQApiMethod.RECALL_MSG,
|
||
args: [
|
||
{
|
||
peer,
|
||
msgIds,
|
||
},
|
||
null,
|
||
],
|
||
})
|
||
}
|
||
|
||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||
if (getBuildVersion() >= 26702) {
|
||
return NTQQMsgApi.sendMsgV2(peer, msgElements, waitComplete, timeout)
|
||
}
|
||
const waiter = sendWaiter(peer, waitComplete, timeout)
|
||
callNTQQApi({
|
||
methodName: NTQQApiMethod.SEND_MSG,
|
||
args: [
|
||
{
|
||
msgId: '0',
|
||
peer,
|
||
msgElements,
|
||
msgAttributeInfos: new Map(),
|
||
},
|
||
null,
|
||
],
|
||
}).then()
|
||
return await waiter
|
||
}
|
||
|
||
static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||
if (peer.chatType === ChatType.temp) {
|
||
//await NTQQMsgApi.PrepareTempChat().then().catch()
|
||
}
|
||
function generateMsgId() {
|
||
const timestamp = Math.floor(Date.now() / 1000)
|
||
const random = Math.floor(Math.random() * Math.pow(2, 32))
|
||
const buffer = Buffer.alloc(8)
|
||
buffer.writeUInt32BE(timestamp, 0)
|
||
buffer.writeUInt32BE(random, 4)
|
||
const msgId = BigInt('0x' + buffer.toString('hex')).toString()
|
||
return msgId
|
||
}
|
||
// 此处有采用Hack方法 利用数据返回正确得到对应消息
|
||
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
|
||
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
|
||
let msgId: string
|
||
try {
|
||
msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime())
|
||
} catch (error) {
|
||
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
|
||
//兜底识别策略V2
|
||
msgId = generateMsgId().toString()
|
||
}
|
||
let data = await NTEventDispatch.CallNormalEvent<
|
||
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
|
||
(msgList: RawMessage[]) => void
|
||
>(
|
||
'NodeIKernelMsgService/sendMsg',
|
||
'NodeIKernelMsgListener/onMsgInfoListUpdate',
|
||
1,
|
||
timeout,
|
||
(msgRecords: RawMessage[]) => {
|
||
for (let msgRecord of msgRecords) {
|
||
if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
},
|
||
msgId,
|
||
peer,
|
||
msgElements,
|
||
new Map()
|
||
)
|
||
const retMsg = data[1].find(msgRecord => {
|
||
if (msgRecord.msgId === msgId) {
|
||
return true
|
||
}
|
||
})
|
||
return retMsg!
|
||
}
|
||
|
||
static async getMsgUnique(chatType: number, time: string) {
|
||
const session = getSession()
|
||
if (getBuildVersion() >= 26702) {
|
||
return session?.getMsgService().generateMsgUniqueId(chatType, time)!
|
||
}
|
||
return session?.getMsgService().getMsgUniqueId(time)!
|
||
}
|
||
|
||
static async getServerTime() {
|
||
const session = getSession()
|
||
return session?.getMSFService().getServerTime()!
|
||
}
|
||
|
||
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||
const waiter = sendWaiter(destPeer, true, 10000)
|
||
callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.FORWARD_MSG,
|
||
args: [
|
||
{
|
||
msgIds: msgIds,
|
||
srcContact: srcPeer,
|
||
dstContacts: [destPeer],
|
||
commentElements: [],
|
||
msgAttributeInfos: new Map(),
|
||
},
|
||
null,
|
||
],
|
||
}).then().catch(log)
|
||
return await waiter
|
||
}
|
||
|
||
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||
const msgInfos = msgIds.map((id) => {
|
||
return { msgId: id, senderShowName: selfInfo.nick }
|
||
})
|
||
const apiArgs = [
|
||
{
|
||
msgInfos,
|
||
srcContact: srcPeer,
|
||
dstContact: destPeer,
|
||
commentElements: [],
|
||
msgAttributeInfos: new Map(),
|
||
},
|
||
null,
|
||
]
|
||
return await new Promise<RawMessage>((resolve, reject) => {
|
||
let complete = false
|
||
setTimeout(() => {
|
||
if (!complete) {
|
||
reject('转发消息超时')
|
||
}
|
||
}, 5000)
|
||
registerReceiveHook(ReceiveCmdS.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => {
|
||
const msg = payload.msgRecord
|
||
// 需要判断它是转发的消息,并且识别到是当前转发的这一条
|
||
const arkElement = msg.elements.find((ele) => ele.arkElement)
|
||
if (!arkElement) {
|
||
// log("收到的不是转发消息")
|
||
return
|
||
}
|
||
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData)
|
||
if (forwardData.app != 'com.tencent.multimsg') {
|
||
return
|
||
}
|
||
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) {
|
||
complete = true
|
||
await dbUtil.addMsg(msg)
|
||
resolve(msg)
|
||
log('转发消息成功:', payload)
|
||
}
|
||
})
|
||
callNTQQApi<GeneralCallResult>({
|
||
methodName: NTQQApiMethod.MULTI_FORWARD_MSG,
|
||
args: apiArgs,
|
||
}).then((result) => {
|
||
log('转发消息结果:', result, apiArgs)
|
||
if (result.result !== 0) {
|
||
complete = true
|
||
reject('转发消息失败,' + JSON.stringify(result))
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||
const session = getSession()
|
||
return await session?.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z)!
|
||
}
|
||
}
|