fix: All images are the first image in single msg

fix: remote rkey
This commit is contained in:
linyuchen 2024-05-11 14:52:59 +08:00
parent fdf96b479c
commit 9b3916307a
6 changed files with 129 additions and 127 deletions

View File

@ -46,8 +46,8 @@ let config = {
{ src: './manifest.json', dest: 'dist' }, { src: './manifest.json', dest: 'dist' },
{ src: './icon.jpg', dest: 'dist' }, { src: './icon.jpg', dest: 'dist' },
{ src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' }, { src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
{ src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' }, // { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
{ src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' }, // { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
], ],
}), }),
], ],

View File

@ -1,10 +1,10 @@
{ {
"manifest_version": 4, "manifest_version": 4,
"type": "extension", "type": "extension",
"name": "LLOneBot v3.24.2", "name": "LLOneBot v3.24.3",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新", "description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
"version": "3.24.2", "version": "3.24.3",
"icon": "./icon.jpg", "icon": "./icon.jpg",
"authors": [ "authors": [
{ {
@ -20,7 +20,11 @@
"name": "LLOneBot.zip" "name": "LLOneBot.zip"
} }
}, },
"platform": ["win32", "linux", "darwin"], "platform": [
"win32",
"linux",
"darwin"
],
"injects": { "injects": {
"renderer": "./renderer/index.js", "renderer": "./renderer/index.js",
"main": "./main/main.cjs", "main": "./main/main.cjs",

View File

@ -9,22 +9,14 @@ import {
ChatType, ChatType,
ElementType, ElementType,
IMAGE_HTTP_HOST, IMAGE_HTTP_HOST,
IMAGE_HTTP_HOST_NT, IMAGE_HTTP_HOST_NT, PicElement,
RawMessage, RawMessage,
} from '../types' } from '../types'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { log } from '@/common/utils' import { log } from '@/common/utils'
import https from 'https' import { rkeyManager } from '@/ntqqapi/api/rkey'
import { sleep } from '@/common/utils'
import { hookApi } from '../native/moehook/hook'
let privateImageRKey = ''
let groupImageRKey = ''
let lastGetPrivateRKeyTime = 0
let lastGetGroupRKeyTime = 0
const rkeyExpireTime = 1000 * 60 * 30
export class NTQQFileApi { export class NTQQFileApi {
static async getFileType(filePath: string) { static async getFileType(filePath: string) {
@ -161,16 +153,12 @@ export class NTQQFileApi {
}) })
} }
static async getImageUrl(msg: RawMessage) { static async getImageUrl(picElement: PicElement, chatType: ChatType) {
const isPrivateImage = msg.chatType !== ChatType.group const isPrivateImage = chatType !== ChatType.group
const msgElement = msg.elements.find((e) => !!e.picElement) const url = picElement.originImageUrl // 没有域名
if (!msgElement) { const md5HexStr = picElement.md5HexStr
return '' const fileMd5 = picElement.md5HexStr
} const fileUuid = picElement.fileUuid
const url = msgElement.picElement.originImageUrl // 没有域名
const md5HexStr = msgElement.picElement.md5HexStr
const fileMd5 = msgElement.picElement.md5HexStr
const fileUuid = msgElement.picElement.fileUuid
if (url) { if (url) {
if (url.startsWith('/download')) { if (url.startsWith('/download')) {
// console.log('rkey', rkey); // console.log('rkey', rkey);
@ -178,81 +166,9 @@ export class NTQQFileApi {
return IMAGE_HTTP_HOST_NT + url return IMAGE_HTTP_HOST_NT + url
} }
if (!hookApi.isAvailable()) { const rkeyData = await rkeyManager.getRkey();
log('hookApi is not available') const existsRKey = isPrivateImage ? rkeyData.private_rkey : rkeyData.group_rkey;
return '' return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
}
const saveRKey = (rkey: string) => {
if (isPrivateImage) {
privateImageRKey = rkey
lastGetPrivateRKeyTime = Date.now()
} else {
groupImageRKey = rkey
lastGetGroupRKeyTime = Date.now()
}
}
const refreshRKey = async () => {
log('获取图片rkey...')
NTQQFileApi.downloadMedia(
msg.msgId,
msg.chatType,
msg.peerUid,
msgElement.elementId,
'',
msgElement.picElement.sourcePath,
false,
)
.then()
.catch(() => {})
await sleep(1000)
const _rkey = hookApi.getRKey()
if (_rkey) {
const imageUrl = IMAGE_HTTP_HOST_NT + url + _rkey
// 验证_rkey是否有效
try {
await new Promise((res, rej) => {
https
.get(imageUrl, (response) => {
if (response.statusCode !== 200) {
rej('图片rkey获取失败')
} else {
res(response)
}
})
.on('error', (e) => {
rej(e)
})
})
log('图片rkey获取成功', _rkey)
saveRKey(_rkey)
return _rkey
} catch (e) {
log('图片rkey有误', imageUrl)
}
}
}
const existsRKey = isPrivateImage ? privateImageRKey : groupImageRKey
const lastGetRKeyTime = isPrivateImage ? lastGetPrivateRKeyTime : lastGetGroupRKeyTime
if (Date.now() - lastGetRKeyTime > rkeyExpireTime) {
// rkey过期
const newRKey = await refreshRKey()
if (newRKey) {
return IMAGE_HTTP_HOST_NT + url + `${newRKey}`
} else {
log('图片rkey获取失败', url)
if (existsRKey) {
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
}
return ''
}
}
// 使用未过期的rkey
if (existsRKey) {
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
}
} else { } else {
// 老的图片url不需要rkey // 老的图片url不需要rkey
return IMAGE_HTTP_HOST + url return IMAGE_HTTP_HOST + url
@ -261,7 +177,7 @@ export class NTQQFileApi {
// 没有url需要自己拼接 // 没有url需要自己拼接
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0` return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
} }
log('图片url获取失败', msg) log('图片url获取失败', picElement)
return '' return ''
} }
} }

59
src/ntqqapi/api/rkey.ts Normal file
View File

@ -0,0 +1,59 @@
//远端rkey获取
import { log } from '@/common/utils'
interface ServerRkeyData{
group_rkey: string;
private_rkey: string;
expired_time: number;
}
class RkeyManager {
serverUrl: string = '';
private rkeyData: ServerRkeyData = {
group_rkey: '',
private_rkey: '',
expired_time: 0
};
constructor(serverUrl: string) {
this.serverUrl = serverUrl;
}
async getRkey(){
if (this.isExpired()) {
try {
await this.refreshRkey();
} catch (e) {
log('获取rkey失败', e);
}
}
return this.rkeyData;
}
isExpired(): boolean {
const now = new Date().getTime() / 1000;
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`);
return now > this.rkeyData.expired_time;
}
async refreshRkey(): Promise<any> {
//刷新rkey
this.rkeyData = await this.fetchServerRkey();
}
async fetchServerRkey(){
return new Promise<ServerRkeyData>((resolve, reject) => {
fetch(this.serverUrl)
.then(response => {
if (!response.ok) {
return reject(response.statusText); // 请求失败,返回错误信息
}
return response.json(); // 解析 JSON 格式的响应体
})
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
}
export const rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey');

View File

@ -87,13 +87,15 @@ export class OB11Constructor {
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role) resMsg.sender.role = OB11Constructor.groupMemberRole(member.role)
resMsg.sender.nickname = member.nick resMsg.sender.nickname = member.nick
} }
} else if (msg.chatType == ChatType.friend) { }
else if (msg.chatType == ChatType.friend) {
resMsg.sub_type = 'friend' resMsg.sub_type = 'friend'
const friend = await getFriend(msg.senderUin) const friend = await getFriend(msg.senderUin)
if (friend) { if (friend) {
resMsg.sender.nickname = friend.nick resMsg.sender.nickname = friend.nick
} }
} else if (msg.chatType == ChatType.temp) { }
else if (msg.chatType == ChatType.temp) {
resMsg.sub_type = 'group' resMsg.sub_type = 'group'
const tempGroupCode = tempGroupCodeMap[msg.peerUin] const tempGroupCode = tempGroupCodeMap[msg.peerUin]
if (tempGroupCode) { if (tempGroupCode) {
@ -111,7 +113,8 @@ export class OB11Constructor {
if (element.textElement.atType == AtType.atAll) { if (element.textElement.atType == AtType.atAll) {
// message_data["data"]["mention"] = "all" // message_data["data"]["mention"] = "all"
message_data['data']['qq'] = 'all' message_data['data']['qq'] = 'all'
} else { }
else {
let atUid = element.textElement.atNtUid let atUid = element.textElement.atNtUid
let atQQ = element.textElement.atUid let atQQ = element.textElement.atUid
if (!atQQ || atQQ === '0') { if (!atQQ || atQQ === '0') {
@ -125,14 +128,16 @@ export class OB11Constructor {
message_data['data']['qq'] = atQQ message_data['data']['qq'] = atQQ
} }
} }
} else if (element.textElement) { }
else if (element.textElement) {
message_data['type'] = 'text' message_data['type'] = 'text'
let text = element.textElement.content let text = element.textElement.content
if (!text.trim()) { if (!text.trim()) {
continue continue
} }
message_data['data']['text'] = text message_data['data']['text'] = text
} else if (element.replyElement) { }
else if (element.replyElement) {
message_data['type'] = 'reply' message_data['type'] = 'reply'
// log("收到回复消息", element.replyElement.replayMsgSeq) // log("收到回复消息", element.replyElement.replayMsgSeq)
try { try {
@ -140,20 +145,22 @@ export class OB11Constructor {
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId) // log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
if (replyMsg) { if (replyMsg) {
message_data['data']['id'] = replyMsg.msgShortId.toString() message_data['data']['id'] = replyMsg.msgShortId.toString()
} else { }
else {
continue continue
} }
} catch (e) { } catch (e) {
log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq) log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq)
} }
} else if (element.picElement) { }
else if (element.picElement) {
message_data['type'] = 'image' message_data['type'] = 'image'
// message_data["data"]["file"] = element.picElement.sourcePath // message_data["data"]["file"] = element.picElement.sourcePath
message_data['data']['file'] = element.picElement.fileName message_data['data']['file'] = element.picElement.fileName
// message_data["data"]["path"] = element.picElement.sourcePath // message_data["data"]["path"] = element.picElement.sourcePath
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64" // let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
message_data['data']['url'] = await NTQQFileApi.getImageUrl(msg) message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType);
// message_data["data"]["file_id"] = element.picElement.fileUuid // message_data["data"]["file_id"] = element.picElement.fileUuid
message_data['data']['file_size'] = element.picElement.fileSize message_data['data']['file_size'] = element.picElement.fileSize
dbUtil dbUtil
@ -175,7 +182,8 @@ export class OB11Constructor {
}) })
.then() .then()
// 不在自动下载图片 // 不在自动下载图片
} else if (element.videoElement || element.fileElement) { }
else if (element.videoElement || element.fileElement) {
const videoOrFileElement = element.videoElement || element.fileElement const videoOrFileElement = element.videoElement || element.fileElement
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
message_data['type'] = ob11MessageDataType message_data['type'] = ob11MessageDataType
@ -204,7 +212,8 @@ export class OB11Constructor {
}) })
.then() .then()
// 怎么拿到url呢 // 怎么拿到url呢
} else if (element.pttElement) { }
else if (element.pttElement) {
message_data['type'] = OB11MessageDataType.voice message_data['type'] = OB11MessageDataType.voice
message_data['data']['file'] = element.pttElement.fileName message_data['data']['file'] = element.pttElement.fileName
message_data['data']['path'] = element.pttElement.filePath message_data['data']['path'] = element.pttElement.filePath
@ -224,22 +233,27 @@ export class OB11Constructor {
// }).catch(err => { // }).catch(err => {
// console.log("语音转文字失败", err); // console.log("语音转文字失败", err);
// }) // })
} else if (element.arkElement) { }
else if (element.arkElement) {
message_data['type'] = OB11MessageDataType.json message_data['type'] = OB11MessageDataType.json
message_data['data']['data'] = element.arkElement.bytesData message_data['data']['data'] = element.arkElement.bytesData
} else if (element.faceElement) { }
else if (element.faceElement) {
const faceId = element.faceElement.faceIndex const faceId = element.faceElement.faceIndex
if (faceId === FaceIndex.dice) { if (faceId === FaceIndex.dice) {
message_data['type'] = OB11MessageDataType.dice message_data['type'] = OB11MessageDataType.dice
message_data['data']['result'] = element.faceElement.resultId message_data['data']['result'] = element.faceElement.resultId
} else if (faceId === FaceIndex.RPS) { }
else if (faceId === FaceIndex.RPS) {
message_data['type'] = OB11MessageDataType.RPS message_data['type'] = OB11MessageDataType.RPS
message_data['data']['result'] = element.faceElement.resultId message_data['data']['result'] = element.faceElement.resultId
} else { }
else {
message_data['type'] = OB11MessageDataType.face message_data['type'] = OB11MessageDataType.face
message_data['data']['id'] = element.faceElement.faceIndex.toString() message_data['data']['id'] = element.faceElement.faceIndex.toString()
} }
} else if (element.marketFaceElement) { }
else if (element.marketFaceElement) {
message_data['type'] = OB11MessageDataType.mface message_data['type'] = OB11MessageDataType.mface
message_data['data']['summary'] = element.marketFaceElement.faceName message_data['data']['summary'] = element.marketFaceElement.faceName
const md5 = element.marketFaceElement.emojiId const md5 = element.marketFaceElement.emojiId
@ -253,10 +267,12 @@ export class OB11Constructor {
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId) message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId)
message_data['data']['key'] = element.marketFaceElement.key message_data['data']['key'] = element.marketFaceElement.key
mFaceCache.set(md5, element.marketFaceElement.faceName) mFaceCache.set(md5, element.marketFaceElement.faceName)
} else if (element.markdownElement) { }
else if (element.markdownElement) {
message_data['type'] = OB11MessageDataType.markdown message_data['type'] = OB11MessageDataType.markdown
message_data['data']['data'] = element.markdownElement.content message_data['data']['data'] = element.markdownElement.content
} else if (element.multiForwardMsgElement) { }
else if (element.multiForwardMsgElement) {
message_data['type'] = OB11MessageDataType.forward message_data['type'] = OB11MessageDataType.forward
message_data['data']['id'] = msg.msgId message_data['data']['id'] = msg.msgId
} }
@ -264,7 +280,8 @@ export class OB11Constructor {
const cqCode = encodeCQCode(message_data) const cqCode = encodeCQCode(message_data)
if (messagePostFormat === 'string') { if (messagePostFormat === 'string') {
;(resMsg.message as string) += cqCode ;(resMsg.message as string) += cqCode
} else (resMsg.message as OB11MessageData[]).push(message_data) }
else (resMsg.message as OB11MessageData[]).push(message_data)
resMsg.raw_message += cqCode resMsg.raw_message += cqCode
} }
@ -313,7 +330,8 @@ export class OB11Constructor {
// log("构造群增加事件", event) // log("构造群增加事件", event)
return event return event
} }
} else if (groupElement.type === TipGroupElementType.ban) { }
else if (groupElement.type === TipGroupElementType.ban) {
log('收到群群员禁言提示', groupElement) log('收到群群员禁言提示', groupElement)
const memberUid = groupElement.shutUp.member.uid const memberUid = groupElement.shutUp.member.uid
const adminUid = groupElement.shutUp.admin.uid const adminUid = groupElement.shutUp.admin.uid
@ -324,7 +342,8 @@ export class OB11Constructor {
memberUin = memberUin =
(await getGroupMember(msg.peerUid, memberUid))?.uin || (await getGroupMember(msg.peerUid, memberUid))?.uin ||
(await NTQQUserApi.getUserDetailInfo(memberUid))?.uin (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
} else { }
else {
memberUin = '0' // 0表示全员禁言 memberUin = '0' // 0表示全员禁言
if (duration > 0) { if (duration > 0) {
duration = -1 duration = -1
@ -341,7 +360,8 @@ export class OB11Constructor {
sub_type, sub_type,
) )
} }
} else if (groupElement.type == TipGroupElementType.kicked) { }
else if (groupElement.type == TipGroupElementType.kicked) {
log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement) log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
deleteGroup(msg.peerUid) deleteGroup(msg.peerUid)
NTQQGroupApi.quitGroup(msg.peerUid).then() NTQQGroupApi.quitGroup(msg.peerUid).then()
@ -361,7 +381,8 @@ export class OB11Constructor {
return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave') return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave')
} }
} }
} else if (element.fileElement) { }
else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), { return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
id: element.fileElement.fileUuid, id: element.fileElement.fileUuid,
name: element.fileElement.fileName, name: element.fileElement.fileName,
@ -426,7 +447,8 @@ export class OB11Constructor {
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite') return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite')
} }
} }
} else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) { }
else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr) const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
/* /*
{ {
@ -495,7 +517,8 @@ export class OB11Constructor {
parseInt(operator.uin), parseInt(operator.uin),
msg.msgShortId, msg.msgShortId,
) )
} else { }
else {
return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin), msg.msgShortId) return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin), msg.msgShortId)
} }
} }

View File

@ -1 +1 @@
export const version = '3.24.2' export const version = '3.24.3'