From ecee497ec6178dc3151d969f7703faba7374fc00 Mon Sep 17 00:00:00 2001 From: StageGuard <1355416608@qq.com> Date: Fri, 1 Sep 2023 10:40:46 +0800 Subject: [PATCH] [core] initial support for decoding FriendFileMessage, sending friend file --- .../kotlin/contact/FileSupported.kt | 21 +- .../src/commonMain/kotlin/contact/Friend.kt | 2 +- .../kotlin/contact/file/AbsoluteFile.kt | 2 + .../kotlin/message/code/internal/impl.kt | 2 +- .../src/internal/contact/MockFriendImpl.kt | 31 ++- .../src/internal/contact/MockGroupImpl.kt | 9 + .../absolutefile/MockAbsoluteFile.kt | 4 +- .../remotefile/remotefile/MockRemoteFile.kt | 6 +- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 2 +- .../commonMain/kotlin/contact/FriendImpl.kt | 109 ++++++++ .../commonMain/kotlin/contact/GroupImpl.kt | 11 + .../kotlin/contact/file/AbsoluteFolderImpl.kt | 17 +- .../contact/file/AbsoluteFriendFileImpl.kt | 115 ++++++++ ...teFileImpl.kt => AbsoluteGroupFileImpl.kt} | 10 +- .../file/AbstractAbsoluteFileFolder.kt | 2 +- .../kotlin/message/ReceiveMessageHandler.kt | 19 -- ...MessageImpl.kt => GroupFileMessageImpl.kt} | 47 +++- .../message/protocol/impl/AudioProtocol.kt | 24 ++ .../protocol/impl/FileMessageProtocol.kt | 96 ++++--- .../kotlin/network/highway/Highway.kt | 1 + .../notice/priv/PrivateMessageProcessor.kt | 36 ++- .../network/protocol/data/proto/Cmd0x346.kt | 36 +++ .../network/protocol/data/proto/Exciting.kt | 9 +- .../network/protocol/packet/PacketFactory.kt | 4 + .../packet/chat/OfflineFilleHandleSvr.kt | 247 ++++++++++++++++++ .../message/data/MessageSerializationTest.kt | 4 +- .../kotlin/utils/RemoteFileImpl.kt | 12 +- 27 files changed, 774 insertions(+), 104 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFriendFileImpl.kt rename mirai-core/src/commonMain/kotlin/contact/file/{AbsoluteFileImpl.kt => AbsoluteGroupFileImpl.kt} (94%) rename mirai-core/src/commonMain/kotlin/message/data/{FileMessageImpl.kt => GroupFileMessageImpl.kt} (67%) create mode 100644 mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/OfflineFilleHandleSvr.kt diff --git a/mirai-core-api/src/commonMain/kotlin/contact/FileSupported.kt b/mirai-core-api/src/commonMain/kotlin/contact/FileSupported.kt index 92454e87c3..70b8d7f939 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/FileSupported.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/FileSupported.kt @@ -10,12 +10,17 @@ package net.mamoe.mirai.contact +import net.mamoe.mirai.contact.file.AbsoluteFile +import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.contact.file.RemoteFiles +import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.DeprecatedSinceMirai +import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.NotStableForInheritance +import net.mamoe.mirai.utils.ProgressionCallback /** - * 支持文件操作的 [Contact]. 目前仅 [Group]. + * 支持文件操作的 [Contact]. 目前仅 [Group] 和 [Friend]. * * 获取文件操作相关示例: [RemoteFiles] * @@ -47,4 +52,18 @@ public interface FileSupported : Contact { * @since 2.8 */ public val files: RemoteFiles + + /** + * 上传一个文件到联系人,并返回文件消息. + * 对于群, 上传到群文件根目录. + * + * @param filename 文件名, 不可包含路径符(slash "/") + * @see AbsoluteFolder.uploadNewFile + * @since 2.17 + */ + public suspend fun uploadFile( + filename: String, + content: ExternalResource, + callback: ProgressionCallback? = null + ): FileMessage } \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt b/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt index ba1479fd0a..31432d41f9 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt @@ -37,7 +37,7 @@ import net.mamoe.mirai.utils.NotStableForInheritance */ @Suppress("RedundantSetter") @NotStableForInheritance -public interface Friend : User, CoroutineScope, AudioSupported, RoamingSupported { +public interface Friend : User, CoroutineScope, AudioSupported, RoamingSupported, FileSupported { /** * 该好友所在的好友分组 diff --git a/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFile.kt b/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFile.kt index 5d0aa61611..05141f0119 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFile.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFile.kt @@ -53,6 +53,8 @@ public interface AbsoluteFile : AbsoluteFileFolder { * * 注意该操作有可能产生同名文件或目录 (当 [folder] 中已经存在一个名称为 [name] 的文件或目录时). * + * 对于好友文件, 此方法不会产生任何效果. + * * @throws IOException 当发生网络错误时可能抛出 * @throws IllegalStateException 当发生已知的协议错误时抛出 * @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传) diff --git a/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt b/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt index 862d5348b6..388f3ddfe1 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt @@ -137,7 +137,7 @@ private object MiraiCodeParsers : AbstractMap(), Map - FileMessage(id, internalId.toInt(), name, size.toLong()) + FileMessage(id, internalId.toInt(), name, size.toLong()) // TODO: parser for friend file }, ) diff --git a/mirai-core-mock/src/internal/contact/MockFriendImpl.kt b/mirai-core-mock/src/internal/contact/MockFriendImpl.kt index 5f0f022f7c..4d76436e4d 100644 --- a/mirai-core-mock/src/internal/contact/MockFriendImpl.kt +++ b/mirai-core-mock/src/internal/contact/MockFriendImpl.kt @@ -7,7 +7,12 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") +@file:Suppress( + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE", + "CANNOT_OVERRIDE_INVISIBLE_MEMBER", + "DEPRECATION_ERROR" +) package net.mamoe.mirai.mock.internal.contact @@ -15,15 +20,14 @@ import kotlinx.coroutines.cancel import net.mamoe.mirai.contact.AvatarSpec import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.OtherClient +import net.mamoe.mirai.contact.file.AbsoluteFile +import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.OfflineAudio -import net.mamoe.mirai.message.data.OnlineMessageSource +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockFriend import net.mamoe.mirai.mock.internal.contact.friendfroup.MockFriendGroups @@ -34,6 +38,8 @@ import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcToFriend import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.mock.utils.broadcastBlocking import net.mamoe.mirai.utils.ExternalResource +import net.mamoe.mirai.utils.ProgressionCallback +import net.mamoe.mirai.utils.RemoteFile import net.mamoe.mirai.utils.cast import java.util.concurrent.CancellationException import kotlin.coroutines.CoroutineContext @@ -92,6 +98,21 @@ internal class MockFriendImpl( FriendRemarkChangeEvent(this, ov, value).broadcastBlocking() } + @Deprecated("Please use files instead.", replaceWith = ReplaceWith("files.root"), level = DeprecationLevel.ERROR) + override val filesRoot: RemoteFile + get() = throw UnsupportedOperationException("file system is not supported by MockFriend, please use uploadFile instead.") + + override val files: RemoteFiles + get() = throw UnsupportedOperationException("file system is not supported by MockFriend, please use uploadFile instead.") + + override suspend fun uploadFile( + filename: String, + content: ExternalResource, + callback: ProgressionCallback? + ): FileMessage { + TODO("Not yet implemented") + } + override fun newMessagePreSend(message: Message): MessagePreSendEvent { return FriendMessagePreSendEvent(this, message) } diff --git a/mirai-core-mock/src/internal/contact/MockGroupImpl.kt b/mirai-core-mock/src/internal/contact/MockGroupImpl.kt index b0be49d219..a9ad189db4 100644 --- a/mirai-core-mock/src/internal/contact/MockGroupImpl.kt +++ b/mirai-core-mock/src/internal/contact/MockGroupImpl.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.runBlocking import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.announcement.OfflineAnnouncement import net.mamoe.mirai.contact.announcement.buildAnnouncementParameters +import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.data.GroupHonorType @@ -350,6 +351,14 @@ internal class MockGroupImpl( net.mamoe.mirai.mock.internal.remotefile.absolutefile.MockRemoteFiles(this, txFileSystem) } + override suspend fun uploadFile( + filename: String, + content: ExternalResource, + callback: ProgressionCallback? + ): FileMessage { + TODO("uploadFile of MockGroupImpl") + } + override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.mockUploadAudio(bot) diff --git a/mirai-core-mock/src/internal/remotefile/absolutefile/MockAbsoluteFile.kt b/mirai-core-mock/src/internal/remotefile/absolutefile/MockAbsoluteFile.kt index 2f28d013a8..64059f48c6 100644 --- a/mirai-core-mock/src/internal/remotefile/absolutefile/MockAbsoluteFile.kt +++ b/mirai-core-mock/src/internal/remotefile/absolutefile/MockAbsoluteFile.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.firstOrNull import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFolder -import net.mamoe.mirai.internal.message.data.FileMessageImpl +import net.mamoe.mirai.internal.message.data.GroupFileMessageImpl import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.mock.internal.remotefile.remotefile.MockRemoteFile import net.mamoe.mirai.mock.resserver.MockServerRemoteFile @@ -55,7 +55,7 @@ internal class MockAbsoluteFile( override fun toMessage(): FileMessage { //todo busId - return FileMessageImpl(id, 0, name, size) + return GroupFileMessageImpl(id, 0, name, size) } override suspend fun refreshed(): AbsoluteFile? = diff --git a/mirai-core-mock/src/internal/remotefile/remotefile/MockRemoteFile.kt b/mirai-core-mock/src/internal/remotefile/remotefile/MockRemoteFile.kt index 9d5a96142b..ff6316e40a 100644 --- a/mirai-core-mock/src/internal/remotefile/remotefile/MockRemoteFile.kt +++ b/mirai-core-mock/src/internal/remotefile/remotefile/MockRemoteFile.kt @@ -17,7 +17,7 @@ import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.isOperator -import net.mamoe.mirai.internal.message.data.FileMessageImpl +import net.mamoe.mirai.internal.message.data.GroupFileMessageImpl import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.mock.contact.MockGroup @@ -284,7 +284,7 @@ internal class MockRemoteFile( override suspend fun toMessage(): FileMessage? { val resolved = resolveFile() ?: return null - return FileMessageImpl( + return GroupFileMessageImpl( name = resolved.name, id = resolved.id, size = resolved.size, @@ -308,7 +308,7 @@ internal class MockRemoteFile( val rsp = parent.uploadFile(this.name, resource, contact.bot.id) callback?.onProgression(this, resource, rsSize) callback?.onSuccess(this, resource) - return FileMessageImpl( + return GroupFileMessageImpl( name = rsp.name, id = rsp.id, size = rsp.size, diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index ede668b20a..260213fde6 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -621,7 +621,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { } override fun createFileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage { - return FileMessageImpl(id, internalId, name, size) + return GroupFileMessageImpl(id, internalId, name, size) } override fun createUnsupportedMessage(struct: ByteArray): UnsupportedMessage = diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index 99d097858d..71c7692cd5 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -18,6 +18,8 @@ import io.ktor.utils.io.core.* import kotlinx.coroutines.launch import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.file.AbsoluteFile +import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.broadcast @@ -25,15 +27,25 @@ import net.mamoe.mirai.event.events.FriendMessagePostSendEvent import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.event.events.FriendRemarkChangeEvent import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.file.AbsoluteFriendFileImpl import net.mamoe.mirai.internal.contact.info.FriendInfoImpl import net.mamoe.mirai.internal.contact.roaming.RoamingMessagesImplFriend import net.mamoe.mirai.internal.message.data.OfflineAudioImpl +import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.message.protocol.outgoing.FriendMessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.highway.* +import net.mamoe.mirai.internal.network.protocol import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346 +import net.mamoe.mirai.internal.network.protocol.data.proto.ExcitingBusiInfo +import net.mamoe.mirai.internal.network.protocol.data.proto.ExcitingClientInfo +import net.mamoe.mirai.internal.network.protocol.data.proto.ExcitingFileEntry +import net.mamoe.mirai.internal.network.protocol.data.proto.ExcitingFileNameInfo +import net.mamoe.mirai.internal.network.protocol.data.proto.FileUploadEntry +import net.mamoe.mirai.internal.network.protocol.data.proto.FileUploadExt import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.internal.network.protocol.packet.chat.OfflineFilleHandleSvr import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList @@ -41,6 +53,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.summarycard.ChangeFriend import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.OfflineAudio import net.mamoe.mirai.spi.AudioToSilkService @@ -89,6 +102,102 @@ internal class FriendImpl( private val messageProtocolStrategy: MessageProtocolStrategy = FriendMessageProtocolStrategy(this) + override val files: RemoteFiles + get() = throw UnsupportedOperationException("file system is not supported by Friend, please use uploadFile instead.") + + @Suppress("DEPRECATION_ERROR") + @Deprecated("Please use files instead.", replaceWith = ReplaceWith("files.root"), level = DeprecationLevel.ERROR) + override val filesRoot: RemoteFile + get() = throw UnsupportedOperationException("file system is not supported by Friend, please use uploadFile instead.") + + override suspend fun uploadFile( + filename: String, + content: ExternalResource, + callback: ProgressionCallback? + ): FileMessage { + val md5 = content.md5 + val sha1 = content.sha1 + val size = content.size + + val appUpResp = bot.network.sendAndExpect( + OfflineFilleHandleSvr.ApplyUploadV3(bot.client, this, filename, size, md5, sha1) + ) + + if (appUpResp is OfflineFilleHandleSvr.ApplyUploadV3.Response.Failed) { + throw IllegalStateException(appUpResp.message) + } + + val fileUuid = when (appUpResp) { + is OfflineFilleHandleSvr.ApplyUploadV3.Response.FileExists -> appUpResp.fileUuid + is OfflineFilleHandleSvr.ApplyUploadV3.Response.RequireUpload -> appUpResp.fileUuid + else -> error("unreachable!") + } + val file = AbsoluteFriendFileImpl( + this, + fileUuid.decodeToString(), + filename, + bot.id, + 0, + content.size, + content.sha1, + content.md5 + ) + + if (appUpResp is OfflineFilleHandleSvr.ApplyUploadV3.Response.RequireUpload) { + val ext = FileUploadExt( + u1 = 100, + u2 = 2, + entry = FileUploadEntry( + business = ExcitingBusiInfo( + busId = 3, + senderUin = bot.uin, + receiverUin = uin, + groupCode = 0, + ), + fileEntry = ExcitingFileEntry( + fileSize = content.size, + md5 = content.md5, + sha1 = content.sha1, + fileId = appUpResp.fileUuid, + uploadKey = appUpResp.uploadKey, + ), + clientInfo = ExcitingClientInfo( + clientType = 2, + appId = bot.client.protocol.id.toString(), + terminalType = 2, + clientVer = "d92615c5", + unknown = 4, + ), + fileNameInfo = ExcitingFileNameInfo(filename) + ), + u3 = 0, + u200 = null + ) + + Highway.uploadResourceBdh( + bot = bot, + resource = content, + kind = ResourceKind.FRIEND_FILE, + commandId = 69, + extendInfo = ext.toByteArray(FileUploadExt.serializer()), + dataFlag = 0, + callback = if (callback == null) null else fun(it: Long) { + callback.onProgression(file, content, it) + } + ) + + callback?.onFinished(file, content, Result.success(content.size)) + } + + val upSuccResp = bot.network.sendAndExpect(OfflineFilleHandleSvr.UploadSucc(bot.client, this, fileUuid)) + if (upSuccResp is OfflineFilleHandleSvr.FileInfo.Failed) { + throw IllegalStateException(upSuccResp.message) + } + + sendMessage(AllowSendFileMessage + file.toMessage()) + return file.toMessage() + } + override suspend fun delete() { check(bot.friends[id] != null) { "Friend $id had already been deleted" diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 92e7c3b6c0..14c1d4e2bb 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.active.GroupActive import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.contact.essence.Essences +import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.data.GroupHonorType @@ -395,6 +396,16 @@ internal abstract class CommonGroupImpl constructor( override val roamingMessages: RoamingMessages by lazy { RoamingMessagesImplGroup(this) } + override suspend fun uploadFile( + filename: String, + content: ExternalResource, + callback: ProgressionCallback?, + ): FileMessage { + val tailFileName = filename.split('/').last().trim() + val absFile = files.uploadNewFile("/$tailFileName", content, callback) + return absFile.toMessage() + } + // 鉴于在 [essences] 中 有相同的功能的 Web API 所以此方法移除 // override suspend fun removeEssenceMessage(source: MessageSource): Boolean { // checkBotPermission(MemberPermission.ADMINISTRATOR) diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt index 39f45668e7..e1f9c1c2a4 100644 --- a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt @@ -59,7 +59,7 @@ internal fun CommonAbsoluteFolderImpl.createChildFolder( internal fun CommonAbsoluteFolderImpl.createChildFile( info: GroupFileCommon.FileInfo -): AbsoluteFileImpl = AbsoluteFileImpl( +): AbsoluteGroupFileImpl = AbsoluteGroupFileImpl( contact = contact, parent = this, id = info.fileId, @@ -74,12 +74,18 @@ internal fun CommonAbsoluteFolderImpl.createChildFile( busId = info.busId ) +/** + * only for group + */ internal expect class AbsoluteFolderImpl( contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String, uploadTime: Long, uploaderId: Long, lastModifiedTime: Long, contentsCount: Int, ) : CommonAbsoluteFolderImpl +/** + * only for group + */ internal abstract class CommonAbsoluteFolderImpl( contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String, uploadTime: Long, uploaderId: Long, lastModifiedTime: Long, @@ -160,7 +166,7 @@ internal abstract class CommonAbsoluteFolderImpl( -36 -> folder.throwPermissionDeniedException("uploadNewFile") } - val file = AbsoluteFileImpl( + val file = AbsoluteGroupFileImpl( contact = folder.contact, parent = folder, id = resp.fileId, @@ -187,10 +193,10 @@ internal abstract class CommonAbsoluteFolderImpl( return file } - val ext = GroupFileUploadExt( + val ext = FileUploadExt( u1 = 100, u2 = 1, - entry = GroupFileUploadEntry( + entry = FileUploadEntry( business = ExcitingBusiInfo( busId = resp.busId, senderUin = folder.bot.id, @@ -227,7 +233,8 @@ internal abstract class CommonAbsoluteFolderImpl( ), ), u3 = 0, - ).toByteArray(GroupFileUploadExt.serializer()) + u200 = 1 + ).toByteArray(FileUploadExt.serializer()) callback?.onBegin(file, content) diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFriendFileImpl.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFriendFileImpl.kt new file mode 100644 index 0000000000..c1331531fa --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFriendFileImpl.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2019-2023 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.contact.file + +import net.mamoe.mirai.contact.FileSupported +import net.mamoe.mirai.contact.file.AbsoluteFile +import net.mamoe.mirai.contact.file.AbsoluteFolder +import net.mamoe.mirai.internal.asQQAndroidBot +import net.mamoe.mirai.internal.message.data.FriendFileMessageImpl +import net.mamoe.mirai.internal.network.protocol.packet.chat.OfflineFilleHandleSvr +import net.mamoe.mirai.message.data.FileMessage +import net.mamoe.mirai.utils.currentTimeSeconds +import net.mamoe.mirai.utils.warning + +internal class AbsoluteFriendFileImpl( + override var contact: FileSupported, + override var id: String, + override var name: String, + override var uploaderId: Long, + + override var expiryTime: Long, + override val size: Long, + override val sha1: ByteArray, + override val md5: ByteArray, +) : AbsoluteFile { + private inline val bot get() = contact.bot.asQQAndroidBot() + private inline val client get() = bot.client + + override var parent: AbsoluteFolder? = null + + override val absolutePath: String + get() = "/" + + override val isFile: Boolean + get() = true + + override val isFolder: Boolean + get() = false + + override var lastModifiedTime: Long = 0 + + override suspend fun moveTo(folder: AbsoluteFolder): Boolean { + throw UnsupportedOperationException("AbsoluteFile.moveTo is not implemented in FriendFile.") + } + + override suspend fun getUrl(): String? { + val resp = bot.network.sendAndExpect(OfflineFilleHandleSvr.ApplyDownload(client, id.encodeToByteArray())) + return if (resp is OfflineFilleHandleSvr.ApplyDownload.Response.Success) { + resp.url + } else { + null + } + } + + override fun toMessage(): FileMessage { + return FriendFileMessageImpl(id, name, size, false) + } + + override suspend fun refreshed(): AbsoluteFile? { + val queryResp = bot.network.sendAndExpect(OfflineFilleHandleSvr.FileQuery(client, id.encodeToByteArray())) + + if (queryResp is OfflineFilleHandleSvr.FileInfo.Failed) { + contact.bot.logger.warning { "failed to query friend file info: ${queryResp.message}" } + return null + } + + val fileInfo = queryResp as OfflineFilleHandleSvr.FileInfo.Success + return AbsoluteFriendFileImpl( + contact, + fileInfo.fileUuid.decodeToString(), + fileInfo.filename, + fileInfo.ownerUin, + fileInfo.expiryTime, + fileInfo.fileSize, + fileInfo.fileMd5, + fileInfo.fileSha1, + ) + } + override suspend fun exists(): Boolean { + val queryResp = bot.network.sendAndExpect(OfflineFilleHandleSvr.FileQuery(client, id.encodeToByteArray())) + + if (queryResp is OfflineFilleHandleSvr.FileInfo.Failed) { + contact.bot.logger.warning { "failed to query friend file info: ${queryResp.message}" } + return false + } + + val fileInfo = queryResp as OfflineFilleHandleSvr.FileInfo.Success + return fileInfo.expiryTime >= currentTimeSeconds() + } + + override suspend fun renameTo(newName: String): Boolean { + throw UnsupportedOperationException("AbsoluteFile.renameTo is not implemented in FriendFile.") + } + + override suspend fun delete(): Boolean { + throw UnsupportedOperationException("AbsoluteFile.delete is not implemented in FriendFile.") + } + + override suspend fun refresh(): Boolean { + return refreshed() == null + } + + override fun toString(): String { + return "AbsoluteFriendFile(name=$name, id=$id)" + } + + override var uploadTime: Long = 0 +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteGroupFileImpl.kt similarity index 94% rename from mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt rename to mirai-core/src/commonMain/kotlin/contact/file/AbsoluteGroupFileImpl.kt index 0fa7e98b1c..5c00ce2c43 100644 --- a/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/file/AbsoluteGroupFileImpl.kt @@ -13,14 +13,14 @@ import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFolder -import net.mamoe.mirai.internal.message.data.FileMessageImpl +import net.mamoe.mirai.internal.message.data.GroupFileMessageImpl import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.isSameClass import net.mamoe.mirai.utils.toUHexString -internal class AbsoluteFileImpl( +internal class AbsoluteGroupFileImpl( contact: FileSupported, parent: AbsoluteFolder?, id: String, @@ -131,7 +131,7 @@ internal class AbsoluteFileImpl( } override fun toMessage(): FileMessage { - return FileMessageImpl(id, busId, name, size) + return GroupFileMessageImpl(id, busId, name, size) } override suspend fun refresh(): Boolean { @@ -143,7 +143,7 @@ internal class AbsoluteFileImpl( return true } - override fun toString(): String = "AbsoluteFile(name=$name, absolutePath=$absolutePath, id=$id)" + override fun toString(): String = "AbsoluteGroupFile(name=$name, absolutePath=$absolutePath, id=$id)" override suspend fun refreshed(): AbsoluteFile? { val result = bot.network.sendAndExpect(FileManagement.GetFileInfo(client, contact.id, id, busId)) @@ -160,7 +160,7 @@ internal class AbsoluteFileImpl( override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is AbsoluteFileImpl || !isSameClass(this, other)) return false + if (other !is AbsoluteGroupFileImpl || !isSameClass(this, other)) return false if (!super.equals(other)) return false if (expiryTime != other.expiryTime) return false diff --git a/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt b/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt index 6c1d9a4c84..5ca0002fff 100644 --- a/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt +++ b/mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt @@ -25,7 +25,7 @@ import net.mamoe.mirai.utils.isSameType internal fun AbstractAbsoluteFileFolder.api(): AbsoluteFileFolder = this.cast() internal fun AbsoluteFileFolder.impl(): AbstractAbsoluteFileFolder = this.cast() -internal fun AbsoluteFile.impl(): AbsoluteFileImpl = this.cast() +internal fun AbsoluteFile.impl(): AbsoluteGroupFileImpl = this.cast() internal fun AbsoluteFolder.impl(): AbsoluteFolderImpl = this.cast() internal val AbsoluteFolder?.idOrRoot get() = this?.id ?: AbsoluteFolder.ROOT_FOLDER_ID diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index 4ad63f9393..1435a207f5 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -14,9 +14,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements -import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio import net.mamoe.mirai.internal.message.data.LongMessageInternal -import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN @@ -24,9 +22,7 @@ import net.mamoe.mirai.internal.message.source.* import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.structureToString -import net.mamoe.mirai.utils.toLongUnsigned import net.mamoe.mirai.utils.warning /** @@ -138,7 +134,6 @@ private fun List.toMessageChainImpl( ): MessageChain { val messageList = this - val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size }) val source = if (onlineSource != null) { @@ -165,10 +160,6 @@ private fun List.toMessageChainImpl( ) } - for (msg in messageList) { - msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) } - } - return builder.build().cleanupRubbishMessageElements() } @@ -301,14 +292,4 @@ internal object ReceiveMessageTransformer { return builder.asMessageChain() } - - fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl( - filename = fileName.decodeToString(), - fileMd5 = fileMd5, - fileSize = fileSize.toLongUnsigned(), - codec = AudioCodec.fromId(format), - url = downPara.decodeToString(), - length = time.toLongUnsigned(), - originalPtt = this, - ) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/data/FileMessageImpl.kt b/mirai-core/src/commonMain/kotlin/message/data/GroupFileMessageImpl.kt similarity index 67% rename from mirai-core/src/commonMain/kotlin/message/data/FileMessageImpl.kt rename to mirai-core/src/commonMain/kotlin/message/data/GroupFileMessageImpl.kt index a420033243..49a051bc88 100644 --- a/mirai-core/src/commonMain/kotlin/message/data/FileMessageImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/data/GroupFileMessageImpl.kt @@ -24,20 +24,22 @@ import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.contact.file.* import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d8.GetFileListRspBody import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement +import net.mamoe.mirai.internal.network.protocol.packet.chat.OfflineFilleHandleSvr import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.cast +import net.mamoe.mirai.utils.warning import kotlin.contracts.contract -internal fun FileMessage.checkIsImpl(): FileMessageImpl { - contract { returns() implies (this@checkIsImpl is FileMessageImpl) } - return this as? FileMessageImpl ?: error("FileMessage must not be implemented manually.") +internal fun FileMessage.checkIsImpl(): GroupFileMessageImpl { + contract { returns() implies (this@checkIsImpl is GroupFileMessageImpl) } + return this as? GroupFileMessageImpl ?: error("FileMessage must not be implemented manually.") } @Serializable @Suppress("ANNOTATION_ARGUMENT_MUST_BE_CONST") // bug @SerialName(FileMessage.SERIAL_NAME) -internal data class FileMessageImpl( +internal data class GroupFileMessageImpl( override val id: String, @SerialName("internalId") val busId: Int, override val name: String, @@ -84,4 +86,41 @@ internal data class FileMessageImpl( } override fun toString(): String = "[mirai:file:$name, id=$id, internalId=$busId, size=$size]" +} + +@SerialName(FileMessage.SERIAL_NAME) +internal data class FriendFileMessageImpl( + override val id: String, + override val name: String, + override val size: Long, + @Transient val allowSend: Boolean = false, +) : FileMessage { + override val internalId: Int + get() = 0 + + override suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile? { + val queryResp = contact.bot.asQQAndroidBot().network + .sendAndExpect( + OfflineFilleHandleSvr.FileQuery(contact.bot.asQQAndroidBot().client, id.encodeToByteArray()) + ) + + if (queryResp is OfflineFilleHandleSvr.FileInfo.Failed) { + contact.bot.logger.warning { "failed to query friend file info: ${queryResp.message}" } + return null + } + + val fileInfo = queryResp as OfflineFilleHandleSvr.FileInfo.Success + return AbsoluteFriendFileImpl( + contact, + fileInfo.fileUuid.decodeToString(), + fileInfo.filename, + fileInfo.ownerUin, + fileInfo.expiryTime, + fileInfo.fileSize, + fileInfo.fileMd5, + fileInfo.fileSha1, + ) + } + + override fun toString(): String = "[mirai:file:$name, id=$id, size=$size]" } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/AudioProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/AudioProtocol.kt index e18698de3a..bd004e8c69 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/AudioProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/AudioProtocol.kt @@ -13,11 +13,17 @@ import net.mamoe.mirai.internal.message.data.OfflineAudioImpl import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector +import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder +import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer +import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.toLongUnsigned internal class AudioProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { + add(Decoder()) + MessageSerializer.superclassesScope( OnlineAudio::class, Audio::class, @@ -44,4 +50,22 @@ internal class AudioProtocol : MessageProtocol() { ) } } + + private class Decoder : MessageDecoder { + override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { + val originalMsg = runCatching { attributes[MessageDecoderContext.CONTAINING_MSG] } + .getOrNull() ?: return + + val ptt = originalMsg.msgBody.richText.ptt ?: return + collect(OnlineAudioImpl( + filename = ptt.fileName.decodeToString(), + fileMd5 = ptt.fileMd5, + fileSize = ptt.fileSize.toLongUnsigned(), + codec = AudioCodec.fromId(ptt.format), + url = ptt.downPara.decodeToString(), + length = ptt.time.toLongUnsigned(), + originalPtt = ptt, + )) + } + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt index e7a76d694b..52f139a861 100644 --- a/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt +++ b/mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt @@ -13,7 +13,8 @@ import io.ktor.utils.io.core.* import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import net.mamoe.mirai.internal.contact.SendMessageStep -import net.mamoe.mirai.internal.message.data.FileMessageImpl +import net.mamoe.mirai.internal.message.data.FriendFileMessageImpl +import net.mamoe.mirai.internal.message.data.GroupFileMessageImpl import net.mamoe.mirai.internal.message.data.checkIsImpl import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol @@ -32,7 +33,9 @@ import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.message.visitor.MessageVisitorEx import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ObjMsg +import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x4 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement +import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.message.data.MessageChain @@ -60,8 +63,8 @@ internal class FileMessageProtocol : MessageProtocol() { MessageSerializer.superclassesScope(FileMessage::class, MessageContent::class, SingleMessage::class) { add( MessageSerializer( - FileMessageImpl::class, - FileMessageImpl.serializer() + GroupFileMessageImpl::class, + GroupFileMessageImpl.serializer() ) ) } @@ -85,7 +88,7 @@ internal class FileMessageProtocol : MessageProtocol() { override fun visitFileMessage(message: FileMessage, data: Unit) { if (ALLOW_SENDING_FILE_MESSAGE) return // #1715 - if (message !is FileMessageImpl) error("Customized FileMessage cannot be send") + if (message !is GroupFileMessageImpl) error("Customized FileMessage cannot be send") if (!message.allowSend) { hasFileMessage = true } @@ -110,7 +113,7 @@ internal class FileMessageProtocol : MessageProtocol() { val file = currentMessageChain[FileMessage] ?: return markAsConsumed() - file.checkIsImpl() + file.checkIsImpl() // TODO: file check impl val contact = attributes[CONTACT] val bot = contact.bot @@ -133,43 +136,58 @@ internal class FileMessageProtocol : MessageProtocol() { private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { - if (data.transElemInfo == null) return - if (data.transElemInfo.elemType != 24) return - - markAsConsumed() - - data.transElemInfo.elemValue.read { - // group file feed - // 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63 - // fun getFileRsrvAttr(file: ObjMsg.MsgContentInfo.MsgFile): HummerResv21.ResvAttr? { - // if (file.ext.isEmpty()) return null - // val element = kotlin.runCatching { - // jsonForFileDecode.parseToJsonElement(file.ext) as? JsonObject - // }.getOrNull() ?: return null - // val extInfo = element["ExtInfo"]?.toString()?.decodeBase64() ?: return null - // return extInfo.loadAs(HummerResv21.ResvAttr.serializer()) - // } - - val var7 = readByte() - if (var7 == 1.toByte()) { - while (remaining > 2) { - val proto = readProtoBuf(ObjMsg.ObjMsg.serializer(), readShort().toUShort().toInt()) - // proto.msgType=6 - - val file = proto.msgContentInfo.firstOrNull()?.msgFile ?: continue // officially get(0) only. - // val attr = getFileRsrvAttr(file) ?: continue - // val info = attr.forwardExtFileInfo ?: continue - - collect( - FileMessageImpl( - id = file.filePath, - busId = file.busId, // path i.e. /a99e95fa-7b2d-11eb-adae-5452007b698a - name = file.fileName, - size = file.fileSize + if (data.transElemInfo != null && data.transElemInfo.elemType == 24) { + markAsConsumed() + data.transElemInfo.elemValue.read { + // group file feed + // 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63 + // fun getFileRsrvAttr(file: ObjMsg.MsgContentInfo.MsgFile): HummerResv21.ResvAttr? { + // if (file.ext.isEmpty()) return null + // val element = kotlin.runCatching { + // jsonForFileDecode.parseToJsonElement(file.ext) as? JsonObject + // }.getOrNull() ?: return null + // val extInfo = element["ExtInfo"]?.toString()?.decodeBase64() ?: return null + // return extInfo.loadAs(HummerResv21.ResvAttr.serializer()) + // } + + val var7 = readByte() + if (var7 == 1.toByte()) { + while (remaining > 2) { + val proto = readProtoBuf(ObjMsg.ObjMsg.serializer(), readShort().toUShort().toInt()) + // proto.msgType=6 + + val file = proto.msgContentInfo.firstOrNull()?.msgFile ?: continue // officially get(0) only. + // val attr = getFileRsrvAttr(file) ?: continue + // val info = attr.forwardExtFileInfo ?: continue + + collect( + GroupFileMessageImpl( + id = file.filePath, + busId = file.busId, // path i.e. /a99e95fa-7b2d-11eb-adae-5452007b698a + name = file.fileName, + size = file.fileSize + ) ) - ) + } } } + return + } + + val originalMsg = runCatching { attributes[MessageDecoderContext.CONTAINING_MSG] }.getOrNull() + if (originalMsg != null && originalMsg.msgHead.msgType == 529) { + markAsConsumed() + val sub0x4 = originalMsg.msgBody.msgContent.loadAs(SubMsgType0x4.MsgBody.serializer()) + if (sub0x4.msgNotOnlineFile != null) { + collect( + FriendFileMessageImpl( + sub0x4.msgNotOnlineFile.fileUuid.decodeToString(), + sub0x4.msgNotOnlineFile.fileName.decodeToString(), + sub0x4.msgNotOnlineFile.fileSize + ) + ) + } + return } } } diff --git a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt index 44dc622162..5211af2b29 100644 --- a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt +++ b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt @@ -121,6 +121,7 @@ internal enum class ResourceKind( GROUP_AUDIO("group audio"), GROUP_FILE("group file"), + FRIEND_FILE("friend file"), LONG_MESSAGE("long message"), FORWARD_MESSAGE("forward message"), diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt index ba0a27a163..cc8690366a 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt @@ -15,19 +15,28 @@ import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.getGroupByUinOrCode +import net.mamoe.mirai.internal.message.ReceiveMessageTransformer import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.SimpleRefineContext +import net.mamoe.mirai.internal.message.data.FriendFileMessageImpl import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.fromSync import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor +import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x4 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore +import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSourceKind +import net.mamoe.mirai.message.data.buildMessageChain +import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.utils.assertUnreachable import net.mamoe.mirai.utils.context @@ -90,7 +99,8 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor(type if (!bot.components[SsoProcessor].firstLoginSucceed) return val senderUin = if (fromSync) msgHead.toUin else msgHead.fromUin when (msgHead.msgType) { - 166, 167, // 单向好友 + 166, + 167, // 单向好友 208, // friend ptt, maybe also support stranger -> { data.msgBody.richText.ptt?.let { ptt -> @@ -118,6 +128,13 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor(type handlePrivateMessage(data, group[senderUin] ?: return) } + 529, // friend file + -> { + val content = msgBody.msgContent + if (content.isEmpty()) return + handlePrivateMessage(data, bot.getFriend(senderUin)?.impl() ?: return) + } + else -> markNotConsumed() } @@ -143,11 +160,20 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor(type RefineContextKey.GroupIdOrZero to 0L, ) ) - val time = msgHead.msgTime - collected += if (fromSync) { - val client = bot.otherClients.find { it.appId == msgHead.fromInstid } - ?: return // don't compare with dstAppId. diff. + collected += constructMessageEvent(fromSync, msgHead.fromInstid, user, chain, msgHead.msgTime) + } + + private fun NoticePipelineContext.constructMessageEvent( + fromSync: Boolean, + fromInstid: Int, + user: AbstractUser, + chain: MessageChain, + time: Int + ): MessageEvent? { + return if (fromSync) { + val client = bot.otherClients.find { it.appId == fromInstid } + ?: return null // don't compare with dstAppId. diff. when (user) { is FriendImpl -> FriendMessageSyncEvent(client, user, chain, time) is StrangerImpl -> StrangerMessageSyncEvent(client, user, chain, time) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x346.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x346.kt index 2282dd2bd0..9d0296a483 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x346.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x346.kt @@ -18,6 +18,15 @@ import kotlin.jvm.JvmField @Serializable internal class Cmd0x346 : ProtoBuf { + @Serializable + internal class Addr( + @JvmField @ProtoNumber(1) val outIp: Int = 0, + @JvmField @ProtoNumber(2) val outPort: Int = 0, + @JvmField @ProtoNumber(3) val innerIp: Int = 0, + @JvmField @ProtoNumber(4) val innerPort: Int = 0, + @JvmField @ProtoNumber(5) val ipType: Int = 0, + ) : ProtoBuf + @Serializable internal class AddrList( @JvmField @ProtoNumber(2) val strIp: List = emptyList(), @@ -80,6 +89,7 @@ internal class Cmd0x346 : ProtoBuf { internal class ApplyDownloadAbsReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(30) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -94,8 +104,11 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(30) val ownerType: Int = 0, + @JvmField @ProtoNumber(50) val filetype: Int = 0, + @JvmField @ProtoNumber(60) val fileidcrc: String = "", @JvmField @ProtoNumber(500) val extUintype: Int = 0, @JvmField @ProtoNumber(501) val needHttpsUrl: Int = 0, + @JvmField @ProtoNumber(600) val fileid: String = "", ) : ProtoBuf @Serializable @@ -104,6 +117,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val msgDownloadInfo: Cmd0x346.DownloadInfo? = null, @JvmField @ProtoNumber(40) val msgFileInfo: Cmd0x346.FileInfo? = null, + @JvmField @ProtoNumber(50) val fileSha: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable @@ -113,6 +127,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(30) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(40) val dangerLevel: Int = 0, @JvmField @ProtoNumber(50) val totalSpace: Long = 0L, + @JvmField @ProtoNumber(60) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -122,6 +137,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(30) val totalSpace: Long = 0L, @JvmField @ProtoNumber(40) val usedSpace: Long = 0L, @JvmField @ProtoNumber(50) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(60) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -275,6 +291,9 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(70) val localFilepath: String = "", @JvmField @ProtoNumber(80) val dangerLevel: Int = 0, @JvmField @ProtoNumber(90) val totalSpace: Long = 0L, + @JvmField @ProtoNumber(100) val contenttype: Int /* enum */ = 0, + @JvmField @ProtoNumber(110) val md5: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(120) val _3sha: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable @@ -337,6 +356,9 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(150) val uploadHttpsDomain: String = "", @JvmField @ProtoNumber(160) val uploadDns: String = "", @JvmField @ProtoNumber(170) val uploadLanip: String = "", + @JvmField @ProtoNumber(200) val fileidcrc: String = "", + @JvmField @ProtoNumber(210) val rtpMediaPlatformUploadAddress: List = emptyList(), + @JvmField @ProtoNumber(220) val mediaPlateformUploadKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable @@ -345,6 +367,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(20) val peerUin: Long = 0L, @JvmField @ProtoNumber(30) val deleteType: Int = 0, @JvmField @ProtoNumber(40) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(50) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -374,12 +397,15 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(80) val httpsPort: Int = 443, @JvmField @ProtoNumber(90) val httpsDownloadDomain: String = "", @JvmField @ProtoNumber(110) val downloadDns: String = "", + @JvmField @ProtoNumber(120) val mediaPlatformDownloadKey: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(130) val downloadipv6List: List = emptyList(), ) : ProtoBuf @Serializable internal class DownloadSuccReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(30) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -434,12 +460,16 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(120) val ownerUin: Long = 0L, @JvmField @ProtoNumber(121) val peerUin: Long = 0L, @JvmField @ProtoNumber(130) val expireTime: Int = 0, + @JvmField @ProtoNumber(140) val fileidcrc: String = "", + @JvmField @ProtoNumber(141) val md5: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(142) val _3sha: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class FileQueryReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(30) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -453,6 +483,7 @@ internal class Cmd0x346 : ProtoBuf { internal class RecallFileReq( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(3) val fileidcrc: String = "", ) : ProtoBuf @Serializable @@ -466,6 +497,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val beginIndex: Int = 0, @JvmField @ProtoNumber(3) val reqCount: Int = 0, + @JvmField @ProtoNumber(4) val filterFiletype: Int = 0, ) : ProtoBuf @Serializable @@ -517,6 +549,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(21) val msgApplyUploadHitReqV3: Cmd0x346.ApplyUploadHitReqV3? = null, @JvmField @ProtoNumber(101) val businessId: Int = 0, @JvmField @ProtoNumber(102) val clientType: Int = 0, + @JvmField @ProtoNumber(200) val flagSupportMediaplatform: Int = 0, @JvmField @ProtoNumber(90000) val msgApplyCopyToReq: Cmd0x346.ApplyCopyToReq? = null, @JvmField @ProtoNumber(90001) val msgApplyCleanTrafficReq: Cmd0x346.ApplyCleanTrafficReq? = null, @JvmField @ProtoNumber(90002) val msgApplyGetTrafficReq: Cmd0x346.ApplyGetTrafficReq? = null, @@ -546,6 +579,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(19) val msgApplyUploadRspV3: Cmd0x346.ApplyUploadRspV3? = null, @JvmField @ProtoNumber(20) val msgApplyUploadHitRspV2: Cmd0x346.ApplyUploadHitRspV2? = null, @JvmField @ProtoNumber(21) val msgApplyUploadHitRspV3: Cmd0x346.ApplyUploadHitRspV3? = null, + @JvmField @ProtoNumber(50) val flagUseMediaPlatform: Int = 0, @JvmField @ProtoNumber(90000) val msgApplyCopyToRsp: Cmd0x346.ApplyCopyToRsp? = null, @JvmField @ProtoNumber(90001) val msgApplyCleanTrafficRsp: Cmd0x346.ApplyCleanTrafficRsp? = null, @JvmField @ProtoNumber(90002) val msgApplyGetTrafficRsp: Cmd0x346.ApplyGetTrafficRsp? = null, @@ -557,6 +591,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val beginIndex: Int = 0, @JvmField @ProtoNumber(3) val reqCount: Int = 0, + @JvmField @ProtoNumber(4) val filterFiletype: Int = 0, ) : ProtoBuf @Serializable @@ -577,6 +612,7 @@ internal class Cmd0x346 : ProtoBuf { @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val uuid: ByteArray = EMPTY_BYTE_ARRAY, + @JvmField @ProtoNumber(40) val fileidcrc: String = "", ) : ProtoBuf @Serializable diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Exciting.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Exciting.kt index 694e218eae..f303babb0d 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Exciting.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Exciting.kt @@ -16,20 +16,21 @@ import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField @Serializable -internal class GroupFileUploadExt( +internal class FileUploadExt( @JvmField @ProtoNumber(1) val u1: Int, @JvmField @ProtoNumber(2) val u2: Int, - @JvmField @ProtoNumber(100) val entry: GroupFileUploadEntry, @JvmField @ProtoNumber(3) val u3: Int, + @JvmField @ProtoNumber(100) val entry: FileUploadEntry, + @JvmField @ProtoNumber(200) val u200: Int? = null, ) : ProtoBuf @Serializable -internal class GroupFileUploadEntry( +internal class FileUploadEntry( @JvmField @ProtoNumber(100) val business: ExcitingBusiInfo, @JvmField @ProtoNumber(200) val fileEntry: ExcitingFileEntry, @JvmField @ProtoNumber(300) val clientInfo: ExcitingClientInfo, @JvmField @ProtoNumber(400) val fileNameInfo: ExcitingFileNameInfo, - @JvmField @ProtoNumber(500) val host: ExcitingHostConfig, + @JvmField @ProtoNumber(500) val host: ExcitingHostConfig? = null, ) : ProtoBuf @Serializable diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index 2c4d7a886c..ed4a1d7e3f 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -181,6 +181,10 @@ internal object KnownPacketFactories { SummaryCard.ReqSummaryCard, ChangeFriendRemark, MusicSharePacket, + OfflineFilleHandleSvr.UploadSucc, + OfflineFilleHandleSvr.ApplyDownload, + OfflineFilleHandleSvr.FileQuery, + OfflineFilleHandleSvr.ApplyUploadV3, *FileManagement.factories ) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/OfflineFilleHandleSvr.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/OfflineFilleHandleSvr.kt new file mode 100644 index 0000000000..4ce54a247c --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/OfflineFilleHandleSvr.kt @@ -0,0 +1,247 @@ +/* + * Copyright 2019-2023 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.network.protocol.packet.chat + +import io.ktor.utils.io.core.* +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.uin +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346 +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf +import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf + +internal class OfflineFilleHandleSvr { + + internal sealed class FileInfo : Packet { + class Success( + val fileUuid: ByteArray, + val filename: String, + val fileSha1: ByteArray, + val fileMd5: ByteArray, + val fileSize: Long, + val expiryTime: Long, + val ownerUin: Long, + ) : FileInfo() + class Failed(val message: String) : FileInfo() + } + + internal object UploadSucc : OutgoingPacketFactory( + "OfflineFilleHandleSvr.pb_ftn_CMD_REQ_UPLOAD_SUCC-800" + ) { + + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): FileInfo { + val resp = readProtoBuf(Cmd0x346.RspBody.serializer()) + + val upResp = resp.msgUploadSuccRsp + ?: return FileInfo.Failed("msgUploadSuccRsp is null") + + if (upResp.int32RetCode != 0) { + return FileInfo.Failed("return code is ${upResp.int32RetCode}: ${upResp.retMsg}") + } + + val fileInfo = upResp.msgFileInfo + ?: return FileInfo.Failed("msgUploadSuccRsp.msgFileInfo is null") + + return FileInfo.Success( + fileInfo.uuid, + fileInfo.fileName, + fileInfo._3sha, + fileInfo.md5, + fileInfo.fileSize, + fileInfo.expireTime.toLong(), + fileInfo.ownerUin, + ) + } + + operator fun invoke( + client: QQAndroidClient, + contact: Contact, + fileUuid: ByteArray, + ) = buildOutgoingUniPacket(client) { seq -> + writeProtoBuf( + Cmd0x346.ReqBody.serializer(), + Cmd0x346.ReqBody( + cmd = 800, + seq = seq, + msgUploadSuccReq = Cmd0x346.UploadSuccReq( + senderUin = client.uin, + recverUin = contact.uin, + uuid = fileUuid, + ) + ) + ) + } + + } + + internal object ApplyDownload : OutgoingPacketFactory( + "OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_DOWNLOAD-1200" + ) { + internal sealed class Response : Packet { + class Success(val url: String) : Response() + class Failed(val message: String) : Response() + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp = readProtoBuf(Cmd0x346.RspBody.serializer()) + + val downResp = resp.msgApplyDownloadRsp + ?: return Response.Failed("msgApplyDownloadRsp is null") + + if (downResp.int32RetCode != 0) { + return Response.Failed("return code is ${downResp.int32RetCode}: ${downResp.retMsg}") + } + + val downInfo = downResp.msgDownloadInfo + ?: return Response.Failed("msgDownloadInfo is null") + + return Response.Success(buildString { + append("http://") + append(downInfo.downloadDomain) + append(downInfo.downloadUrl) + }) + + } + + operator fun invoke( + client: QQAndroidClient, + fileUuid: ByteArray + ) = buildOutgoingUniPacket(client) { seq -> + writeProtoBuf( + Cmd0x346.ReqBody.serializer(), + Cmd0x346.ReqBody( + cmd = 1200, + seq = seq, + businessId = 3, + clientType = 104, + msgApplyDownloadReq = Cmd0x346.ApplyDownloadReq( + uin = client.uin, + uuid = fileUuid, + ownerType = 2 + ), + msgExtensionReq = Cmd0x346.ExtensionReq( + downloadUrlType = 1 + ) + ) + ) + } + } + + internal object FileQuery : OutgoingPacketFactory( + "OfflineFilleHandleSvr.pb_ftn_CMD_REQ_FILE_QUERY-1400" + ) { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): FileInfo { + val resp = readProtoBuf(Cmd0x346.RspBody.serializer()) + + val queryResp = resp.msgFileQueryRsp + ?: return FileInfo.Failed("msgFileQueryRsp is null") + + if (queryResp.int32RetCode != 0) { + return FileInfo.Failed("return code is ${queryResp.int32RetCode}: ${queryResp.retMsg}") + } + + val fileInfo = queryResp.msgFileInfo + ?: return FileInfo.Failed("msgFileQueryRsp.msgFileInfo is null") + + return FileInfo.Success( + fileInfo.uuid, + fileInfo.fileName, + fileInfo._3sha, + fileInfo.md5, + fileInfo.fileSize, + fileInfo.expireTime.toLong(), + fileInfo.ownerUin, + ) + } + + operator fun invoke( + client: QQAndroidClient, + fileUuid: ByteArray, + ) = buildOutgoingUniPacket(client) { seq -> + writeProtoBuf( + Cmd0x346.ReqBody.serializer(), + Cmd0x346.ReqBody( + cmd = 1400, + seq = seq, + businessId = 3, + clientType = 104, + msgFileQueryReq = Cmd0x346.FileQueryReq( + uin = client.uin, + uuid = fileUuid, + ) + ) + ) + } + } + + internal object ApplyUploadV3 : OutgoingPacketFactory( + "OfflineFilleHandleSvr.pb_ftn_CMD_REQ_APPLY_UPLOAD_V3-1700" + ) { + internal sealed class Response : Packet { + class FileExists(val fileUuid: ByteArray,) : Response() + class RequireUpload(val fileUuid: ByteArray, val uploadKey: ByteArray) : Response() + class Failed(val message: String) : Response() + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp = readProtoBuf(Cmd0x346.RspBody.serializer()) + + val upResp = resp.msgApplyUploadRspV3 + ?: return Response.Failed("msgApplyUploadRspV3 is null") + + if (upResp.int32RetCode != 0) { + return Response.Failed("return code is ${upResp.int32RetCode}: ${upResp.retMsg}") + } + + return if (upResp.boolFileExist) { + Response.FileExists(upResp.uuid) + } else { + Response.RequireUpload(upResp.uuid, upResp.mediaPlateformUploadKey) + } + } + + operator fun invoke( + client: QQAndroidClient, + contact: Contact, + filename: String, + fileSize: Long, + fileMd5: ByteArray, + fileSha1: ByteArray, + ) = buildOutgoingUniPacket(client) { seq -> + writeProtoBuf( + Cmd0x346.ReqBody.serializer(), + Cmd0x346.ReqBody( + cmd = 1700, + seq = seq, + msgApplyUploadReqV3 = Cmd0x346.ApplyUploadReqV3( + senderUin = client.uin, + recverUin = contact.uin, + fileSize = fileSize, + fileName = filename, + _10mMd5 = fileMd5, + sha = fileSha1, + localFilepath = "/storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/$filename", + dangerLevel = 0, + totalSpace = 0, + contenttype = 0, + md5 = fileMd5 + ) + ) + ) + } + } + + +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/data/MessageSerializationTest.kt b/mirai-core/src/commonTest/kotlin/message/data/MessageSerializationTest.kt index ecd71ae484..974a8f9212 100644 --- a/mirai-core/src/commonTest/kotlin/message/data/MessageSerializationTest.kt +++ b/mirai-core/src/commonTest/kotlin/message/data/MessageSerializationTest.kt @@ -110,7 +110,7 @@ internal class MessageSerializationTest : AbstractTest() { MessageOrigin(SimpleServiceMessage(1, "content"), "resource id", MessageOriginKind.LONG), ShowImageFlag, Dice(1), - FileMessageImpl("id", 2, "name", 1) + GroupFileMessageImpl("id", 2, "name", 1) ) @Serializable @@ -120,7 +120,7 @@ internal class MessageSerializationTest : AbstractTest() { @Test fun `test FileMessage serialization`() { - val w = W(FileMessageImpl("id", 2, "name", 1)) + val w = W(GroupFileMessageImpl("id", 2, "name", 1)) println(w.serialize(W.serializer())) assertEquals(w, w.serialize(W.serializer()).deserialize(W.serializer())) } diff --git a/mirai-core/src/jvmBaseMain/kotlin/utils/RemoteFileImpl.kt b/mirai-core/src/jvmBaseMain/kotlin/utils/RemoteFileImpl.kt index 5f018c75a6..93db3748c4 100644 --- a/mirai-core/src/jvmBaseMain/kotlin/utils/RemoteFileImpl.kt +++ b/mirai-core/src/jvmBaseMain/kotlin/utils/RemoteFileImpl.kt @@ -19,7 +19,7 @@ import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.contact.groupCode -import net.mamoe.mirai.internal.message.data.FileMessageImpl +import net.mamoe.mirai.internal.message.data.GroupFileMessageImpl import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind @@ -446,10 +446,10 @@ internal abstract class CommonRemoteFileImpl( return resp } - val ext = GroupFileUploadExt( + val ext = FileUploadExt( u1 = 100, u2 = 1, - entry = GroupFileUploadEntry( + entry = FileUploadEntry( business = ExcitingBusiInfo( busId = resp.busId, senderUin = bot.id, @@ -486,7 +486,7 @@ internal abstract class CommonRemoteFileImpl( ), ), u3 = 0, - ).toByteArray(GroupFileUploadExt.serializer()) + ).toByteArray(FileUploadExt.serializer()) callback?.onBegin(this, resource) @@ -519,7 +519,7 @@ internal abstract class CommonRemoteFileImpl( callback: RemoteFile.ProgressionCallback?, ): FileMessage { val resp = upload0(resource, callback) ?: error("Failed to upload file.") - return FileMessageImpl( + return GroupFileMessageImpl( resp.fileId, resp.busId, name, resource.size, allowSend = true ) } @@ -582,7 +582,7 @@ internal abstract class CommonRemoteFileImpl( override suspend fun toMessage(): FileMessage? { val info = getFileFolderInfo() ?: return null if (!info.isFile) return null - return FileMessageImpl(info.id, info.busId, name, info.size) + return GroupFileMessageImpl(info.id, info.busId, name, info.size) } }