Skip to content

Commit

Permalink
[core] initial support for decoding FriendFileMessage, sending friend…
Browse files Browse the repository at this point in the history
… file
  • Loading branch information
StageGuard committed Sep 1, 2023
1 parent ab5d08a commit ecee497
Show file tree
Hide file tree
Showing 27 changed files with 774 additions and 104 deletions.
21 changes: 20 additions & 1 deletion mirai-core-api/src/commonMain/kotlin/contact/FileSupported.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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]
*
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion mirai-core-api/src/commonMain/kotlin/contact/Friend.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

/**
* 该好友所在的好友分组
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public interface AbsoluteFile : AbsoluteFileFolder {
*
* 注意该操作有可能产生同名文件或目录 (当 [folder] 中已经存在一个名称为 [name] 的文件或目录时).
*
* 对于好友文件, 此方法不会产生任何效果.
*
* @throws IOException 当发生网络错误时可能抛出
* @throws IllegalStateException 当发生已知的协议错误时抛出
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
)

Expand Down
31 changes: 26 additions & 5 deletions mirai-core-mock/src/internal/contact/MockFriendImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,27 @@
* 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

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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
9 changes: 9 additions & 0 deletions mirai-core-mock/src/internal/contact/MockGroupImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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? =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion mirai-core/src/commonMain/kotlin/MiraiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
109 changes: 109 additions & 0 deletions mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,42 @@ 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
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
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.ChangeFriendRemark
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
Expand Down Expand Up @@ -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"
Expand Down
11 changes: 11 additions & 0 deletions mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit ecee497

Please sign in to comment.