1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-05-06 05:45:19 +08:00

Unified Image types: Online/Offline Image, Group/Friend Image

This commit is contained in:
Him188 2020-03-03 13:45:33 +08:00
parent 3c25c3df65
commit 12ebfa1db5
13 changed files with 323 additions and 79 deletions
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid
mirai-core/src
androidMain/kotlin/net/mamoe/mirai/contact
commonMain/kotlin/net.mamoe.mirai
jvmMain/kotlin/net/mamoe/mirai/contact

View File

@ -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<LongConn.OffPicUp.Response>()
@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> = 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() }

View File

@ -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}")
}

View File

@ -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<ImMsgB
elements.add(ImMsgBody.Elem(text = it.toJceData()))
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
}
is CustomFaceFromFile -> 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<ImMsgB
return elements
}
internal class CustomFaceFromServer(
internal class OnlineGroupImageImpl(
internal val delegate: ImMsgBody.CustomFace
) : CustomFace() {
) : OnlineGroupImage() {
override val filepath: String = delegate.filePath
override val fileId: Int get() = delegate.fileId
override val serverIp: Int get() = delegate.serverIp
@ -293,9 +293,11 @@ internal class CustomFaceFromServer(
override val original: Int get() = delegate.origin
override val pbReserve: ByteArray get() = delegate.pbReserve
override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType)
override val originUrl: String
get() = "http://gchat.qpic.cn" + delegate.origUrl
override fun equals(other: Any?): Boolean {
return other is CustomFaceFromServer && other.filepath == this.filepath && other.md5.contentEquals(this.md5)
return other is OnlineGroupImageImpl && other.filepath == this.filepath && other.md5.contentEquals(this.md5)
}
override fun hashCode(): Int {
@ -303,9 +305,9 @@ internal class CustomFaceFromServer(
}
}
internal class NotOnlineImageFromServer(
internal class OnlineFriendImageImpl(
internal val delegate: ImMsgBody.NotOnlineImage
) : NotOnlineImage() {
) : OnlineFriendImage() {
override val resourceId: String get() = delegate.resId
override val md5: ByteArray get() = delegate.picMd5
override val filepath: String get() = delegate.filePath
@ -317,9 +319,12 @@ internal class NotOnlineImageFromServer(
override val downloadPath: String get() = delegate.downloadPath
override val fileId: Int get() = delegate.fileId
override val original: Int get() = delegate.original
override val originUrl: String
get() = "http://c2cpicdw.qpic.cn" + this.delegate.origUrl
override fun equals(other: Any?): Boolean {
return other is NotOnlineImageFromServer && other.resourceId == this.resourceId && other.md5.contentEquals(this.md5)
return other is OnlineFriendImageImpl && other.resourceId == this.resourceId && other.md5
.contentEquals(this.md5)
}
override fun hashCode(): Int {
@ -368,8 +373,8 @@ internal fun List<ImMsgBody.Elem>.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()) {

View File

@ -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<out Contact>
/**
@ -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] 相同.

View File

@ -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<Group>
/**
* 上传一个图片以备发送.
*
* @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

View File

@ -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<QQ>
/**
* 上传一个图片以备发送.
*
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
* @see ImageUploadEvent 图片发送完成事件
*
* @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
*/
@JvmName("uploadImageSuspend")
@JvmSynthetic
actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage
}

View File

@ -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] 相同.

View File

@ -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<Group>
/**
* 上传一个图片以备发送.
*
* @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

View File

@ -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<QQ>
/**
* 上传一个图片以备发送.
*
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
* @see ImageUploadEvent 图片发送完成事件
*
* @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
*/
@JvmName("uploadImageSuspend")
@JvmSynthetic
abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage
}

View File

@ -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<Image> {
@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<Image>
/**
* 图片的 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
}
}
}
/**
* 接收消息时获取到的 [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

View File

@ -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] 相同.

View File

@ -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<Group>
/**
* 上传一个图片以备发送.
*
* @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

View File

@ -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<QQ>
/**
* 上传一个图片以备发送.
*
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
* @see ImageUploadEvent 图片发送完成事件
*
* @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
*/
@JvmSynthetic
@JvmName("uploadImageSuspend")
actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage
}