diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt
index b270b3858..dbef3f8c5 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt
@@ -720,6 +720,7 @@ internal abstract class QQAndroidBotBase constructor(
         }
     }
 
+    @Suppress("DEPRECATION")
     override suspend fun queryImageUrl(image: Image): String = when (image) {
         is OnlineFriendImageImpl -> image.originUrl
         is OnlineGroupImageImpl -> image.originUrl
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt
index ecfab54db..75d86d63f 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt
@@ -25,8 +25,8 @@ 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.message.MessageReceipt
+import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.message.data.Message
-import net.mamoe.mirai.message.data.OfflineFriendImage
 import net.mamoe.mirai.message.data.isContentNotEmpty
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.network.highway.postImage
@@ -86,7 +86,7 @@ internal class FriendImpl(
 
     @JvmSynthetic
     @OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class)
-    override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try {
+    override suspend fun uploadImage(image: ExternalImage): Image = try {
         if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
             throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
         }
@@ -103,11 +103,12 @@ internal class FriendImpl(
                 )
             ).sendAndExpect<LongConn.OffPicUp.Response>()
 
-            @Suppress("UNCHECKED_CAST") // bug
+            @Suppress("UNCHECKED_CAST", "DEPRECATION") // bug
             return when (response) {
-                is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(response.resourceId).also {
-                    ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast()
-                }
+                is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId)
+                    .also {
+                        ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast()
+                    }
                 is LongConn.OffPicUp.Response.RequireUpload -> {
                     bot.network.logger.verbose {
                         "[Http] Uploading friend image, size=${image.inputSize.sizeToString()}"
@@ -139,7 +140,7 @@ internal class FriendImpl(
                     )*/
                     // 为什么不能 ??
 
-                    return OfflineFriendImage(response.resourceId).also {
+                    return net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId).also {
                         ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast()
                     }
                 }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt
index 62f74b828..da8048897 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt
@@ -399,6 +399,7 @@ internal class GroupImpl(
         return MessageReceipt(source, this, botAsMember)
     }
 
+    @Suppress("DEPRECATION")
     @OptIn(ExperimentalTime::class)
     @JvmSynthetic
     override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage = try {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt
index 8f0ecbbf4..a5f0350cf 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt
@@ -24,8 +24,8 @@ import net.mamoe.mirai.event.events.MemberCardChangeEvent
 import net.mamoe.mirai.event.events.MemberLeaveEvent
 import net.mamoe.mirai.event.events.MemberSpecialTitleChangeEvent
 import net.mamoe.mirai.message.MessageReceipt
+import net.mamoe.mirai.message.data.Image
 import net.mamoe.mirai.message.data.Message
-import net.mamoe.mirai.message.data.OfflineFriendImage
 import net.mamoe.mirai.message.data.asMessageChain
 import net.mamoe.mirai.message.data.isContentNotEmpty
 import net.mamoe.mirai.qqandroid.QQAndroidBot
@@ -81,7 +81,7 @@ internal class MemberImpl constructor(
     }
 
     @JvmSynthetic
-    override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = qq.uploadImage(image)
+    override suspend fun uploadImage(image: ExternalImage): Image = qq.uploadImage(image)
 
     override var permission: MemberPermission = memberInfo.permission
 
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt
index 31ae7e9c3..c4f22992f 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt
@@ -129,10 +129,14 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
                 )
                 transformOneMessage(UNSUPPORTED_POKE_MESSAGE_PLAIN)
             }
-            is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
+            is @Suppress("DEPRECATION")
+            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 @Suppress("DEPRECATION")
+            OfflineFriendImage
+            -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
             is GroupFlashImage -> elements.add(it.toJceData())
                 .also { transformOneMessage(UNSUPPORTED_FLASH_MESSAGE_PLAIN) }
             is FriendFlashImage -> elements.add(it.toJceData())
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt
index 3e8356b6a..c00b02f26 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/imagesImpl.kt
@@ -16,7 +16,8 @@ import net.mamoe.mirai.utils.ExternalImage
 
 internal class OnlineGroupImageImpl(
     internal val delegate: ImMsgBody.CustomFace
-) : OnlineGroupImage() {
+) : @Suppress("DEPRECATION")
+OnlineGroupImage() {
     override val imageId: String = ExternalImage.generateImageId(delegate.md5)
     override val originUrl: String
         get() = "http://gchat.qpic.cn" + delegate.origUrl
@@ -32,7 +33,8 @@ internal class OnlineGroupImageImpl(
 
 internal class OnlineFriendImageImpl(
     internal val delegate: ImMsgBody.NotOnlineImage
-) : OnlineFriendImage() {
+) : @Suppress("DEPRECATION")
+OnlineFriendImage() {
     override val imageId: String get() = delegate.resId
     override val originUrl: String
         get() = if (delegate.origUrl.isNotEmpty()) {
@@ -51,6 +53,7 @@ internal class OnlineFriendImageImpl(
     }
 }
 
+@Suppress("DEPRECATION")
 internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace {
     return ImMsgBody.CustomFace(
         filePath = this.imageId,
@@ -67,6 +70,7 @@ private val oldData: ByteArray =
     "15 36 20 39 32 6B 41 31 00 38 37 32 66 30 36 36 30 33 61 65 31 30 33 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 30 31 45 39 34 35 31 42 2D 37 30 45 44 2D 45 41 45 33 2D 42 33 37 43 2D 31 30 31 46 31 45 45 42 46 35 42 35 7D 2E 70 6E 67 41".hexToBytes()
 
 
+@Suppress("DEPRECATION")
 internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage {
     return ImMsgBody.NotOnlineImage(
         filePath = this.imageId,
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt
index 1ea7feb39..b73f0479b 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt
@@ -146,7 +146,7 @@ internal class MessageSourceToGroupImpl(
             type = 0,
             time = time,
             pbReserve = SourceMsg.ResvAttr(
-                origUids = id.toLong() and 0xffFFffFF // id is actually messageRandom
+                origUids = internalId.toLong() and 0xffFFffFF // id is actually messageRandom
             ).toByteArray(SourceMsg.ResvAttr.serializer()),
             srcMsg = MsgComm.Msg(
                 msgHead = MsgComm.MsgHead(
@@ -156,7 +156,7 @@ internal class MessageSourceToGroupImpl(
                     c2cCmd = 1,
                     msgSeq = sequenceId,
                     msgTime = time,
-                    msgUid = id.toLong() and 0xffFFffFF, // ok
+                    msgUid = internalId.toLong() and 0xffFFffFF, // ok
                     groupInfo = MsgComm.GroupInfo(groupCode = targetId),
                     isSrcMsg = true
                 ),
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 71959069d..ac0ea3dd1 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
@@ -89,7 +89,7 @@ abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot
      * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时抛出. (最大大小约为 20 MB, 但 mirai 限制的大小为 30 MB)
      */
     @JvmSynthetic
-    abstract suspend fun uploadImage(image: ExternalImage): OfflineImage
+    abstract suspend fun uploadImage(image: ExternalImage): Image
 
     final override fun equals(other: Any?): Boolean = super.equals(other)
     final override fun hashCode(): Int = super.hashCode()
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 11a38f1e4..4957d9dd2 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
@@ -20,8 +20,8 @@ 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.Image
 import net.mamoe.mirai.message.data.Message
-import net.mamoe.mirai.message.data.OfflineGroupImage
 import net.mamoe.mirai.message.data.toMessage
 import net.mamoe.mirai.utils.*
 import net.mamoe.mirai.utils.internal.runBlocking
@@ -172,7 +172,7 @@ abstract class Group : Contact(), CoroutineScope {
      * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
      */
     @JvmSynthetic
-    abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage
+    abstract override suspend fun uploadImage(image: ExternalImage): Image
 
     companion object {
         /**
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt
index 7db56e1cc..dd5e9d2d5 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt
@@ -19,8 +19,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.Message
-import net.mamoe.mirai.message.data.OfflineFriendImage
 import net.mamoe.mirai.message.data.toMessage
 import net.mamoe.mirai.utils.ExternalImage
 import net.mamoe.mirai.utils.OverFileSizeMaxException
@@ -91,7 +91,7 @@ abstract class User : Contact(), CoroutineScope {
      * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
      */
     @JvmSynthetic
-    abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage
+    abstract override suspend fun uploadImage(image: ExternalImage): Image
 
     abstract override fun toString(): String
 }
\ 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 3e239dd3f..48a81f6b5 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
@@ -10,7 +10,7 @@
 @file:JvmMultifileClass
 @file:JvmName("MessageUtils")
 
-@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
+@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "WRONG_MODIFIER_CONTAINING_DECLARATION", "DEPRECATION")
 
 package net.mamoe.mirai.message.data
 
@@ -81,9 +81,50 @@ expect interface Image : Message, MessageContent {
     @Deprecated("""
         不要自行实现 Image, 它必须由协议模块实现, 否则会无法发送也无法解析.
     """, level = DeprecationLevel.HIDDEN)
-    @Suppress("PropertyName", "DeprecatedCallableAddReplaceWith")
+    @Suppress("PropertyName")
     @get:JvmSynthetic
-    val DoNotImplementThisClass: Nothing?
+    internal val DoNotImplementThisClass: Nothing?
+}
+
+/**
+ * 群图片.
+ *
+ * 群拖
+ *
+ * @property imageId 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 `".mirai"`)
+ * @see Image 查看更多说明
+ */
+@Suppress("DEPRECATION_ERROR")
+// CustomFace
+@OptIn(MiraiInternalAPI::class)
+sealed class GroupImage : AbstractImage() {
+    companion object Key : Message.Key<GroupImage> {
+        override val typeName: String get() = "GroupImage"
+    }
+}
+
+/**
+ * 计算图片的 md5 校验值.
+ *
+ * 在 Java 使用: `MessageUtils.calculateImageMd5(image)`
+ */
+@get:JvmName("calculateImageMd5")
+@SinceMirai("0.39.0")
+val Image.md5: ByteArray
+    get() = calculateImageMd5ByImageId(imageId)
+
+
+/**
+ * 好友图片
+ *
+ * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度)  或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度)
+ */ // NotOnlineImage
+@Suppress("DEPRECATION_ERROR")
+@OptIn(MiraiInternalAPI::class)
+sealed class FriendImage : AbstractImage() {
+    companion object Key : Message.Key<FriendImage> {
+        override val typeName: String get() = "FriendImage"
+    }
 }
 
 /**
@@ -148,29 +189,18 @@ fun Image(imageId: String): OfflineImage = when {
     else -> throw IllegalArgumentException("Illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE")
 }
 
-// region 在线图片
-
-/**
- * 在服务器上的图片. 它可以直接获取下载链接.
- *
- * 一般由 [Contact.uploadImage] 得到
- */
-interface OnlineImage : Image {
-    companion object Key : Message.Key<OnlineImage> {
-        override val typeName: String get() = "OnlineImage"
-    }
-
-    /**
-     * 原图下载链接. 包含域名
-     */
-    val originUrl: String
-}
-
 /**
  * 查询原图下载链接.
+ *
+ * 当图片为从服务器接收的消息中的图片时, 可以直接获取下载链接, 本函数不会挂起协程.
+ * 其他情况下协程可能会挂起并向服务器查询下载链接, 或不挂起并拼接一个链接.
+ *
+ * @return 原图 HTTP 下载链接 (非 HTTPS)
+ * @throws IllegalStateException 当无任何 [Bot] 在线时抛出 (因为无法获取相关协议)
  */
 @JvmSynthetic
 suspend fun Image.queryUrl(): String {
+    @Suppress("DEPRECATION")
     @OptIn(MiraiInternalAPI::class)
     return when (this) {
         is OnlineImage -> this.originUrl
@@ -179,10 +209,32 @@ suspend fun Image.queryUrl(): String {
     }
 }
 
-// endregion 在线图片
+
+/////////////////////////
+///// 以下 API 已弃用 /////
+/////////////////////////
 
 
-// region 离线图片
+// region 已启用
+
+internal const val ONLINE_OFFLINE_DEPRECATION_MESSAGE = """
+自 1.0.0 起, mirai 已经能正常处理离线图片和在线图片的下载链接等功能. 
+使用者无需考虑一个图片为在线图片还是离线图片, 只需使用 Image 类型.
+"""
+
+
+@PlannedRemoval("1.2.0") // 改为 internal
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.WARNING,
+    replaceWith = ReplaceWith("Image", "net.mamoe.mirai.message.data.Image")
+)
+interface OnlineImage : Image {
+    companion object Key : Message.Key<OnlineImage> {
+        override val typeName: String get() = "OnlineImage"
+    }
+
+    val originUrl: String
+}
 
 /**
  * 离线的图片, 即为客户端主动上传到服务器而获得的 [Image] 实例.
@@ -190,45 +242,37 @@ suspend fun Image.queryUrl(): String {
  *
  * 一般由 [Contact.uploadImage] 得到
  */
+@PlannedRemoval("1.2.0") // 改为 internal
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.WARNING,
+    replaceWith = ReplaceWith("Image", "net.mamoe.mirai.message.data.Image")
+)
 interface OfflineImage : Image {
     companion object Key : Message.Key<OfflineImage> {
         override val typeName: String get() = "OfflineImage"
     }
 }
 
-/**
- * 原图下载链接. 包含域名
- */
+@PlannedRemoval("1.2.0") // 删除
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.HIDDEN
+)
 @JvmSynthetic
 suspend fun OfflineImage.queryUrl(): String {
     @OptIn(MiraiInternalAPI::class)
     return BotImpl.instances.peekFirst().get()?.queryImageUrl(this) ?: error("No Bot available to query image url")
 }
 
-// endregion 离线图片
-
-// region 群图片
-
-
-/**
- * 群图片.
- *
- * [imageId] 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (45 长度)
- */
-@Suppress("DEPRECATION_ERROR")
-// CustomFace
-@OptIn(MiraiInternalAPI::class)
-sealed class GroupImage : AbstractImage() {
-    companion object Key : Message.Key<GroupImage> {
-        override val typeName: String get() = "GroupImage"
-    }
-}
-
 /**
  * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl]
  *
  * @param imageId 参考 [Image.imageId]
  */
+@PlannedRemoval("1.2.0") // 改为 internal
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.WARNING,
+    replaceWith = ReplaceWith("Image", "net.mamoe.mirai.message.data.Image")
+)
 @Serializable
 data class OfflineGroupImage(
     override val imageId: String
@@ -241,40 +285,26 @@ data class OfflineGroupImage(
     }
 }
 
-@get:JvmName("calculateImageMd5")
-@SinceMirai("0.39.0")
-val Image.md5: ByteArray
-    get() = calculateImageMd5ByImageId(imageId)
-
 /**
  * 接收消息时获取到的 [GroupImage]. 它可以直接获取下载链接 [originUrl]
  */
+@PlannedRemoval("1.2.0") // 改为 internal
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.WARNING,
+    replaceWith = ReplaceWith("Image", "net.mamoe.mirai.message.data.Image")
+)
 abstract class OnlineGroupImage : GroupImage(), OnlineImage
 
-
-// endregion 群图片
-
-// region 好友图片
-
-
-/**
- * 好友图片
- *
- * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度)  或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度)
- */ // NotOnlineImage
-@Suppress("DEPRECATION_ERROR")
-@OptIn(MiraiInternalAPI::class)
-sealed class FriendImage : AbstractImage() {
-    companion object Key : Message.Key<FriendImage> {
-        override val typeName: String get() = "FriendImage"
-    }
-}
-
 /**
  * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl]
  *
  * @param imageId 参考 [Image.imageId]
  */
+@PlannedRemoval("1.2.0") // 改为 internal
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.WARNING,
+    replaceWith = ReplaceWith("Image", "net.mamoe.mirai.message.data.Image")
+)
 @Serializable
 data class OfflineFriendImage(
     override val imageId: String
@@ -289,6 +319,11 @@ data class OfflineFriendImage(
 /**
  * 接收消息时获取到的 [FriendImage]. 它可以直接获取下载链接 [originUrl]
  */
+@PlannedRemoval("1.2.0") // 改为 internal
+@Deprecated(ONLINE_OFFLINE_DEPRECATION_MESSAGE,
+    level = DeprecationLevel.WARNING,
+    replaceWith = ReplaceWith("Image", "net.mamoe.mirai.message.data.Image")
+)
 abstract class OnlineFriendImage : FriendImage(), OnlineImage
 
 // endregion
@@ -326,6 +361,7 @@ sealed class AbstractImage : Image {
             field = "[mirai:image:$imageId]"
             field
         }
+
     final override fun toString(): String = _stringValue!!
     final override fun contentToString(): String = "[图片]"
 }
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt
index b33d48118..dffe346e7 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt
@@ -25,6 +25,7 @@ import kotlin.jvm.JvmSynthetic
 
 /**
  * 可发送的或从服务器接收的消息.
+ *
  * 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等.
  *
  * [消息][Message] 分为
@@ -37,20 +38,20 @@ import kotlin.jvm.JvmSynthetic
  * 这与使用 [String] 的使用非常类似.
  *
  * 比较 [SingleMessage] 与 [String]:
- *  `if(message.contentToString() == "你好") qq.sendMessage(event)`
+ *  `if(message.contentToString() == "你好") friend.sendMessage(event)`
  *
- * 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]):
- *  ```kotlin
+ * 连接 [Message] 与 [Message], [String], (使用操作符 [Message.plus]):
+ *  ```
  *      text = PlainText("Hello ")
  *      qq.sendMessage(text + "world")
  *  ```
  *
  * `Message1 + Message2 + Message3`, 类似 [String] 的连接:
- *
+ * ```
  *   +----------+   plus  +----------+   plus  +----------+
  *   | Message1 | <------ | Message2 | <------ | Message3 |
  *   +----------+         +----------+         +----------+
- *
+ * ```
  *
  * 但注意: 不能 `String + Message`. 只能 `Message + String`
  *
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt
index 7e7f01bbe..6a2e0f432 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt
@@ -198,7 +198,7 @@ internal inline fun <T> List<T>.indexOfFirst(offset: Int, predicate: (T) -> Bool
 
 @OptIn(MiraiExperimentalAPI::class)
 @JvmSynthetic
-@Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR")
+@Suppress("UNCHECKED_CAST", "DEPRECATION_ERROR", "DEPRECATION")
 internal fun <M : Message> MessageChain.firstOrNullImpl(key: Message.Key<M>): M? = when (key) {
     At -> firstIsInstanceOrNull<At>()
     AtAll -> firstIsInstanceOrNull<AtAll>()
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt
index baf377551..03f7fad91 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt
@@ -21,7 +21,6 @@ import net.mamoe.mirai.contact.Group
 import net.mamoe.mirai.contact.User
 import net.mamoe.mirai.message.MessageReceipt
 import net.mamoe.mirai.message.data.Image
-import net.mamoe.mirai.message.data.OfflineImage
 import net.mamoe.mirai.message.data.sendTo
 import net.mamoe.mirai.message.data.toLongUnsigned
 import kotlin.jvm.JvmSynthetic
@@ -117,7 +116,7 @@ suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> =
  * @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人
  */
 @JvmSynthetic
-suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) {
+suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) {
     is Group -> contact.uploadImage(this)
     is User -> contact.uploadImage(this)
     else -> error("unreachable")
@@ -129,6 +128,8 @@ suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact
 @JvmSynthetic
 suspend inline fun <C : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = image.sendTo(this)
 
+
+@JvmSynthetic
 internal operator fun ByteArray.get(rangeStart: Int, rangeEnd: Int): String = buildString {
     for (it in rangeStart..rangeEnd) {
         append(this@get[it].fixToString())
diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt
index 31bfecdeb..c01a42aa9 100644
--- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt
+++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt
@@ -16,7 +16,6 @@ import kotlinx.coroutines.withContext
 import kotlinx.io.core.Input
 import net.mamoe.mirai.contact.Contact
 import net.mamoe.mirai.message.data.Image
-import net.mamoe.mirai.message.data.OfflineImage
 import net.mamoe.mirai.utils.OverFileSizeMaxException
 import net.mamoe.mirai.utils.sendTo
 import net.mamoe.mirai.utils.toExternalImage
@@ -82,8 +81,9 @@ suspend fun <C : Contact> File.sendAsImageTo(contact: C): MessageReceipt<C> {
  * 在 [Dispatchers.IO] 中将图片上传后构造 [Image]. 不会创建临时文件
  * @throws OverFileSizeMaxException
  */
+@JvmSynthetic
 @Throws(OverFileSizeMaxException::class)
-suspend fun BufferedImage.upload(contact: Contact): OfflineImage =
+suspend fun BufferedImage.upload(contact: Contact): Image =
     withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
 
 /**
@@ -91,7 +91,7 @@ suspend fun BufferedImage.upload(contact: Contact): OfflineImage =
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend fun URL.uploadAsImage(contact: Contact): OfflineImage =
+suspend fun URL.uploadAsImage(contact: Contact): Image =
     withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
 
 /**
@@ -99,7 +99,7 @@ suspend fun URL.uploadAsImage(contact: Contact): OfflineImage =
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend fun Input.uploadAsImage(contact: Contact): OfflineImage =
+suspend fun Input.uploadAsImage(contact: Contact): Image =
     withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
 
 /**
@@ -107,7 +107,7 @@ suspend fun Input.uploadAsImage(contact: Contact): OfflineImage =
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend fun InputStream.uploadAsImage(contact: Contact): OfflineImage =
+suspend fun InputStream.uploadAsImage(contact: Contact): Image =
     withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
 
 /**
@@ -115,7 +115,7 @@ suspend fun InputStream.uploadAsImage(contact: Contact): OfflineImage =
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend fun File.uploadAsImage(contact: Contact): OfflineImage {
+suspend fun File.uploadAsImage(contact: Contact): Image {
     require(this.isFile && this.exists() && this.canRead()) { "file ${this.path} is not readable" }
     return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
 }
@@ -170,34 +170,34 @@ suspend inline fun <C : Contact> C.sendImage(file: File): MessageReceipt<C> = fi
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): OfflineImage = bufferedImage.upload(this)
+suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): Image = bufferedImage.upload(this)
 
 /**
  * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend inline fun Contact.uploadImage(imageUrl: URL): OfflineImage = imageUrl.uploadAsImage(this)
+suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this)
 
 /**
  * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend inline fun Contact.uploadImage(imageInput: Input): OfflineImage = imageInput.uploadAsImage(this)
+suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this)
 
 /**
  * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend inline fun Contact.uploadImage(imageStream: InputStream): OfflineImage = imageStream.uploadAsImage(this)
+suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this)
 
 /**
  * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
  * @throws OverFileSizeMaxException
  */
 @Throws(OverFileSizeMaxException::class)
-suspend inline fun Contact.uploadImage(file: File): OfflineImage = file.uploadAsImage(this)
+suspend inline fun Contact.uploadImage(file: File): Image = file.uploadAsImage(this)
 
 // endregion
\ No newline at end of file
diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt
index 78f5f49f4..def08db8c 100644
--- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt
+++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt
@@ -9,6 +9,7 @@
 
 @file:JvmMultifileClass
 @file:JvmName("MessageUtils")
+@file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION")
 
 package net.mamoe.mirai.message.data
 
@@ -74,7 +75,7 @@ actual interface Image : Message, MessageContent {
     @Deprecated("""
         不要自行实现 OnlineGroupImage, 它必须由协议模块实现, 否则会无法发送也无法解析.
     """, level = DeprecationLevel.HIDDEN)
-    @Suppress("PropertyName", "DeprecatedCallableAddReplaceWith")
+    @Suppress( "PropertyName", "unused")
     @get:JvmSynthetic
-    actual val DoNotImplementThisClass: Nothing?
+    internal actual val DoNotImplementThisClass: Nothing?
 }
\ No newline at end of file