From 12ebfa1db55f9c8327e5d5ef68466267a346089b Mon Sep 17 00:00:00 2001 From: Him188 Date: Tue, 3 Mar 2020 13:45:33 +0800 Subject: [PATCH] Unified Image types: Online/Offline Image, Group/Friend Image --- .../net/mamoe/mirai/qqandroid/ContactImpl.kt | 24 ++- .../net/mamoe/mirai/qqandroid/QQAndroidBot.kt | 19 +- .../mamoe/mirai/qqandroid/message/messages.kt | 33 ++-- .../kotlin/net/mamoe/mirai/contact/Contact.kt | 9 +- .../kotlin/net/mamoe/mirai/contact/Group.kt | 31 +++ .../kotlin/net/mamoe/mirai/contact/QQ.kt | 18 ++ .../kotlin/net.mamoe.mirai/contact/Contact.kt | 2 +- .../kotlin/net.mamoe.mirai/contact/Group.kt | 16 ++ .../kotlin/net.mamoe.mirai/contact/QQ.kt | 18 +- .../net.mamoe.mirai/message/data/Image.kt | 181 ++++++++++++++---- .../kotlin/net/mamoe/mirai/contact/Contact.kt | 4 +- .../kotlin/net/mamoe/mirai/contact/Group.kt | 30 +++ .../kotlin/net/mamoe/mirai/contact/QQ.kt | 17 ++ 13 files changed, 323 insertions(+), 79 deletions(-) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index b2e7ed041..500bad2c0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -20,7 +20,10 @@ import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.message.data.OfflineGroupImage import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.postImage @@ -73,7 +76,7 @@ internal class QQImpl( return MessageReceipt(source, this, null) } - override suspend fun uploadImage(image: ExternalImage): Image = try { + override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try { if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } @@ -93,8 +96,9 @@ internal class QQImpl( ) ).sendAndExpect() + @Suppress("UNCHECKED_CAST") // bug return when (response) { - is LongConn.OffPicUp.Response.FileExists -> NotOnlineImageFromFile( + is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage( filepath = response.resourceId, md5 = response.imageInfo.fileMd5, fileLength = response.imageInfo.fileSize.toInt(), @@ -125,7 +129,7 @@ internal class QQImpl( //) // 为什么不能 ?? - return NotOnlineImageFromFile( + return OfflineFriendImage( filepath = response.resourceId, md5 = image.md5, fileLength = image.inputSize.toInt(), @@ -192,6 +196,7 @@ internal class MemberImpl( // region QQ delegate override val id: Long = qq.id override val nick: String = qq.nick + @MiraiExperimentalAPI override suspend fun queryProfile(): Profile = qq.queryProfile() @@ -202,12 +207,14 @@ internal class MemberImpl( override suspend fun queryRemark(): FriendNameRemark = qq.queryRemark() override suspend fun sendMessage(message: MessageChain): MessageReceipt = qq.sendMessage(message) - override suspend fun uploadImage(image: ExternalImage): Image = qq.uploadImage(image) + override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = qq.uploadImage(image) // endregion override var permission: MemberPermission = memberInfo.permission + @Suppress("PropertyName") internal var _nameCard: String = memberInfo.nameCard + @Suppress("PropertyName") internal var _specialTitle: String = memberInfo.specialTitle @@ -589,7 +596,7 @@ internal class GroupImpl( return MessageReceipt(source, this, botAsMember) } - override suspend fun uploadImage(image: ExternalImage): Image = try { + override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage = try { if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } @@ -606,6 +613,7 @@ internal class GroupImpl( filename = image.filename ).sendAndExpect() + @Suppress("UNCHECKED_CAST") // bug when (response) { is ImgStore.GroupPicUp.Response.Failed -> { ImageUploadEvent.Failed(this@GroupImpl, image, response.resultCode, response.message).broadcast() @@ -625,7 +633,7 @@ internal class GroupImpl( // fileId = response.fileId.toInt() // ) // println("NMSL") - return CustomFaceFromFile( + return OfflineGroupImage( md5 = image.md5, filepath = resourceId ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } @@ -655,7 +663,7 @@ internal class GroupImpl( // imageType = image.imageType, // fileId = response.fileId.toInt() // ) - return CustomFaceFromFile( + return OfflineGroupImage( md5 = image.md5, filepath = resourceId ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index ecf60011a..72c5d54a9 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -24,8 +24,8 @@ import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.MessageRecallEvent import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.qqandroid.message.CustomFaceFromServer -import net.mamoe.mirai.qqandroid.message.NotOnlineImageFromServer +import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl +import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl @@ -58,6 +58,7 @@ internal abstract class QQAndroidBotBase constructor( ) internal var firstLoginSucceed: Boolean = false override val uin: Long get() = client.uin + @Deprecated( "use friends instead", level = DeprecationLevel.ERROR, @@ -208,14 +209,14 @@ internal abstract class QQAndroidBotBase constructor( } } - override suspend fun queryImageUrl(image: Image): String = "http://gchat.qpic.cn" + when (image) { - is NotOnlineImageFromServer -> image.delegate.origUrl - is CustomFaceFromServer -> image.delegate.origUrl - is CustomFaceFromFile -> { - TODO() + override suspend fun queryImageUrl(image: Image): String = when (image) { + is OnlineFriendImageImpl -> image.originUrl + is OnlineGroupImageImpl -> image.originUrl + is OfflineGroupImage -> { + TODO("暂不支持获取离线图片链接") } - is NotOnlineImageFromFile -> { - TODO() + is OfflineFriendImage -> { + TODO("暂不支持获取离线图片链接") } else -> error("unsupported image class: ${image::class.simpleName}") } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt index b6385c8c9..e23516c3b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt @@ -38,7 +38,7 @@ internal fun At.toJceData(): ImMsgBody.Text { ) } -internal fun NotOnlineImageFromFile.toJceData(): ImMsgBody.NotOnlineImage { +internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage { return ImMsgBody.NotOnlineImage( filePath = this.filepath, resId = this.resourceId, @@ -104,7 +104,7 @@ internal fun Face.toJceData(): ImMsgBody.Face { ) } -internal fun CustomFaceFromFile.toJceData(): ImMsgBody.CustomFace { +internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { return ImMsgBody.CustomFace( filePath = this.filepath, fileId = this.fileId, @@ -240,10 +240,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList elements.add(ImMsgBody.Elem(customFace = it.toJceData())) - is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) - is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) - is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData())) + is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) + is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) + is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) + is OfflineFriendImage -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData())) is AtAll -> elements.add(atAllData) is Face -> elements.add(ImMsgBody.Elem(face = it.toJceData())) is QuoteReplyToSend -> { @@ -273,9 +273,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList.joinToMessageChain(message: MessageChainBuilde this.forEach { when { it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg))) - it.notOnlineImage != null -> message.add(NotOnlineImageFromServer(it.notOnlineImage)) - it.customFace != null -> message.add(CustomFaceFromServer(it.customFace)) + it.notOnlineImage != null -> message.add(OnlineFriendImageImpl(it.notOnlineImage)) + it.customFace != null -> message.add(OnlineGroupImageImpl(it.customFace)) it.face != null -> message.add(Face(it.face.index)) it.text != null -> { if (it.text.attr6Buf.isEmpty()) { diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt index 1ab127d8b..43488674d 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt @@ -18,8 +18,8 @@ import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineImage import net.mamoe.mirai.message.data.id import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI @@ -32,6 +32,7 @@ import net.mamoe.mirai.utils.WeakRefProperty * * @author Him188moe */ +@Suppress("INAPPLICABLE_JVM_NAME") @UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class) actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() { /** @@ -61,6 +62,8 @@ actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() { * * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. */ + @JvmName("sendMessgaeSuspend") + @JvmSynthetic actual abstract suspend fun sendMessage(message: MessageChain): MessageReceipt /** @@ -72,7 +75,9 @@ actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() { * @throws EventCancelledException 当发送消息事件被取消 * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) */ - actual abstract suspend fun uploadImage(image: ExternalImage): Image + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract suspend fun uploadImage(image: ExternalImage): OfflineImage /** * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt index 326c0bd93..c1a6d1c21 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt @@ -17,11 +17,15 @@ import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException /** * 群. 在 QQ Android 中叫做 "Troop" */ +@Suppress("INAPPLICABLE_JVM_NAME") actual abstract class Group : Contact(), CoroutineScope { /** * 群名称. @@ -33,6 +37,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var name: String + /** * 入群公告, 没有时为空字符串. * @@ -42,6 +47,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var entranceAnnouncement: String + /** * 全体禁言状态. `true` 为开启. * @@ -51,6 +57,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var isMuteAll: Boolean + /** * 坦白说状态. `true` 为允许. * @@ -60,6 +67,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var isConfessTalkEnabled: Boolean + /** * 允许群员邀请好友入群的状态. `true` 为允许 * @@ -69,29 +77,35 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var isAllowMemberInvite: Boolean + /** * 自动加群审批 */ actual abstract val isAutoApproveEnabled: Boolean + /** * 匿名聊天 */ actual abstract val isAnonymousChatEnabled: Boolean + /** * 同为 groupCode, 用户看到的群号码. */ actual abstract override val id: Long + /** * 群主. * * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 */ actual abstract val owner: Member + /** * [Bot] 在群内的 [Member] 实例 */ @MiraiExperimentalAPI actual abstract val botAsMember: Member + /** * 机器人被禁言还剩余多少秒 * @@ -99,6 +113,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @see isBotMuted 判断机器人是否正在被禁言 */ actual abstract val botMuteRemaining: Int + /** * 机器人在这个群里的权限 * @@ -108,6 +123,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @see BotGroupPermissionChangeEvent 机器人群员修改 */ actual abstract val botPermission: MemberPermission + /** * 群头像下载链接. */ @@ -161,8 +177,23 @@ actual abstract class Group : Contact(), CoroutineScope { * * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) */ + @JvmName("sendMessageSuspend") + @JvmSynthetic actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage + actual companion object { /** * by @kar98k diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt index ba8d14e4c..a33e7931a 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt @@ -7,12 +7,17 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException /** * QQ 对象. @@ -84,4 +89,17 @@ actual abstract class QQ : Contact(), CoroutineScope { @JvmName("sendMessageSuspend") @JvmSynthetic actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index 24e655a7b..c2a4e1d11 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -82,7 +82,7 @@ expect abstract class Contact() : CoroutineScope, ContactJavaHappyAPI { */ @JvmName("uploadImageSuspend") @JvmSynthetic - abstract suspend fun uploadImage(image: ExternalImage): Image + abstract suspend fun uploadImage(image: ExternalImage): OfflineImage /** * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index 83b2ce7bc..15c7e97d7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -19,7 +19,10 @@ import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -177,6 +180,19 @@ expect abstract class Group() : Contact, CoroutineScope { @JvmSynthetic abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage + companion object { // don't @JvmStatic: JDK 1.8 required fun calculateGroupUinByGroupCode(groupCode: Long): Long diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt index 450abee24..b5c41001f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt @@ -16,12 +16,17 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -95,5 +100,16 @@ expect abstract class QQ() : Contact, CoroutineScope { @JvmName("sendMessageSuspend") abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt - + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index f86ff59b4..32a7dd04d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -16,26 +16,21 @@ package net.mamoe.mirai.message.data import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import net.mamoe.mirai.Bot +import net.mamoe.mirai.BotImpl +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.chunkedHexToBytes import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName -import kotlin.jvm.JvmStatic /** * 自定义表情 (收藏的表情), 图片 */ -sealed class Image : Message, MessageContent { - companion object Key : Message.Key { - @JvmStatic - @JsName("fromId") - @JvmName("fromId") - operator fun invoke(imageId: String): Image = when (imageId.length) { - 37 -> NotOnlineImageFromFile(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f - 42 -> CustomFaceFromFile(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png - else -> throw IllegalArgumentException("Bad imageId, expecting length=37 or 42, got ${imageId.length}") - } - } +interface Image : Message, MessageContent { + companion object Key : Message.Key /** * 图片的 id. 只需要有这个 id 即可发送图片. @@ -44,14 +39,84 @@ sealed class Image : Message, MessageContent { * 好友图片的 id: `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * 群图片的 id: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png` */ - abstract val imageId: String + val imageId: String +} +@Suppress("FunctionName") +@JsName("newImage") +@JvmName("newImage") +fun Image(imageId: String): Image = when (imageId.length) { + 37 -> OfflineFriendImage(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f + 42 -> OfflineGroupImage(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png + else -> throw IllegalArgumentException("Bad imageId, expecting length=37 or 42, got ${imageId.length}") +} + +@MiraiInternalAPI("使用 Image") +abstract class AbstractImage internal constructor() : Image { final override fun toString(): String { return "[mirai:$imageId]" } } -abstract class CustomFace : Image() { +// region 在线图片 + +/** + * 在服务器上的图片. 它可以直接获取下载链接. + * + * 一般由 [Contact.uploadImage] 得到 + */ +interface OnlineImage : Image { + /** + * 原图下载链接. 包含域名 + */ + val originUrl: String +} + +/** + * 查询原图下载链接. + */ +suspend fun Image.queryUrl(): String { + @UseExperimental(MiraiInternalAPI::class) + return when (this) { + is OnlineImage -> this.originUrl + else -> BotImpl.instances.peekFirst().get()?.queryImageUrl(this) + ?: error("No Bot available to query image url") + } +} + +// endregion 在线图片 + + +// region 离线图片 + +/** + * 离线的图片, 即为客户端主动上传到服务器而获得的 [Image] 实例. + * 不能直接获取它在服务器上的链接. 需要通过 [Bot.queryImageUrl] 查询 + * + * 一般由 [Contact.uploadImage] 得到 + * @see queryOriginUrl + */ +interface OfflineImage : Image + +/** + * 原图下载链接. 包含域名 + */ +suspend fun OfflineImage.queryOriginUrl(): String { + @UseExperimental(MiraiInternalAPI::class) + return BotImpl.instances.peekFirst().get()?.queryImageUrl(this) ?: error("No Bot available to query image url") +} + +// endregion 离线图片 + +// region 群图片 + + +/** + * 群图片 + */ +// CustomFace +@UseExperimental(MiraiInternalAPI::class) +sealed class GroupImage : AbstractImage() { abstract val filepath: String abstract val fileId: Int abstract val serverIp: Int @@ -70,30 +135,14 @@ abstract class CustomFace : Image() { abstract val original: Int } -private val EMPTY_BYTE_ARRAY = ByteArray(0) - -private fun calculateImageMd5ByImageId(imageId: String): ByteArray { - return if (imageId.startsWith('/')) { - imageId - .drop(1) - .replace("-", "") - .take(16 * 2) - .chunkedHexToBytes() - } else { - imageId - .substringAfter('{') - .substringBefore('}') - .replace("-", "") - .take(16 * 2) - .chunkedHexToBytes() - } -} - +/** + * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl] + */ @Serializable -data class CustomFaceFromFile( +data class OfflineGroupImage( override val filepath: String, // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png override val md5: ByteArray -) : CustomFace() { +) : GroupImage(), OfflineImage { constructor(imageId: String) : this(filepath = imageId, md5 = calculateImageMd5ByImageId(imageId)) override val fileId: Int get() = 0 @@ -117,14 +166,26 @@ data class CustomFaceFromFile( } override fun equals(other: Any?): Boolean { - return other is CustomFaceFromFile && other.md5.contentEquals(this.md5) && other.filepath == this.filepath + return other is OfflineGroupImage && other.md5.contentEquals(this.md5) && other.filepath == this.filepath } } /** - * 电脑可能看不到这个消息. + * 接收消息时获取到的 [GroupImage]. 它可以直接获取下载链接 [originUrl] */ -abstract class NotOnlineImage : Image() { +abstract class OnlineGroupImage : GroupImage(), OnlineImage + + +// endregion 群图片 + +// region 好友图片 + + +/** + * 好友图片 + */ // NotOnlineImage +@UseExperimental(MiraiInternalAPI::class) +sealed class FriendImage : AbstractImage() { abstract val resourceId: String abstract val md5: ByteArray abstract val filepath: String @@ -140,7 +201,10 @@ abstract class NotOnlineImage : Image() { override val imageId: String get() = resourceId } -data class NotOnlineImageFromFile( +/** + * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl] + */ +data class OfflineFriendImage( override val resourceId: String, override val md5: ByteArray, @Transient override val filepath: String = resourceId, @@ -151,7 +215,7 @@ data class NotOnlineImageFromFile( @Transient override val imageType: Int = 1000, @Transient override val downloadPath: String = resourceId, @Transient override val fileId: Int = 0 -) : NotOnlineImage() { +) : FriendImage(), OfflineImage { constructor(imageId: String) : this(resourceId = imageId, md5 = calculateImageMd5ByImageId(imageId)) override fun hashCode(): Int { @@ -159,6 +223,39 @@ data class NotOnlineImageFromFile( } override fun equals(other: Any?): Boolean { - return other is NotOnlineImageFromFile && other.md5.contentEquals(this.md5) && other.resourceId == this.resourceId + return other is OfflineFriendImage && other.md5 + .contentEquals(this.md5) && other.resourceId == this.resourceId } -} \ No newline at end of file +} + +/** + * 接收消息时获取到的 [FriendImage]. 它可以直接获取下载链接 [originUrl] + */ +abstract class OnlineFriendImage : FriendImage(), OnlineImage + + +// endregion + + +// region internal + +private val EMPTY_BYTE_ARRAY = ByteArray(0) + +private fun calculateImageMd5ByImageId(imageId: String): ByteArray { + return if (imageId.startsWith('/')) { + imageId + .drop(1) + .replace("-", "") + .take(16 * 2) + .chunkedHexToBytes() + } else { + imageId + .substringAfter('{') + .substringBefore('}') + .replace("-", "") + .take(16 * 2) + .chunkedHexToBytes() + } +} + +// endregion \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt index a9e23294d..174266426 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt @@ -18,8 +18,8 @@ import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineImage import net.mamoe.mirai.message.data.id import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI @@ -76,7 +76,7 @@ actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() { */ @JvmName("uploadImageSuspend") @JvmSynthetic - actual abstract suspend fun uploadImage(image: ExternalImage): Image + actual abstract suspend fun uploadImage(image: ExternalImage): OfflineImage /** * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt index 9e6e1632e..c1a6d1c21 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt @@ -17,11 +17,15 @@ import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException /** * 群. 在 QQ Android 中叫做 "Troop" */ +@Suppress("INAPPLICABLE_JVM_NAME") actual abstract class Group : Contact(), CoroutineScope { /** * 群名称. @@ -33,6 +37,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var name: String + /** * 入群公告, 没有时为空字符串. * @@ -42,6 +47,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var entranceAnnouncement: String + /** * 全体禁言状态. `true` 为开启. * @@ -51,6 +57,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var isMuteAll: Boolean + /** * 坦白说状态. `true` 为允许. * @@ -60,6 +67,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var isConfessTalkEnabled: Boolean + /** * 允许群员邀请好友入群的状态. `true` 为允许 * @@ -69,29 +77,35 @@ actual abstract class Group : Contact(), CoroutineScope { * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ actual abstract var isAllowMemberInvite: Boolean + /** * 自动加群审批 */ actual abstract val isAutoApproveEnabled: Boolean + /** * 匿名聊天 */ actual abstract val isAnonymousChatEnabled: Boolean + /** * 同为 groupCode, 用户看到的群号码. */ actual abstract override val id: Long + /** * 群主. * * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 */ actual abstract val owner: Member + /** * [Bot] 在群内的 [Member] 实例 */ @MiraiExperimentalAPI actual abstract val botAsMember: Member + /** * 机器人被禁言还剩余多少秒 * @@ -99,6 +113,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @see isBotMuted 判断机器人是否正在被禁言 */ actual abstract val botMuteRemaining: Int + /** * 机器人在这个群里的权限 * @@ -108,6 +123,7 @@ actual abstract class Group : Contact(), CoroutineScope { * @see BotGroupPermissionChangeEvent 机器人群员修改 */ actual abstract val botPermission: MemberPermission + /** * 群头像下载链接. */ @@ -161,9 +177,23 @@ actual abstract class Group : Contact(), CoroutineScope { * * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) */ + @JvmName("sendMessageSuspend") @JvmSynthetic actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage + actual companion object { /** * by @kar98k diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt index baa553f65..e91222c8e 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt @@ -7,12 +7,17 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException /** * QQ 对象. @@ -85,4 +90,16 @@ actual abstract class QQ : Contact(), CoroutineScope { @JvmSynthetic actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmSynthetic + @JvmName("uploadImageSuspend") + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage } \ No newline at end of file