diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Group.kt b/mirai-core-api/src/commonMain/kotlin/contact/Group.kt index 76436b58d..069f6ae2a 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Group.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Group.kt @@ -18,6 +18,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.active.GroupActive import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.contact.file.RemoteFiles +import net.mamoe.mirai.contact.roaming.RoamingSupported import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* @@ -60,7 +61,7 @@ import kotlin.jvm.JvmSynthetic * 使用 [Group.files] 获取群文件管理器后操作. 详见 [RemoteFiles]. */ @NotStableForInheritance -public interface Group : Contact, CoroutineScope, FileSupported, AudioSupported { +public interface Group : Contact, CoroutineScope, FileSupported, AudioSupported, RoamingSupported { /** * 群名称. * diff --git a/mirai-core-mock/src/internal/contact/MockGroupImpl.kt b/mirai-core-mock/src/internal/contact/MockGroupImpl.kt index ad78778bd..257670ee0 100644 --- a/mirai-core-mock/src/internal/contact/MockGroupImpl.kt +++ b/mirai-core-mock/src/internal/contact/MockGroupImpl.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.announcement.OfflineAnnouncement import net.mamoe.mirai.contact.announcement.buildAnnouncementParameters import net.mamoe.mirai.contact.file.RemoteFiles +import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.broadcast @@ -31,6 +32,7 @@ import net.mamoe.mirai.mock.contact.MockGroupControlPane import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.contact.active.MockGroupActive import net.mamoe.mirai.mock.internal.contact.active.MockGroupActiveImpl +import net.mamoe.mirai.mock.internal.contact.roaming.MockRoamingMessages import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcToGroup import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.mock.utils.broadcastBlocking @@ -353,4 +355,6 @@ internal class MockGroupImpl( override fun toString(): String { return "Group($id)" } + + override val roamingMessages: RoamingMessages = MockRoamingMessages(this) } \ No newline at end of file diff --git a/mirai-core-mock/test/mock/MessagingTest.kt b/mirai-core-mock/test/mock/MessagingTest.kt index 8d341bc8c..8ffef3c08 100644 --- a/mirai-core-mock/test/mock/MessagingTest.kt +++ b/mirai-core-mock/test/mock/MessagingTest.kt @@ -12,11 +12,8 @@ package net.mamoe.mirai.mock.test.mock import kotlinx.coroutines.flow.toList import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageSource.Key.recall -import net.mamoe.mirai.message.data.OnlineMessageSource -import net.mamoe.mirai.message.data.PlainText -import net.mamoe.mirai.message.data.messageChainOf -import net.mamoe.mirai.message.data.source import net.mamoe.mirai.mock.MockActions.mockFireRecalled import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.mock.utils.broadcastMockEvents @@ -148,6 +145,24 @@ internal class MessagingTest: MockBotTestBase() { assertEquals(messageChainOf(PlainText("Test2!")), messages[1]) assertEquals(messageChainOf(PlainText("Pong!")), messages[2]) } + + val mockGroup = bot.addGroup(2, "2") + val mockGroupMember1 = mockGroup.addMember(123, "123") + val mockGroupMember2 = mockGroup.addMember(124, "124") + val mockGroupMember3 = mockGroup.addMember(125, "125") + + broadcastMockEvents { + mockGroupMember1 says { append("msg1") } + mockGroupMember2 says { append("msg2") } + mockGroupMember3 says { append("msg3") } + } + + with(mockGroup.roamingMessages.getAllMessages().toList()) { + assertEquals(3, size) + assertEquals(messageChainOf(PlainText("msg1")), get(0)) + assertEquals(messageChainOf(PlainText("msg2")), get(1)) + assertEquals(messageChainOf(PlainText("msg3")), get(2)) + } } @Test diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 8f312ae48..7b879c7dc 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.active.GroupActive import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.contact.file.RemoteFiles +import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.MemberInfo @@ -30,6 +31,7 @@ import net.mamoe.mirai.internal.contact.active.GroupActiveImpl import net.mamoe.mirai.internal.contact.announcement.AnnouncementsImpl import net.mamoe.mirai.internal.contact.file.RemoteFilesImpl import net.mamoe.mirai.internal.contact.info.MemberInfoImpl +import net.mamoe.mirai.internal.contact.roaming.RoamingMessagesImplGroup import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.data.OfflineAudioImpl import net.mamoe.mirai.internal.message.image.OfflineGroupImage @@ -389,6 +391,8 @@ internal abstract class CommonGroupImpl constructor( return result.success } + override val roamingMessages: RoamingMessages by lazy { RoamingMessagesImplGroup(this) } + override fun toString(): String = "Group($id)" } diff --git a/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt b/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt index 15e180c08..6db85639c 100644 --- a/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImpl.kt @@ -20,10 +20,14 @@ 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.CommonGroupImpl 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.TroopManagement +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetGroupMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq +import net.mamoe.mirai.internal.utils.indexFirstBE import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.check import net.mamoe.mirai.utils.mapToIntArray @@ -106,4 +110,71 @@ internal class RoamingMessagesImplFriend( ) ).value.check() } +} + +internal class RoamingMessagesImplGroup( + override val contact: CommonGroupImpl +) : RoamingMessagesImpl() { + override suspend fun requestRoamMsg( + timeStart: Long, + lastMessageTime: Long, + random: Long // unused field + ): MessageSvcPbGetRoamMsgReq.Response { + val lastMsgSeq = contact.bot.network.sendAndExpect( + TroopManagement.GetGroupLastMsgSeq( + client = contact.bot.client, + groupUin = contact.uin + ) + ) + return when(lastMsgSeq) { + is TroopManagement.GetGroupLastMsgSeq.Response.Success -> { + val results = mutableListOf() + var currentSeq = lastMsgSeq.seq + + while (true) { + if (currentSeq <= 0) break + + val resp = contact.bot.network.sendAndExpect( + MessageSvcPbGetGroupMsg( + client = contact.bot.client, + groupUin = contact.uin, + messageSequence = currentSeq, + 20 // maximum 20 + ) + ) + if (resp is MessageSvcPbGetGroupMsg.Failed) break + if ((resp as MessageSvcPbGetGroupMsg.Success).msgElem.isEmpty()) break + + // the message may be sorted increasing by message time, + // if so, additional sortBy will not take cost. + val msgElems = resp.msgElem.sortedBy { it.msgHead.msgTime } + results.addAll(0, msgElems) + + val firstMsgElem = msgElems.first() + if (firstMsgElem.msgHead.msgTime < timeStart) { + break + } else { + currentSeq = (firstMsgElem.msgHead.msgSeq - 1).toLong() + } + } + + // use binary search to find the first message that message time is lager than lastMessageTime + var right = results.indexFirstBE(lastMessageTime) { it.msgHead.msgTime.toLong() } + // check messages with same time + if (results[right].msgHead.msgTime.toLong() == lastMessageTime) { + do { right ++ } while (right <= results.size - 1 && results[right].msgHead.msgTime <= lastMessageTime) + } + // loops at most 20 times, just traverse + val left = results.indexOfFirst { it.msgHead.msgTime >= timeStart } + + MessageSvcPbGetRoamMsgReq.Response( + if (left == right) null else results.subList(left, right), + if (left == right) -1L else results[right - 1].msgHead.msgTime.toLong(), -1L, byteArrayOf() + ) + } + is TroopManagement.GetGroupLastMsgSeq.Response.Failed -> { + MessageSvcPbGetRoamMsgReq.Response(null, -1L, -1L, byteArrayOf()) + } + } + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/utils/collection.kt b/mirai-core/src/commonMain/kotlin/utils/collection.kt new file mode 100644 index 000000000..f8b827ce0 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/utils/collection.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019-2022 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.utils + +internal fun > List.indexFirstBE( + value: M, + mapping: (T) -> M +): Int { + var (left, right) = 0 to size - 1 + var index = -1 + while (left <= right) { + val middle = left + (right - left) / 2 + if (mapping(get(middle)) >= value) { + index = middle + right = middle - 1 + } else { + left = middle + 1 + } + } + return index +} \ No newline at end of file