diff --git a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api index d6f64b16c..1518f082d 100644 --- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api +++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api @@ -4445,9 +4445,14 @@ public abstract interface class net/mamoe/mirai/message/data/Image : net/mamoe/m public static fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public static fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; public abstract fun getImageType ()Lnet/mamoe/mirai/message/data/ImageType; + public fun getMd5 ()[B public abstract fun getSize ()J public abstract fun getWidth ()I public fun isEmoji ()Z + public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z + public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z + public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -4463,10 +4468,15 @@ public final class net/mamoe/mirai/message/data/Image$AsStringSerializer : kotli public final class net/mamoe/mirai/message/data/Image$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public static final field SERIAL_NAME Ljava/lang/String; + public final fun calculateImageMd5ByImageId (Ljava/lang/String;)[B public final fun fromId (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun getImageIdRegex ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; + public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z + public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z + public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -4488,6 +4498,7 @@ public final class net/mamoe/mirai/message/data/ImageType : java/lang/Enum { public static final field JPG Lnet/mamoe/mirai/message/data/ImageType; public static final field PNG Lnet/mamoe/mirai/message/data/ImageType; public static final field UNKNOWN Lnet/mamoe/mirai/message/data/ImageType; + public final fun getFormatName ()Ljava/lang/String; public static final fun match (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static final fun matchOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; @@ -4853,7 +4864,7 @@ public final class net/mamoe/mirai/message/data/MessageUtils { public static final fun buildMessageChain (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/IMirai;JLnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; - public static final fun calculateImageMd5 (Lnet/mamoe/mirai/message/data/Image;)[B + public static final synthetic fun calculateImageMd5 (Lnet/mamoe/mirai/message/data/Image;)[B public static final fun contentsList (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/util/List; public static final synthetic fun contentsSequence (Lnet/mamoe/mirai/message/data/MessageChain;)Lkotlin/sequences/Sequence; public static final fun copySource (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api index 371a7128f..ed909966c 100644 --- a/binary-compatibility-validator/api/binary-compatibility-validator.api +++ b/binary-compatibility-validator/api/binary-compatibility-validator.api @@ -4445,9 +4445,14 @@ public abstract interface class net/mamoe/mirai/message/data/Image : net/mamoe/m public static fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public static fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; public abstract fun getImageType ()Lnet/mamoe/mirai/message/data/ImageType; + public fun getMd5 ()[B public abstract fun getSize ()J public abstract fun getWidth ()I public fun isEmoji ()Z + public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z + public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z + public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -4463,10 +4468,15 @@ public final class net/mamoe/mirai/message/data/Image$AsStringSerializer : kotli public final class net/mamoe/mirai/message/data/Image$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public static final field SERIAL_NAME Ljava/lang/String; + public final fun calculateImageMd5ByImageId (Ljava/lang/String;)[B public final fun fromId (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun getImageIdRegex ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; + public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z + public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z + public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -4488,6 +4498,7 @@ public final class net/mamoe/mirai/message/data/ImageType : java/lang/Enum { public static final field JPG Lnet/mamoe/mirai/message/data/ImageType; public static final field PNG Lnet/mamoe/mirai/message/data/ImageType; public static final field UNKNOWN Lnet/mamoe/mirai/message/data/ImageType; + public final fun getFormatName ()Ljava/lang/String; public static final fun match (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static final fun matchOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; @@ -4853,7 +4864,7 @@ public final class net/mamoe/mirai/message/data/MessageUtils { public static final fun buildMessageChain (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/IMirai;JLnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; - public static final fun calculateImageMd5 (Lnet/mamoe/mirai/message/data/Image;)[B + public static final synthetic fun calculateImageMd5 (Lnet/mamoe/mirai/message/data/Image;)[B public static final fun contentsList (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/util/List; public static final synthetic fun contentsSequence (Lnet/mamoe/mirai/message/data/MessageChain;)Lkotlin/sequences/Sequence; public static final fun copySource (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt index 928761ef2..2725ae560 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt @@ -100,7 +100,7 @@ public interface Image : Message, MessageContent, CodableMessage { public val height: Int /** - * 图片的大小(字节), 当无法获取时为 0 + * 图片的大小(字节), 当无法获取时为 0. 可用于 [isUploaded]. * * @since 2.8.0 */ @@ -122,6 +122,15 @@ public interface Image : Message, MessageContent, CodableMessage { */ public val isEmoji: Boolean get() = false + /** + * 图片文件 MD5. 可用于 [isUploaded]. + * + * @return 16 bytes + * @see isUploaded + * @since 2.9.0 + */ // was an extension on Image before 2.9.0-M1. + public val md5: ByteArray get() = calculateImageMd5ByImageId(imageId) + public object AsStringSerializer : KSerializer by String.serializer().mapPrimitive( SERIAL_NAME, serialize = { imageId }, @@ -143,6 +152,7 @@ public interface Image : Message, MessageContent, CodableMessage { ) } + @JvmBlockingBridge public companion object Key : AbstractMessageKey({ it.safeCast() }) { public const val SERIAL_NAME: String = "Image" @@ -166,12 +176,56 @@ public interface Image : Message, MessageContent, CodableMessage { * @throws IllegalStateException 当无任何 [Bot] 在线时抛出 (因为无法获取相关协议) */ @JvmStatic - @JvmBlockingBridge public suspend fun Image.queryUrl(): String { val bot = Bot.instancesSequence.firstOrNull() ?: error("No Bot available to query image url") return Mirai.queryImageUrl(bot, this) } + /** + * 当图片在服务器上存在时返回 `true`, 这意味着图片可以直接发送给 [contact]. + * + * 若返回 `false`, 则图片需要用 [ExternalResource] 重新上传 ([Contact.uploadImage]). + * + * @since 2.9.0 + */ + @JvmStatic + public suspend fun Image.isUploaded(bot: Bot): Boolean = + InternalImageProtocol.instance.isUploaded(bot, md5, size, null, imageType, width, height) + + /** + * 当图片在服务器上存在时返回 `true`, 这意味着图片可以直接发送给 [contact]. + * + * 若返回 `false`, 则图片需要用 [ExternalResource] 重新上传 ([Contact.uploadImage]). + * + * @param md5 图片文件 MD5. 可通过 [Image.md5] 获得. + * @param size 图片文件大小. + * + * @since 2.9.0 + */ + @JvmStatic + public suspend fun isUploaded( + bot: Bot, + md5: ByteArray, + size: Long, + ): Boolean = InternalImageProtocol.instance.isUploaded(bot, md5, size, null) + + /** + * 由 [Image.imageId] 计算 [Image.md5]. + * + * @since 2.9.0 + */ + public fun calculateImageMd5ByImageId(imageId: String): ByteArray { + @Suppress("DEPRECATION") + return when { + imageId matches IMAGE_ID_REGEX -> imageId.imageIdToMd5(1) + imageId matches IMAGE_RESOURCE_ID_REGEX_2 -> imageId.imageIdToMd5(imageId.skipToSecondHyphen() + 1) + imageId matches IMAGE_RESOURCE_ID_REGEX_1 -> imageId.imageIdToMd5(1) + + else -> throw IllegalArgumentException( + "Illegal imageId: '$imageId'. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE" + ) + } + } /** * 统一 ID 正则表达式 @@ -222,17 +276,24 @@ public interface Image : Message, MessageContent, CodableMessage { @JvmSynthetic public inline fun Image(imageId: String): Image = Image.fromId(imageId) -public enum class ImageType { - PNG, - BMP, - JPG, - GIF, +public enum class ImageType( + /** + * @since 2.9.0 + */ + @MiraiInternalApi public val formatName: String, +) { + PNG("png"), + BMP("bmp"), + JPG("jpg"), + GIF("gif"), + //WEBP, //Unsupported by pc client - APNG, - UNKNOWN; + APNG("png"), + UNKNOWN("gif"); // bad design, should use `null` to represent unknown, but we cannot change it anymore. public companion object { private val IMAGE_TYPE_ENUM_LIST = values() + @JvmStatic public fun match(str: String): ImageType { return matchOrNull(str) ?: UNKNOWN @@ -250,15 +311,12 @@ public enum class ImageType { // Internals /////////////////////////////////////////////////////////////////////////// -/** - * 计算图片的 md5 校验值. - * - * 在 Java 使用: `MessageUtils.calculateImageMd5(image)` - */ +@Deprecated("Use member function", level = DeprecationLevel.HIDDEN) // safe since it was internal +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") @MiraiInternalApi @get:JvmName("calculateImageMd5") public val Image.md5: ByteArray - get() = calculateImageMd5ByImageId(imageId) + get() = Image.calculateImageMd5ByImageId(imageId) /** @@ -320,4 +378,35 @@ public abstract class FriendImage @MiraiInternalApi public constructor() : public abstract class GroupImage @MiraiInternalApi public constructor() : AbstractImage() { // change to sealed in the future. public companion object -} \ No newline at end of file +} + + +/** + * 内部图片协议实现 + * @since 2.9.0-M1 + */ +@MiraiInternalApi +public interface InternalImageProtocol { // naming it Internal* to assign it a lower priority when resolving Image* + /** + * @param context 用于检查的 [Contact]. 群图片与好友图片是两个通道, 建议使用欲发送到的 [Contact] 对象作为 [contact] 参数, 但目前不提供此参数时也可以检查. + */ + public suspend fun isUploaded( + bot: Bot, + md5: ByteArray, + size: Long, + context: Contact? = null, + type: ImageType = ImageType.UNKNOWN, + width: Int = 0, + height: Int = 0 + ): Boolean + + @MiraiInternalApi + public companion object { + public val instance: InternalImageProtocol by lazy { + loadService( + InternalImageProtocol::class, + "net.mamoe.mirai.internal.message.InternalImageProtocolImpl" + ) + } + } +} diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt b/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt index 6ba0510a6..d852cc37f 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/impl.kt @@ -20,7 +20,6 @@ import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2 import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.asImmutable import net.mamoe.mirai.utils.replaceAllKotlin -import kotlin.native.concurrent.SharedImmutable // region image @@ -201,7 +200,6 @@ internal fun MessageChainImplBySequence( ////////////////////// -@SharedImmutable @get:JvmSynthetic internal val EMPTY_BYTE_ARRAY = ByteArray(0) @@ -247,20 +245,6 @@ internal fun String.imageIdToMd5(offset: Int): ByteArray { error("Internal error: failed imageIdToMd5, no enough chars. Input=$this, offset=$offset") } -@OptIn(ExperimentalStdlibApi::class) -internal fun calculateImageMd5ByImageId(imageId: String): ByteArray { - @Suppress("DEPRECATION") - return when { - imageId matches IMAGE_ID_REGEX -> imageId.imageIdToMd5(1) - imageId matches IMAGE_RESOURCE_ID_REGEX_2 -> imageId.imageIdToMd5(imageId.skipToSecondHyphen() + 1) - imageId matches IMAGE_RESOURCE_ID_REGEX_1 -> imageId.imageIdToMd5(1) - - else -> error( - "illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE" - ) - } -} - internal val ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE: String = "ImageId must match Regex `${IMAGE_RESOURCE_ID_REGEX_1.pattern}`, " + "`${IMAGE_RESOURCE_ID_REGEX_2.pattern}` or " + diff --git a/mirai-core-api/src/commonTest/kotlin/message.data/ImageTest.kt b/mirai-core-api/src/commonTest/kotlin/message.data/ImageTest.kt index 084a71cca..14921ad40 100644 --- a/mirai-core-api/src/commonTest/kotlin/message.data/ImageTest.kt +++ b/mirai-core-api/src/commonTest/kotlin/message.data/ImageTest.kt @@ -1,10 +1,10 @@ /* - * Copyright 2019-2020 Mamoe Technologies and contributors. + * Copyright 2019-2021 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. + * 此源代码的使用受 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/master/LICENSE + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") @@ -35,17 +35,17 @@ internal class ImageTest { fun testCalculateImageMd5ByImageId() { assertEquals( "01E9451B-70ED-EAE3-B37C-101F1EEBF5B5".filterNot { it == '-' }.autoHexToBytes().contentToString(), - calculateImageMd5ByImageId("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai").contentToString() + Image.calculateImageMd5ByImageId("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai").contentToString() ) assertEquals( "f8f1ab55-bf8e-4236-b55e-955848d7069f".filterNot { it == '-' }.autoHexToBytes().contentToString(), - calculateImageMd5ByImageId("/f8f1ab55-bf8e-4236-b55e-955848d7069f").contentToString() + Image.calculateImageMd5ByImageId("/f8f1ab55-bf8e-4236-b55e-955848d7069f").contentToString() ) assertEquals( "BFB7027B9354B8F899A062061D74E206".filterNot { it == '-' }.autoHexToBytes().contentToString(), - calculateImageMd5ByImageId("/000000000-3814297509-BFB7027B9354B8F899A062061D74E206").contentToString() + Image.calculateImageMd5ByImageId("/000000000-3814297509-BFB7027B9354B8F899A062061D74E206").contentToString() ) } diff --git a/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt b/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt new file mode 100644 index 000000000..373e41e49 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/message/InternalImageProtocolImpl.kt @@ -0,0 +1,159 @@ +/* + * Copyright 2019-2021 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.message + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.User +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.asQQAndroidBot +import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 +import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore +import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn +import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect +import net.mamoe.mirai.message.data.ImageType +import net.mamoe.mirai.message.data.InternalImageProtocol +import net.mamoe.mirai.utils.cast +import net.mamoe.mirai.utils.toUHexString + +internal class InternalImageProtocolImpl : InternalImageProtocol { + + /** + * Test Notes: + * + * - 查图片只需要 md5 和 size + * - 上传给群的图片可以通过 GroupPicUp(groupCode=user.id) 或 OffPicUp(dstUin=user.id) 查询 + * - 上传给好友的图片可以通过 GroupPicUp(groupCode=group.id) 或 OffPicUp(dstUin=group.id) 查询 + */ + fun interface ImageUploadedChecker { + suspend fun isUploaded( + bot: QQAndroidBot, + context: C, + md5: ByteArray, + type: ImageType, + size: Long, + width: Int, + height: Int + ): Boolean + + companion object { + val checkers = mapOf( + Group::class to ImageUploadedCheckerGroup(), + User::class to ImageUploadedCheckerUser(), + null to ImageUploadedCheckerFallback() + ) + } + } + + class ImageUploadedCheckerGroup : ImageUploadedChecker { + override suspend fun isUploaded( + bot: QQAndroidBot, + context: Group, + md5: ByteArray, + type: ImageType, + size: Long, + width: Int, + height: Int + ): Boolean { + val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp( + bot.client, + uin = bot.id, + groupCode = context.id, + md5 = md5, + size = size, + filename = "${md5.toUHexString("")}.${type.formatName}", + picWidth = width, + picHeight = height, + picType = getIdByImageType(type), + originalPic = 1 + ).sendAndExpect(bot) + + return response is ImgStore.GroupPicUp.Response.FileExists + } + } + + class ImageUploadedCheckerUser : ImageUploadedChecker { + override suspend fun isUploaded( + bot: QQAndroidBot, + context: User, + md5: ByteArray, + type: ImageType, + size: Long, + width: Int, + height: Int + ): Boolean { + val resp = LongConn.OffPicUp( + bot.client, + Cmd0x352.TryUpImgReq( + buType = 1, + srcUin = bot.id, + dstUin = context.id, + fileMd5 = md5, + fileSize = size, + imgWidth = width, + imgHeight = height, + imgType = getIdByImageType(type), + fileName = "${md5.toUHexString("")}.${type.formatName}", + imgOriginal = true, + buildVer = bot.client.buildVer, + ), + ).sendAndExpect(bot) + + return resp is LongConn.OffPicUp.Response.FileExists + } + } + + class ImageUploadedCheckerFallback : ImageUploadedChecker { + override suspend fun isUploaded( + bot: QQAndroidBot, + context: Nothing?, + md5: ByteArray, + type: ImageType, + size: Long, + width: Int, + height: Int + ): Boolean { + val response: ImgStore.GroupPicUp.Response = ImgStore.GroupPicUp( + bot.client, + uin = bot.id, + groupCode = 1, + md5 = md5, + size = size, + filename = "${md5.toUHexString("")}.${type.formatName}", + picWidth = width, + picHeight = height, + picType = getIdByImageType(type), + originalPic = 1 + ).sendAndExpect(bot) + + return response is ImgStore.GroupPicUp.Response.FileExists + } + } + + override suspend fun isUploaded( + bot: Bot, + md5: ByteArray, + size: Long, + context: Contact?, + type: ImageType, + width: Int, + height: Int + ): Boolean { + val checker = findChecker(context) ?: return false + checker.cast>() + return checker.isUploaded(bot.asQQAndroidBot(), context.cast(), md5, type, size, width, height) + } + + fun findChecker(context: Contact?) = ImageUploadedChecker.checkers.asSequence() + .find { bothNull(it.key, context) || it.key?.isInstance(context) == true }?.value + + private fun bothNull(a: Any?, b: Any?) = a == null && b == null +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.message.data.InternalImageProtocol b/mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.message.data.InternalImageProtocol new file mode 100644 index 000000000..7d468c002 --- /dev/null +++ b/mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.message.data.InternalImageProtocol @@ -0,0 +1,10 @@ +# +# Copyright 2019-2021 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 +# + +net.mamoe.mirai.internal.message.InternalImageProtocolImpl \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/message/InternalImageProtocolImplTest.kt b/mirai-core/src/commonTest/kotlin/message/InternalImageProtocolImplTest.kt new file mode 100644 index 000000000..c05fccfcb --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/message/InternalImageProtocolImplTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019-2021 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.message + +import net.mamoe.mirai.internal.notice.processors.AbstractNoticeProcessorTest +import kotlin.test.Test +import kotlin.test.assertIs + +internal class InternalImageProtocolImplTest : AbstractNoticeProcessorTest() { // borrow Bot testkit + val instance = InternalImageProtocolImpl() + + @Test + fun testFindChecker() { + assertIs(instance.findChecker(setBot(1).addGroup(2, 3))) + assertIs(instance.findChecker(setBot(1).addFriend(2))) + assertIs(instance.findChecker(null)) + + // these 3 tests are complete -- no need to add more when adding more checkers. + } +} \ No newline at end of file