mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-24 20:43:33 +08:00
[core] initial support for decoding FriendFileMessage, sending friend file
This commit is contained in:
parent
ab5d08afb1
commit
ecee497ec6
mirai-core-api/src/commonMain/kotlin
mirai-core-mock/src/internal
contact
remotefile
mirai-core/src
commonMain/kotlin
MiraiImpl.kt
contact
message
network
highway
notice/priv
protocol
commonTest/kotlin/message/data
jvmBaseMain/kotlin/utils
@ -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<AbsoluteFile, Long>? = null
|
||||
): FileMessage
|
||||
}
|
@ -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 {
|
||||
|
||||
/**
|
||||
* 该好友所在的好友分组
|
||||
|
@ -53,6 +53,8 @@ public interface AbsoluteFile : AbsoluteFileFolder {
|
||||
*
|
||||
* 注意该操作有可能产生同名文件或目录 (当 [folder] 中已经存在一个名称为 [name] 的文件或目录时).
|
||||
*
|
||||
* 对于好友文件, 此方法不会产生任何效果.
|
||||
*
|
||||
* @throws IOException 当发生网络错误时可能抛出
|
||||
* @throws IllegalStateException 当发生已知的协议错误时抛出
|
||||
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
|
||||
|
@ -137,7 +137,7 @@ private object MiraiCodeParsers : AbstractMap<String, MiraiCodeParser>(), Map<St
|
||||
MusicShare(MusicKind.valueOf(kind), title, summary, jumpUrl, pictureUrl, musicUrl, brief)
|
||||
},
|
||||
"file" to MiraiCodeParser(Regex("""(.*?),(.*?),(.*?),(.*?)""")) { (id, internalId, name, size) ->
|
||||
FileMessage(id, internalId.toInt(), name, size.toLong())
|
||||
FileMessage(id, internalId.toInt(), name, size.toLong()) // TODO: parser for friend file
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -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<AbsoluteFile, Long>?
|
||||
): FileMessage {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun newMessagePreSend(message: Message): MessagePreSendEvent {
|
||||
return FriendMessagePreSendEvent(this, message)
|
||||
}
|
||||
|
@ -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<AbsoluteFile, Long>?
|
||||
): FileMessage {
|
||||
TODO("uploadFile of MockGroupImpl")
|
||||
}
|
||||
|
||||
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio =
|
||||
resource.mockUploadAudio(bot)
|
||||
|
||||
|
@ -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? =
|
||||
|
@ -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,
|
||||
|
@ -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 =
|
||||
|
@ -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<FriendImpl> = 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<AbsoluteFile, Long>?
|
||||
): 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"
|
||||
|
@ -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<AbsoluteFile, Long>?,
|
||||
): 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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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<MsgComm.Msg>.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<MsgComm.Msg>.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,
|
||||
)
|
||||
}
|
@ -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]"
|
||||
}
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
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())
|
||||
// }
|
||||
|
||||
markAsConsumed()
|
||||
val var7 = readByte()
|
||||
if (var7 == 1.toByte()) {
|
||||
while (remaining > 2) {
|
||||
val proto = readProtoBuf(ObjMsg.ObjMsg.serializer(), readShort().toUShort().toInt())
|
||||
// proto.msgType=6
|
||||
|
||||
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 file = proto.msgContentInfo.firstOrNull()?.msgFile ?: continue // officially get(0) only.
|
||||
// val attr = getFileRsrvAttr(file) ?: continue
|
||||
// val info = attr.forwardExtFileInfo ?: continue
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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<MsgComm.Msg>(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<MsgComm.Msg>(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<MsgComm.Msg>(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)
|
||||
|
@ -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<String> = 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<Cmd0x346.Addr> = 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<String> = 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
|
||||
|
@ -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
|
||||
|
@ -181,6 +181,10 @@ internal object KnownPacketFactories {
|
||||
SummaryCard.ReqSummaryCard,
|
||||
ChangeFriendRemark,
|
||||
MusicSharePacket,
|
||||
OfflineFilleHandleSvr.UploadSucc,
|
||||
OfflineFilleHandleSvr.ApplyDownload,
|
||||
OfflineFilleHandleSvr.FileQuery,
|
||||
OfflineFilleHandleSvr.ApplyUploadV3,
|
||||
*FileManagement.factories
|
||||
)
|
||||
|
||||
|
@ -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<FileInfo>(
|
||||
"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<ApplyDownload.Response>(
|
||||
"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<FileInfo>(
|
||||
"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<ApplyUploadV3.Response>(
|
||||
"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
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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()))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user