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 c661c70b8..3ff4d76d1 100644 --- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api +++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api @@ -331,7 +331,7 @@ public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamo public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile; } -public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User { +public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User, net/mamoe/mirai/contact/roaming/RoamingSupported { public fun delete ()V public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge; @@ -748,6 +748,57 @@ public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt { public static final fun getBot (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;)Lnet/mamoe/mirai/Bot; } +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessage { + public fun getBot ()Lnet/mamoe/mirai/Bot; + public abstract fun getContact ()Lnet/mamoe/mirai/contact/Contact; + public abstract fun getIds ()[I + public abstract fun getInternalIds ()[I + public abstract fun getSender ()J + public abstract fun getTarget ()J + public abstract fun getTime ()J +} + +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessageFilter { + public static final field ANY Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public static final field Companion Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion; + public static final field RECEIVED Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public static final field SENT Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public fun and (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public abstract fun invoke (Lnet/mamoe/mirai/contact/roaming/RoamingMessage;)Z + public fun not ()Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public fun or (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; +} + +public final class net/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion { +} + +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessages { + public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; + public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getAllMessagesStream ()Ljava/util/stream/Stream; + public fun getAllMessagesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; + public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; + public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; + public abstract fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getMessagesStream (JJ)Ljava/util/stream/Stream; + public fun getMessagesStream (JJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; + public abstract fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; + public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingSupported : net/mamoe/mirai/contact/Contact { + public abstract fun getRoamingMessages ()Lnet/mamoe/mirai/contact/roaming/RoamingMessages; +} + public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api index 95f09e197..93a85ca72 100644 --- a/binary-compatibility-validator/api/binary-compatibility-validator.api +++ b/binary-compatibility-validator/api/binary-compatibility-validator.api @@ -331,7 +331,7 @@ public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamo public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile; } -public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User { +public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User, net/mamoe/mirai/contact/roaming/RoamingSupported { public fun delete ()V public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge; @@ -748,6 +748,57 @@ public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt { public static final fun getBot (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;)Lnet/mamoe/mirai/Bot; } +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessage { + public fun getBot ()Lnet/mamoe/mirai/Bot; + public abstract fun getContact ()Lnet/mamoe/mirai/contact/Contact; + public abstract fun getIds ()[I + public abstract fun getInternalIds ()[I + public abstract fun getSender ()J + public abstract fun getTarget ()J + public abstract fun getTime ()J +} + +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessageFilter { + public static final field ANY Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public static final field Companion Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion; + public static final field RECEIVED Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public static final field SENT Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public fun and (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public abstract fun invoke (Lnet/mamoe/mirai/contact/roaming/RoamingMessage;)Z + public fun not ()Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; + public fun or (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; +} + +public final class net/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion { +} + +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessages { + public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; + public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getAllMessagesStream ()Ljava/util/stream/Stream; + public fun getAllMessagesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; + public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; + public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; + public abstract fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getMessagesStream (JJ)Ljava/util/stream/Stream; + public fun getMessagesStream (JJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; + public abstract fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; + public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public abstract interface class net/mamoe/mirai/contact/roaming/RoamingSupported : net/mamoe/mirai/contact/Contact { + public abstract fun getRoamingMessages ()Lnet/mamoe/mirai/contact/roaming/RoamingMessages; +} + public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt b/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt index bbb6486ba..37dacdea1 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt @@ -15,6 +15,7 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.roaming.RoamingSupported import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.FriendNudge @@ -34,7 +35,7 @@ import net.mamoe.mirai.utils.NotStableForInheritance * @see FriendMessageEvent */ @NotStableForInheritance -public interface Friend : User, CoroutineScope, AudioSupported { +public interface Friend : User, CoroutineScope, AudioSupported, RoamingSupported { /** * 向这个对象发送消息. * diff --git a/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessageFilter.kt b/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessageFilter.kt new file mode 100644 index 000000000..3b9a6d344 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessageFilter.kt @@ -0,0 +1,90 @@ +/* + * 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.contact.roaming + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.message.data.MessageSource + +/** + * @since 2.8 + */ +public fun interface RoamingMessageFilter { + public operator fun invoke(roamingMessage: RoamingMessage): Boolean + + + public infix fun and(other: RoamingMessageFilter): RoamingMessageFilter { + return RoamingMessageFilter { this.invoke(it) && other.invoke(it) } + } + + public infix fun or(other: RoamingMessageFilter): RoamingMessageFilter { + return RoamingMessageFilter { this.invoke(it) || other.invoke(it) } + } + + public fun not(): RoamingMessageFilter { + return RoamingMessageFilter { !this.invoke(it) } + } + + + public companion object { + /** + * 筛选任何消息 (相当于不筛选) + */ + @JvmField + public val ANY: RoamingMessageFilter = RoamingMessageFilter { true } + + /** + * 筛选 bot 接收的消息 + */ + @JvmField + public val RECEIVED: RoamingMessageFilter = RoamingMessageFilter { it.sender != it.bot.id } + + /** + * 筛选 bot 发送的消息 + */ + @JvmField + public val SENT: RoamingMessageFilter = RoamingMessageFilter { it.sender == it.bot.id } + } +} + +/** + * 还未解析的漫游消息. + * + * @since 2.8 + */ +public interface RoamingMessage { + public val contact: Contact + public val bot: Bot get() = contact.bot + + /** + * 发送人 id + */ + public val sender: Long + + /** + * 收信人或群的 id + */ + public val target: Long + + /** + * 时间戳, 单位为秒, 服务器时间. + */ + public val time: Long + + /** + * @see MessageSource.ids + */ + public val ids: IntArray + + /** + * @see MessageSource.internalIds + */ + public val internalIds: IntArray +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessages.kt b/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessages.kt new file mode 100644 index 000000000..1a96f2666 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessages.kt @@ -0,0 +1,101 @@ +/* + * 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 + */ + +@file:JvmBlockingBridge + +package net.mamoe.mirai.contact.roaming + +import kotlinx.coroutines.flow.Flow +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.utils.JavaFriendlyAPI +import java.util.stream.Stream + +/** + * 漫游消息记录管理器. + * + * @since 2.8 + * @see RoamingSupported + */ +public interface RoamingMessages { + /////////////////////////////////////////////////////////////////////////// + // Get list + /////////////////////////////////////////////////////////////////////////// + + /** + * 查询指定时间段内的漫游消息记录. + * + * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. + * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. + * + * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. + * + * @param timeStart 起始时间, UTC+8 时间戳, 单位为秒. 可以为 `0`, 即表示从可以获取的最早的消息起. 负数将会被看是 `0`. + * @param timeEnd 结束时间, UTC+8 时间戳, 单位为秒. 可以为 [Long.MAX_VALUE], 即表示到可以获取的最晚的消息为止. 低于 [timeStart] 的值将会被看作是 [timeStart] 的值. + * @param filter 过滤器. + */ + public suspend fun getMessagesIn( + timeStart: Long, + timeEnd: Long, + filter: RoamingMessageFilter? = null + ): Flow + + /** + * 查询所有漫游消息记录. + * + * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. + * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. + * + * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. + * + * @param filter 过滤器. + */ + public suspend fun getAllMessages( + filter: RoamingMessageFilter? = null + ): Flow = getMessagesIn(0, Long.MAX_VALUE, filter) + + /** + * 查询指定时间段内的漫游消息记录. + * + * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. + * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. + * + * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. + * + * @param timeStart 起始时间, UTC+8 时间戳, 单位为秒. 可以为 `0`, 即表示从可以获取的最早的消息起. 负数将会被看是 `0`. + * @param timeEnd 结束时间, UTC+8 时间戳, 单位为秒. 可以为 [Long.MAX_VALUE], 即表示到可以获取的最晚的消息为止. 低于 [timeStart] 的值将会被看作是 [timeStart] 的值. + * @param filter 过滤器. + */ + @Suppress("OVERLOADS_INTERFACE") + @JvmOverloads + @JavaFriendlyAPI + public suspend fun getMessagesStream( + timeStart: Long, + timeEnd: Long, + filter: RoamingMessageFilter? = null + ): Stream + + /** + * 查询所有漫游消息记录. + * + * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. + * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. + * + * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. + * + * @param filter 过滤器. + */ + @Suppress("OVERLOADS_INTERFACE") + @JvmOverloads + @JavaFriendlyAPI + public suspend fun getAllMessagesStream( + filter: RoamingMessageFilter? = null + ): Stream = getMessagesStream(0, Long.MAX_VALUE, filter) +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingSupported.kt b/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingSupported.kt new file mode 100644 index 000000000..a9cb8341c --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingSupported.kt @@ -0,0 +1,26 @@ +/* + * 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 + */ + +@file:JvmBlockingBridge + +package net.mamoe.mirai.contact.roaming + +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.contact.Contact + +/** + * 支持查询漫游消息记录的 [Contact]. + * @since 2.8 + */ +public interface RoamingSupported : Contact { + /** + * 获取漫游消息记录管理器. + */ + public val roamingMessages: RoamingMessages +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index 352421dca..34d4509aa 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -17,10 +17,12 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.events.FriendMessagePostSendEvent import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.info.FriendInfoImpl +import net.mamoe.mirai.internal.contact.roaming.RoamingMessagesImplFriend import net.mamoe.mirai.internal.message.OfflineAudioImpl import net.mamoe.mirai.internal.network.highway.* import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346 @@ -61,8 +63,8 @@ internal class FriendImpl( override val info: FriendInfoImpl, ) : Friend, AbstractUser(bot, parentCoroutineContext, info) { override suspend fun delete() { - check(bot.friends[this.id] != null) { - "Friend ${this.id} had already been deleted" + check(bot.friends[id] != null) { + "Friend $id had already been deleted" } bot.network.run { FriendList.DelFriend.invoke(bot.client, this@FriendImpl).sendAndExpect().also { @@ -145,4 +147,6 @@ internal class FriendImpl( return audio!! } + + override val roamingMessages: RoamingMessages by lazy { RoamingMessagesImplFriend(this) } } diff --git a/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt b/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt new file mode 100644 index 000000000..2ee2e6487 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt @@ -0,0 +1,137 @@ +/* + * 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 + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package net.mamoe.mirai.internal.contact.roaming + +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.runBlocking +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.roaming.RoamingMessage +import net.mamoe.mirai.contact.roaming.RoamingMessageFilter +import net.mamoe.mirai.contact.roaming.RoamingMessages +import net.mamoe.mirai.internal.contact.AbstractContact +import net.mamoe.mirai.internal.contact.FriendImpl +import net.mamoe.mirai.internal.message.toMessageChainOnline +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq +import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.utils.* +import java.util.stream.Stream + +internal sealed class RoamingMessagesImpl : RoamingMessages { + abstract val contact: AbstractContact + + override suspend fun getMessagesIn( + timeStart: Long, + timeEnd: Long, + filter: RoamingMessageFilter? + ): Flow { + return flow { + var lastMessageTime = timeEnd.coerceAtLeast(timeStart).coerceAtLeast(1) + var random = 0L + while (currentCoroutineContext().isActive) { + val resp = requestRoamMsg(timeStart, lastMessageTime, random) + val messages = resp.messages ?: break + if (filter == null || filter === RoamingMessageFilter.ANY) { + // fast path + messages.forEach { emit(it.toMessageChainOnline(contact.bot)) } + } else { + for (message in messages) { + if (filter.invoke(createRoamingMessage(message, messages))) { + emit(message.toMessageChainOnline(contact.bot)) + } + } + } + + lastMessageTime = resp.lastMessageTime + random = resp.random + } + } + } + + private fun createRoamingMessage( + message: MsgComm.Msg, + messages: List + ) = object : RoamingMessage { + override val contact: Contact get() = this@RoamingMessagesImpl.contact + override val sender: Long get() = message.msgHead.fromUin + override val target: Long + get() = message.msgHead.groupInfo?.groupCode ?: message.msgHead.toUin + override val time: Long get() = message.msgHead.msgTime.toLongUnsigned() + override val ids: IntArray by lazy { messages.mapToIntArray { it.msgHead.msgSeq } } + override val internalIds: IntArray by lazy { + messages.mapToIntArray { it.msgBody.richText.attr?.random ?: 0 } // other client 消息的这个是0 + } + } + + + @JavaFriendlyAPI + override suspend fun getMessagesStream( + timeStart: Long, + timeEnd: Long, + filter: RoamingMessageFilter?, + ): Stream { + return stream { + var lastMessageTime = timeEnd + var random = 0L + while (true) { + val resp = runBlocking { + requestRoamMsg(timeStart, lastMessageTime, random) + } + + val messages = resp.messages ?: break + if (filter == null || filter === RoamingMessageFilter.ANY) { + messages.forEach { yield(runBlocking { it.toMessageChainOnline(contact.bot) }) } + } else { + for (message in messages) { + if (filter.invoke(createRoamingMessage(message, messages))) { + yield(runBlocking { message.toMessageChainOnline(contact.bot) }) + } + } + } + + lastMessageTime = resp.lastMessageTime + random = resp.random + } + } + } + + abstract suspend fun requestRoamMsg( + timeStart: Long, + lastMessageTime: Long, + random: Long + ): MessageSvcPbGetRoamMsgReq.Response +} + +internal class RoamingMessagesImplFriend( + override val contact: FriendImpl +) : RoamingMessagesImpl() { + override suspend fun requestRoamMsg( + timeStart: Long, + lastMessageTime: Long, + random: Long + ): MessageSvcPbGetRoamMsgReq.Response { + return MessageSvcPbGetRoamMsgReq.createForFriend( + client = contact.bot.client, + uin = contact.id, + timeStart = timeStart, + lastMsgTime = lastMessageTime, + random = random, + maxCount = 1000, + sig = byteArrayOf(), + pwd = byteArrayOf() + ).sendAndExpect(contact.bot).value.check() + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt index f4cae68f8..661a98dae 100644 --- a/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt +++ b/mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt @@ -50,6 +50,27 @@ internal suspend fun List.toMessageChainOnline( return toMessageChain(bot, groupIdOrZero, true, messageSourceKind).refineDeep(bot, refineContext) } +internal suspend fun MsgComm.Msg.toMessageChainOnline( + bot: Bot, + refineContext: RefineContext = EmptyRefineContext, +): MessageChain { + fun getSourceKind(c2cCmd: Int): MessageSourceKind { + return when (c2cCmd) { + 11 -> MessageSourceKind.FRIEND // bot 给其他人发消息 + 4 -> MessageSourceKind.FRIEND // bot 给自己作为好友发消息 (非 other client) + 1 -> MessageSourceKind.GROUP + else -> error("Could not get source kind from c2cCmd: $c2cCmd") + } + } + + val kind = getSourceKind(msgHead.c2cCmd) + val groupId = when (kind) { + MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0 + else -> 0 + } + return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext) +} + //internal fun List.toMessageChainOffline( // bot: Bot, // groupIdOrZero: Long, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index f6780679d..39c213889 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -135,6 +135,7 @@ internal object KnownPacketFactories { MessageSvcPushForceOffline, MessageSvcPbSendMsg, MessageSvcPbDeleteMsg, + MessageSvcPbGetRoamMsgReq, FriendList.GetFriendGroupList, FriendList.DelFriend, FriendList.GetTroopListSimplify, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetRoamMsgReq.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetRoamMsgReq.kt new file mode 100644 index 000000000..b1aa94b63 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetRoamMsgReq.kt @@ -0,0 +1,91 @@ +/* + * 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.network.protocol.packet.chat.receive + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf +import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf +import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.utils.Either +import net.mamoe.mirai.utils.Either.Companion.mapRight +import net.mamoe.mirai.utils.FailureResponse +import net.mamoe.mirai.utils.checked + +internal class CheckedResponse( + val value: Either +) : Packet + +internal object MessageSvcPbGetRoamMsgReq : + OutgoingPacketFactory>("MessageSvc.PbGetRoamMsg") { + + class Response( + val messages: List?, + val lastMessageTime: Long, + val random: Long, + val sig: ByteArray, // 似乎没被用到, 服务器每次返回不同 + ) { + + } + + fun createForFriend( + client: QQAndroidClient, + uin: Long, + timeStart: Long, + lastMsgTime: Long, // 上次 resp 中的, 否则为期待的 time end + random: Long = 0, + maxCount: Int = 1000, + sig: ByteArray = EMPTY_BYTE_ARRAY, // 客户端每次请求相同 + pwd: ByteArray = EMPTY_BYTE_ARRAY, + ) = buildOutgoingUniPacket(client) { + writeProtoBuf( + MsgSvc.PbGetRoamMsgReq.serializer(), MsgSvc.PbGetRoamMsgReq( + peerUin = uin, + beginMsgtime = timeStart, + lastMsgtime = lastMsgTime, + checkPwd = 1, // always + readCnt = maxCount, + subcmd = 1, + reqType = 1, + sig = sig, + pwd = pwd, + random = random, + ) + ) + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CheckedResponse { + val resp = readProtoBuf(MsgSvc.PbGetRoamMsgResp.serializer()) + if (resp.result == 1) return CheckedResponse( + Either.right( + Response( + null, + resp.lastMsgtime, + resp.random, + resp.sig, + ) + ) + ) // finished + return CheckedResponse(resp.checked().mapRight { + Response( + messages = resp.msg.asReversed(), + lastMessageTime = resp.lastMsgtime, + random = resp.random, + sig = resp.sig, + ) + }) + } +} \ No newline at end of file