diff --git a/mirai-core-api/compatibility-validation/android/api/android.api b/mirai-core-api/compatibility-validation/android/api/android.api index 06fc3124c..b24bcd209 100644 --- a/mirai-core-api/compatibility-validation/android/api/android.api +++ b/mirai-core-api/compatibility-validation/android/api/android.api @@ -15,6 +15,7 @@ public abstract interface class net/mamoe/mirai/Bot : kotlinx/coroutines/Corouti public abstract fun getConfiguration ()Lnet/mamoe/mirai/utils/BotConfiguration; public abstract fun getEventChannel ()Lnet/mamoe/mirai/event/EventChannel; public fun getFriend (J)Lnet/mamoe/mirai/contact/Friend; + public abstract fun getFriendGroups ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroups; public fun getFriendOrFail (J)Lnet/mamoe/mirai/contact/Friend; public abstract fun getFriends ()Lnet/mamoe/mirai/contact/ContactList; public fun getGroup (J)Lnet/mamoe/mirai/contact/Group; @@ -354,6 +355,7 @@ public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamo 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 abstract fun getFriendGroup ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public abstract fun getRemark ()Ljava/lang/String; public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge; public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; @@ -908,6 +910,27 @@ public abstract interface class net/mamoe/mirai/contact/file/RemoteFiles { public static synthetic fun uploadNewFile$suspendImpl (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroup { + public fun delete ()Z + public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getCount ()I + public abstract fun getFriends ()Ljava/util/Collection; + public abstract fun getId ()I + public abstract fun getName ()Ljava/lang/String; + public fun moveIn (Lnet/mamoe/mirai/contact/Friend;)Z + public abstract fun moveIn (Lnet/mamoe/mirai/contact/Friend;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun renameTo (Ljava/lang/String;)Z + public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroups { + public abstract fun asCollection ()Ljava/util/Collection; + public fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; + public abstract fun create (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun get (I)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; + public fun getDefault ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; +} + 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; @@ -962,6 +985,7 @@ public abstract interface class net/mamoe/mirai/contact/roaming/RoamingSupported } public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { + public abstract fun getFriendGroupId ()I public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; public abstract fun getUin ()J @@ -970,9 +994,11 @@ public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mira public class net/mamoe/mirai/data/FriendInfoImpl : net/mamoe/mirai/data/FriendInfo { public fun (JLjava/lang/String;Ljava/lang/String;)V + public fun getFriendGroupId ()I public fun getNick ()Ljava/lang/String; public fun getRemark ()Ljava/lang/String; public fun getUin ()J + public fun setFriendGroupId (I)V public fun setNick (Ljava/lang/String;)V public fun setRemark (Ljava/lang/String;)V } @@ -1688,6 +1714,7 @@ public abstract interface class net/mamoe/mirai/data/UserInfo { public abstract interface class net/mamoe/mirai/data/UserProfile { public abstract fun getAge ()I public abstract fun getEmail ()Ljava/lang/String; + public abstract fun getFriendGroupId ()I public abstract fun getNickname ()Ljava/lang/String; public abstract fun getQLevel ()I public abstract fun getSex ()Lnet/mamoe/mirai/data/UserProfile$Sex; diff --git a/mirai-core-api/compatibility-validation/jvm/api/jvm.api b/mirai-core-api/compatibility-validation/jvm/api/jvm.api index 621b7bdb4..cf629ff08 100644 --- a/mirai-core-api/compatibility-validation/jvm/api/jvm.api +++ b/mirai-core-api/compatibility-validation/jvm/api/jvm.api @@ -15,6 +15,7 @@ public abstract interface class net/mamoe/mirai/Bot : kotlinx/coroutines/Corouti public abstract fun getConfiguration ()Lnet/mamoe/mirai/utils/BotConfiguration; public abstract fun getEventChannel ()Lnet/mamoe/mirai/event/EventChannel; public fun getFriend (J)Lnet/mamoe/mirai/contact/Friend; + public abstract fun getFriendGroups ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroups; public fun getFriendOrFail (J)Lnet/mamoe/mirai/contact/Friend; public abstract fun getFriends ()Lnet/mamoe/mirai/contact/ContactList; public fun getGroup (J)Lnet/mamoe/mirai/contact/Group; @@ -354,6 +355,7 @@ public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamo 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 abstract fun getFriendGroup ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public abstract fun getRemark ()Ljava/lang/String; public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge; public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; @@ -908,6 +910,27 @@ public abstract interface class net/mamoe/mirai/contact/file/RemoteFiles { public static synthetic fun uploadNewFile$suspendImpl (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroup { + public fun delete ()Z + public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getCount ()I + public abstract fun getFriends ()Ljava/util/Collection; + public abstract fun getId ()I + public abstract fun getName ()Ljava/lang/String; + public fun moveIn (Lnet/mamoe/mirai/contact/Friend;)Z + public abstract fun moveIn (Lnet/mamoe/mirai/contact/Friend;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun renameTo (Ljava/lang/String;)Z + public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroups { + public abstract fun asCollection ()Ljava/util/Collection; + public fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; + public abstract fun create (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun get (I)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; + public fun getDefault ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; +} + 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; @@ -962,6 +985,7 @@ public abstract interface class net/mamoe/mirai/contact/roaming/RoamingSupported } public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { + public abstract fun getFriendGroupId ()I public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; public abstract fun getUin ()J @@ -970,9 +994,11 @@ public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mira public class net/mamoe/mirai/data/FriendInfoImpl : net/mamoe/mirai/data/FriendInfo { public fun (JLjava/lang/String;Ljava/lang/String;)V + public fun getFriendGroupId ()I public fun getNick ()Ljava/lang/String; public fun getRemark ()Ljava/lang/String; public fun getUin ()J + public fun setFriendGroupId (I)V public fun setNick (Ljava/lang/String;)V public fun setRemark (Ljava/lang/String;)V } @@ -1688,6 +1714,7 @@ public abstract interface class net/mamoe/mirai/data/UserInfo { public abstract interface class net/mamoe/mirai/data/UserProfile { public abstract fun getAge ()I public abstract fun getEmail ()Ljava/lang/String; + public abstract fun getFriendGroupId ()I public abstract fun getNickname ()Ljava/lang/String; public abstract fun getQLevel ()I public abstract fun getSex ()Lnet/mamoe/mirai/data/UserProfile$Sex; diff --git a/mirai-core-api/src/commonMain/kotlin/Bot.kt b/mirai-core-api/src/commonMain/kotlin/Bot.kt index 02fd0bc12..3afdd5a95 100644 --- a/mirai-core-api/src/commonMain/kotlin/Bot.kt +++ b/mirai-core-api/src/commonMain/kotlin/Bot.kt @@ -15,6 +15,7 @@ package net.mamoe.mirai import kotlinx.coroutines.* import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.* +import net.mamoe.mirai.contact.friendgroup.FriendGroups import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.message.action.BotNudge @@ -112,6 +113,13 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot { */ public val friends: ContactList + /** + * 全部的好友分组 + * + * @since 2.13 + */ + public val friendGroups: FriendGroups + /** * 以 [对方 QQ 号码][id] 获取一个好友对象, 在获取失败时返回 `null`. diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt b/mirai-core-api/src/commonMain/kotlin/contact/Friend.kt index dd1d730fd..ba1479fd0 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 me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingSupported import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt @@ -38,6 +39,14 @@ import net.mamoe.mirai.utils.NotStableForInheritance @NotStableForInheritance public interface Friend : User, CoroutineScope, AudioSupported, RoamingSupported { + /** + * 该好友所在的好友分组 + * + * @since 2.13 + */ + public val friendGroup: FriendGroup + + /** * 备注信息 * diff --git a/mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroup.kt b/mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroup.kt new file mode 100644 index 000000000..b0939d410 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroup.kt @@ -0,0 +1,77 @@ +/* + * 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.contact.friendgroup + +import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge +import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.utils.NotStableForInheritance + +/** + * 一个好友分组. + * 可能同时存在多个相同[名称][name]的分组, 但是每个分组的 [id] 都是唯一的. + * + * + * 要获取一个分组, 可使用 [get] 根据 [ID][FriendGroup.id] 获取, 或者使用 [asCollection] 获取全部分组列表. + * 也可以通过 [Friend.friendGroup] 获取一个好友所在的分组. + * + * 在每次登录会话中, [FriendGroup] 的实例是依据 [id] 唯一的. 存在于同一个分组中的多个好友的 [Friend.friendGroup] 会返回相同的 [FriendGroup] 实例. + * 但当 bot 重新登录后, 实例可能变化. + * + * @see FriendGroups + * @since 2.13 + */ +@JvmBlockingBridge +@NotStableForInheritance +public interface FriendGroup { + /** + * 好友分组 ID + */ + public val id: Int + + /** + * 好友分组名 + */ + public val name: String + + /** + * 好友分组内好友数量 + */ + public val count: Int + + /** + * 属于本分组的好友集合 + */ + public val friends: Collection + + /** + * 更改好友分组名称. + * + * 允许存在同名分组. + * 当操作成时返回 `true`; 当分组不存在时返回 `false`; 如果因为其他原因造成的改名失败时会抛出 [IllegalStateException] + */ + public suspend fun renameTo(newName: String): Boolean + + /** + * 把一名好友移动至本分组内. + * + * 当远程分组不存在时会自动移动该好友到 ID 为 0 的默认好友分组. + * 当操作成功时返回 `true`; 当分组不存在 (如已经在远程被删除) 时返回 `false`; 因为其他原因移动不成功时抛出 [IllegalStateException]. + */ + public suspend fun moveIn(friend: Friend): Boolean + + /** + * 删除本分组. + * + * 删除后组内全部好友移动至 ID 为 0 的默认好友分组, 本分组的好友列表会被清空. + * 当操作成功时返回 `true`; 当分组不存在或试图删除 ID 为 0 的默认好友分组时返回 `false`; + * 因为其他原因删除不成功时抛出 [IllegalStateException]. + */ + public suspend fun delete(): Boolean +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroups.kt b/mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroups.kt new file mode 100644 index 000000000..70c13612a --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroups.kt @@ -0,0 +1,52 @@ +/* + * 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.contact.friendgroup + +import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge +import net.mamoe.mirai.utils.NotStableForInheritance + +/** + * 好友分组列表 (管理器). + * 允许存在重复名称的分组, 因此依赖于 name 判断不可靠, 需要依赖 ID 判断. + * + * @see FriendGroup + * @since 2.13 + */ +@JvmBlockingBridge +@NotStableForInheritance +public interface FriendGroups { + /** + * 获取 [ID][FriendGroup.id] 为 `0` 的默认分组 ("我的好友"). + */ + public val default: FriendGroup get() = get(0) ?: error("Internal error: could not find FriendGroup with id = 0.") + + /** + * 新建一个好友分组. + * + * 允许名称重复, 当新建一个已存在名称的分组时, 服务器会返回一个拥有重复名字的新分组; + * 当因为其他原因创建不成功时抛出 [IllegalStateException]. + * + * 提示: 要删除一个好友分组, 使用 [FriendGroup.delete]. + */ + public suspend fun create(name: String): FriendGroup + + /** + * 获取指定 ID 的好友分组, 不存在时返回 `null` + */ + public operator fun get(id: Int): FriendGroup? + + /** + * 获取包含全部 [FriendGroup] 的 [Collection]. 返回的 [Collection] 只可读取. + * + * 此方法快速返回, 不会在调用时实例化新的 [Collection] 对象. + * 返回的 [Collection] 是对缓存的引用, 会随着服务器通知和机器人操作 (如 [create]) 变化. + */ + public fun asCollection(): Collection +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt b/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt index a681c4c34..c8ce6d5a7 100644 --- a/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt +++ b/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt @@ -18,6 +18,8 @@ public interface FriendInfo : UserInfo { public override val nick: String public override var remark: String + + public val friendGroupId: Int } @@ -32,4 +34,6 @@ public open class FriendInfoImpl( override val uin: Long, override var nick: String, override var remark: String, -) : FriendInfo \ No newline at end of file +) : FriendInfo { + override var friendGroupId: Int = 0 +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/data/UserProfile.kt b/mirai-core-api/src/commonMain/kotlin/data/UserProfile.kt index 0afcabca8..17cb58c04 100644 --- a/mirai-core-api/src/commonMain/kotlin/data/UserProfile.kt +++ b/mirai-core-api/src/commonMain/kotlin/data/UserProfile.kt @@ -1,10 +1,10 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * 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. + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. * - * https://github.com/mamoe/mirai/blob/master/LICENSE + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ package net.mamoe.mirai.data @@ -27,6 +27,13 @@ public interface UserProfile { public val qLevel: Int public val sex: Sex + /** + * 好友分组 ID, 在非好友情况下或者位于默认分组情况下为 `0` + * + * @since 2.13 + */ + public val friendGroupId: Int + /** * 个性签名 */ diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt index d6153fb93..60ee571a2 100644 --- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt +++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt @@ -100,7 +100,7 @@ internal abstract class AbstractBot constructor( final override val strangers: ContactList = ContactList() final override val asFriend: FriendImpl by lazy { - Mirai.newFriend(this, FriendInfoImpl(uin, "", "")).cast() + Mirai.newFriend(this, FriendInfoImpl(uin, "", "", 0)).cast() } // nick is initialized later on login final override val asStranger: StrangerImpl by lazy { Mirai.newStranger(this, StrangerInfoImpl(bot.id, bot.nick)).cast() diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 264470a8d..b80e3ec98 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -599,7 +599,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { if (!accept) return @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, ""))) + bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "", 0))) } override suspend fun solveBotInvitedJoinGroupRequestEvent( diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index b921edfe3..7a63df064 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -16,6 +16,7 @@ import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent +import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupsImpl import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage @@ -44,6 +45,7 @@ import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor import net.mamoe.mirai.internal.network.notice.group.GroupNotificationProcessor import net.mamoe.mirai.internal.network.notice.group.GroupOrMemberListNoticeProcessor import net.mamoe.mirai.internal.network.notice.group.GroupRecallProcessor +import net.mamoe.mirai.internal.network.notice.priv.FriendGroupNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.OtherClientNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor @@ -71,6 +73,8 @@ internal open class QQAndroidBot constructor( configuration: BotConfiguration, ) : AbstractBot(configuration, account.id) { override val bot: QQAndroidBot get() = this + override val friendGroups: FriendGroupsImpl by lazy { FriendGroupsImpl(this) } + val client get() = components[SsoProcessor].client override fun close(cause: Throwable?) { @@ -191,6 +195,7 @@ internal open class QQAndroidBot constructor( GroupOrMemberListNoticeProcessor(pipelineLogger.subLogger("GroupOrMemberListNoticeProcessor")), GroupMessageProcessor(pipelineLogger.subLogger("GroupMessageProcessor")), GroupNotificationProcessor(pipelineLogger.subLogger("GroupNotificationProcessor")), + FriendGroupNoticeProcessor(pipelineLogger.subLogger("FriendGroupNoticeProcessor")), PrivateMessageProcessor(), OtherClientNoticeProcessor(), GroupRecallProcessor(), diff --git a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt index d3aa8194d..c872ccc5f 100644 --- a/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt @@ -14,11 +14,11 @@ package net.mamoe.mirai.internal.contact -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import io.ktor.utils.io.core.* +import kotlinx.coroutines.launch import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.FriendMessagePostSendEvent @@ -53,7 +53,8 @@ internal fun net.mamoe.mirai.internal.network.protocol.data.jce.FriendInfo.toMir FriendInfoImpl( friendUin, nick, - remark + remark, + groupId.toInt(), ) @OptIn(ExperimentalContracts::class) @@ -83,6 +84,9 @@ internal class FriendImpl( } } + override val friendGroup: FriendGroup + get() = bot.friendGroups[info.friendGroupId] ?: bot.friendGroups[0]!! + private val messageProtocolStrategy: MessageProtocolStrategy = FriendMessageProtocolStrategy(this) override suspend fun delete() { diff --git a/mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupImpl.kt new file mode 100644 index 000000000..12304493a --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupImpl.kt @@ -0,0 +1,111 @@ +/* + * 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.contact.friendgroup + +import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.friendgroup.FriendGroup +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.FriendImpl +import net.mamoe.mirai.internal.contact.impl +import net.mamoe.mirai.internal.contact.info.FriendGroupInfo +import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +internal inline fun FriendGroup.impl(): FriendGroupImpl { + contract { + returns() implies (this@impl is FriendGroupImpl) + } + check(this is FriendGroupImpl) { "A FriendGroup instance is not instance of FriendGroupImpl. Your instance: ${this::class.qualifiedName}" } + return this +} + +internal class FriendGroupImpl constructor( + val bot: QQAndroidBot, + val info: FriendGroupInfo +) : FriendGroup { + override val id: Int by info::groupId + + override var name: String by info::groupName + override val count: Int + get() = friends.size + override val friends: Collection = object : AbstractCollection() { + override val size: Int + get() = bot.friends.count { it.impl().info.friendGroupId == id } + + private val delegateSequence = sequence { + bot.friends.forEach { friend -> + friend.impl() + if (friend.info.friendGroupId == id) { + yield(friend) + } + } + } + + override fun iterator(): Iterator = delegateSequence.iterator() + + override fun isEmpty(): Boolean { + return bot.friends.none { it.impl().info.friendGroupId == id } + } + + override fun contains(element: Friend): Boolean { + if (element !is FriendImpl) return false + return element.info.friendGroupId == id + } + } + + + override suspend fun renameTo(newName: String): Boolean { + bot.network.sendAndExpect(FriendList.SetGroupReqPack.Rename(bot.client, newName, id)).let { + if (it.result.toInt() == 1) { + return false + } + check(it.isSuccess) { + "Cannot rename friendGroup(id=$id) to $newName, code=${it.result.toInt()}, errStr=${it.errStr}" + } + } + info.groupName = newName + return true + } + + override suspend fun moveIn(friend: Friend): Boolean { + bot.network.sendAndExpect(FriendList.MoveGroupMemReqPack(bot.client, friend.id, id)).let { + check(it.isSuccess) { + "Cannot move friend to $this, code=${it.result.toInt()}, errStr=${it.errStr}" + } + } + // 因为 MoveGroupMemReqPack 协议在测试里如果移动到不存在的分组,他会自动移动好友到 id = 0 的默认好友分组然后返回 result = 0 + val id = friend.queryProfile().friendGroupId + friend.impl().info.friendGroupId = id + if (id != this.id && id == 0) return false + return true + } + + override suspend fun delete(): Boolean { + bot.network.sendAndExpect(FriendList.SetGroupReqPack.Delete(bot.client, id)).let { + if (it.result.toInt() == 1) { + return false + } + check(it.isSuccess) { + "Cannot delete friendGroup, code=${it.result.toInt()}, errStr=${it.errStr}" + } + } + friends.forEach { + it.impl().info.friendGroupId = 0 + } + bot.friendGroups.friendGroups.remove(this) + return true + } + + override fun toString(): String { + return "FriendGroup(id=$id, name=$name)" + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupsImpl.kt b/mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupsImpl.kt new file mode 100644 index 000000000..2605e2c34 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupsImpl.kt @@ -0,0 +1,37 @@ +/* + * 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.contact.friendgroup + +import net.mamoe.mirai.contact.friendgroup.FriendGroup +import net.mamoe.mirai.contact.friendgroup.FriendGroups +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.contact.info.FriendGroupInfo +import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList +import net.mamoe.mirai.utils.ConcurrentLinkedDeque +import net.mamoe.mirai.utils.asImmutable + +internal class FriendGroupsImpl( + val bot: QQAndroidBot +) : FriendGroups { + val friendGroups = ConcurrentLinkedDeque() + private val friendGroupsImmutable by lazy { friendGroups.asImmutable() } + + override suspend fun create(name: String): FriendGroup { + val resp = bot.network.sendAndExpect(FriendList.SetGroupReqPack.New(bot.client, name)) + check(resp.isSuccess) { + "Cannot create friendGroup, code=${resp.result.toInt()}, errStr=${resp.errStr}" + } + return FriendGroupImpl(bot, FriendGroupInfo(resp.groupId, name)).apply { friendGroups.add(this) } + } + + override fun get(id: Int): FriendGroup? = friendGroups.firstOrNull { it.id == id } + + override fun asCollection(): Collection = friendGroupsImmutable +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/info/FriendGroupInfo.kt b/mirai-core/src/commonMain/kotlin/contact/info/FriendGroupInfo.kt new file mode 100644 index 000000000..21c9c57a8 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/info/FriendGroupInfo.kt @@ -0,0 +1,20 @@ +/* + * 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.contact.info + +import kotlinx.serialization.Serializable + +@Serializable +internal data class FriendGroupInfo( + val groupId: Int, + var groupName: String, + // val friendCount: Int, + // val onlineFriendCount: Int +) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt b/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt index bb1702798..7c984acd7 100644 --- a/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt @@ -18,8 +18,9 @@ internal data class FriendInfoImpl( override val uin: Long, override var nick: String, override var remark: String, + override var friendGroupId: Int ) : FriendInfo { companion object { - fun FriendInfo.impl() = if (this is FriendInfoImpl) this else FriendInfoImpl(uin, nick, remark) + fun FriendInfo.impl() = if (this is FriendInfoImpl) this else FriendInfoImpl(uin, nick, remark, friendGroupId) } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt b/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt index 4dae96871..facdcd466 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt @@ -23,10 +23,8 @@ import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.StrangerImpl -import net.mamoe.mirai.internal.contact.info.FriendInfoImpl -import net.mamoe.mirai.internal.contact.info.GroupInfoImpl -import net.mamoe.mirai.internal.contact.info.MemberInfoImpl -import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl +import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupImpl +import net.mamoe.mirai.internal.contact.info.* import net.mamoe.mirai.internal.contact.toMiraiFriendInfo import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage @@ -160,6 +158,38 @@ internal class ContactUpdaterImpl( return friendInfos } + suspend fun refreshFriendGroupList(): List { + logger.info { "Start loading friendGroup list..." } + val friendGroupInfos = mutableListOf() + + var count = 0 + var total: Short + while (true) { + val data = bot.network.sendAndExpect( + FriendList.GetFriendGroupList(bot.client, 0, 0, count, 150) + ) + + total = data.totoalGroupCount + + for (jceInfo in data.groupList) { + friendGroupInfos.add( + FriendGroupImpl( + bot, FriendGroupInfo( + jceInfo.groupId.toInt(), + jceInfo.groupname + ) + ) + ) + } + + count += data.groupList.size + logger.verbose { "Loading friendGroup list: ${count}/${total}" } + if (count >= total) break + } + logger.info { "Successfully loaded friendGroup list: $count in total" } + return friendGroupInfos + } + val list = if (friendListCache?.isValid(registerResp) == true) { val list = friendListCache.list logger.info { "Loaded ${list.size} friends from local cache." } @@ -178,11 +208,13 @@ internal class ContactUpdaterImpl( } } + bot.friendGroups.friendGroups.clear() + bot.friendGroups.friendGroups.addAll(refreshFriendGroupList()) + for (friendInfoImpl in list) { bot.addNewFriendAndRemoveStranger(friendInfoImpl) } - initFriendOk = true } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendGroupNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendGroupNoticeProcessor.kt new file mode 100644 index 000000000..39f417bbd --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendGroupNoticeProcessor.kt @@ -0,0 +1,102 @@ +/* + * 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.network.notice.priv + +import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupImpl +import net.mamoe.mirai.internal.contact.friendgroup.impl +import net.mamoe.mirai.internal.contact.impl +import net.mamoe.mirai.internal.contact.info.FriendGroupInfo +import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor +import net.mamoe.mirai.internal.network.components.NoticePipelineContext +import net.mamoe.mirai.internal.network.notice.NewContactSupport +import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 +import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27 +import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.context +import net.mamoe.mirai.utils.error +import net.mamoe.mirai.utils.warning + +internal class FriendGroupNoticeProcessor( + private val logger: MiraiLogger, +) : MixedNoticeProcessor(), NewContactSupport { + + override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) = data.context { + when (data.uSubMsgType) { + 0x27L -> { + val body = vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.SubMsgType0x27MsgBody.serializer()) + for (msgModInfo in body.msgModInfos) { + markAsConsumed(msgModInfo) + when { + msgModInfo.msgModFriendGroup != null -> handleFriendGroupChanged( + msgModInfo.msgModFriendGroup, logger + ) + msgModInfo.msgModGroupName != null -> handleFriendGroupNameChanged( + msgModInfo.msgModGroupName, logger + ) + msgModInfo.msgDelGroup != null -> handleDelGroup(msgModInfo.msgDelGroup, logger) + msgModInfo.msgAddGroup != null -> handleAddGroup(msgModInfo.msgAddGroup) + else -> markNotConsumed(msgModInfo) + } + } + } + } + } + + private fun NoticePipelineContext.handleAddGroup( + addGroup: Submsgtype0x27.SubMsgType0x27.AddGroup + ) { + bot.friendGroups.friendGroups.add( + FriendGroupImpl( + bot, + FriendGroupInfo(addGroup.groupid, addGroup.groupname.decodeToString()) + ) + ) + } + + private fun NoticePipelineContext.handleDelGroup( + delGroup: Submsgtype0x27.SubMsgType0x27.DelGroup, logger: MiraiLogger + ) { + bot.friendGroups[delGroup.groupid]?.let { friendGroup -> + friendGroup.friends.forEach { + it.impl().info.friendGroupId = 0 + } + bot.friendGroups.friendGroups.remove(friendGroup) + } ?: let { + logger.warning { "Detected friendGroup(id=${delGroup.groupid}) was removed but it isn't available in bot's friendGroups list" } + return + } + } + + private fun NoticePipelineContext.handleFriendGroupNameChanged( + modFriendGroup: Submsgtype0x27.SubMsgType0x27.ModGroupName, logger: MiraiLogger + ) { + bot.friendGroups[modFriendGroup.groupid]?.let { + it.impl().name = modFriendGroup.groupname.decodeToString() + } ?: let { + logger.warning { "Detected friendGroup(id=${modFriendGroup.groupid}) was renamed but it cannot be found in bot's friendGroups list" } + return + } + } + + private fun NoticePipelineContext.handleFriendGroupChanged( + modFriendGroup: Submsgtype0x27.SubMsgType0x27.ModFriendGroup, + logger: MiraiLogger + ) { + modFriendGroup.msgFrdGroup.forEach { body -> + val friend = bot.getFriend(body.fuin) ?: let { + logger.error { "Detected friend(id=${body.fuin}) was moved to friendGroup(id=${body.uint32NewGroupId}) but friend not found in bot's friends list" } + return + } + if (friend.impl().info.friendGroupId == body.uint32NewGroupId.first()) return@forEach + friend.info.friendGroupId = body.uint32NewGroupId.first() + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt index 8d9a8ee62..ac782742c 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt @@ -264,6 +264,7 @@ internal class FriendNoticeProcessor( uin = body.msgAddFrdNotify.fuin, nick = body.msgAddFrdNotify.fuinNick, remark = "", + friendGroupId = 0, ) val removed = bot.removeStranger(info.uin) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/MoveGroupMemPack.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/MoveGroupMemPack.kt new file mode 100644 index 000000000..07a4d272d --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/MoveGroupMemPack.kt @@ -0,0 +1,31 @@ +/* + * 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.network.protocol.data.jce + +import kotlinx.serialization.Serializable +import net.mamoe.mirai.internal.utils.io.JceStruct +import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId +import kotlin.jvm.JvmField + + +@Serializable +internal class MovGroupMemReq( + @JvmField @TarsId(0) val uin: Long = 0L, + @JvmField @TarsId(1) val reqtype: Byte = 0, + @JvmField @TarsId(2) val vecBody: ByteArray? = null +) : JceStruct + +@Serializable +internal class MovGroupMemResp( + @JvmField @TarsId(0) val uin: Long = 0L, + @JvmField @TarsId(1) val reqtype: Byte = 0, + @JvmField @TarsId(2) val result: Byte = 0, + @JvmField @TarsId(3) val errorString: String = "" +) : JceStruct \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SetGroupPack.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SetGroupPack.kt new file mode 100644 index 000000000..edb01e97c --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SetGroupPack.kt @@ -0,0 +1,31 @@ +/* + * 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.network.protocol.data.jce + +import kotlinx.serialization.Serializable +import net.mamoe.mirai.internal.utils.io.JceStruct +import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId +import kotlin.jvm.JvmField + + +@Serializable +internal class SetGroupReq( + @JvmField @TarsId(0) val reqtype: Int = 0, + @JvmField @TarsId(1) val uin: Long = 0L, + @JvmField @TarsId(2) val vecBody: ByteArray? = null +) : JceStruct + +@Serializable +internal class SetGroupResp( + @JvmField @TarsId(0) val reqtype: Byte = 0, + @JvmField @TarsId(1) val result: Byte = 0, + @JvmField @TarsId(2) val vecBody: ByteArray? = null, + @JvmField @TarsId(3) val errorString: String = "" +) : JceStruct \ No newline at end of file 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 8b391efc6..70c182680 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -144,6 +144,8 @@ internal object KnownPacketFactories { FriendList.DelFriend, FriendList.GetTroopListSimplify, FriendList.GetTroopMemberList, + FriendList.SetGroupReqPack, + FriendList.MoveGroupMemReqPack, ImgStore.GroupPicUp, PttStore.GroupPttUp, PttStore.GroupPttDown, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/FriendList.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/FriendList.kt index 2431a5814..9271b58d4 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/FriendList.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/FriendList.kt @@ -20,11 +20,9 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.Vec0xd50 import net.mamoe.mirai.internal.network.protocol.data.proto.Vec0xd6b 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.jceRequestSBuffer -import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket -import net.mamoe.mirai.internal.utils.io.serialization.toByteArray -import net.mamoe.mirai.internal.utils.io.serialization.writeJceStruct +import net.mamoe.mirai.internal.utils.io.serialization.* import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.utils.toByteArray internal class FriendList { @@ -163,7 +161,10 @@ internal class FriendList { class Response( val selfInfo: FriendInfo?, val totalFriendCount: Short, - val friendList: List + val friendList: List, + // for FriendGroup + val groupList: List, + val totoalGroupCount: Short ) : Packet { override fun toString(): String = "FriendList.GetFriendGroupList.Response" } @@ -177,7 +178,9 @@ internal class FriendList { return Response( res.stSelfInfo, res.totoalFriendCount, - res.vecFriendInfo.orEmpty() + res.vecFriendInfo.orEmpty(), + res.vecGroupInfo.orEmpty(), + res.totoalGroupCount ?: -1 ) } @@ -247,7 +250,7 @@ internal class FriendList { GetFriendListReq.serializer(), GetFriendListReq( reqtype = 3, - ifReflush = if (friendListStartIndex <= 0) { + ifReflush = if (friendListStartIndex + groupListStartIndex <= 0) { 0 } else { 1 @@ -285,4 +288,118 @@ internal class FriendList { ) } } + + // for FriendGroup + internal object SetGroupReqPack : OutgoingPacketFactory("friendlist.SetGroupReq") { + class Response( + // Success: result == 0x00 + val result: Byte, + val errStr: String, + // groupId for delete + val groupId: Int, + val isSuccess: Boolean = result.toInt() == 0 + ) : Packet { + override fun toString(): String { + return "SetGroupResp(isSuccess=$isSuccess,resultCode=$result, errString=$errStr, groupId=$groupId)" + } + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val pack = this.readUniPacket(SetGroupResp.serializer()) + return if (pack.result.toInt() == 0) { + Response(pack.result, pack.errorString, pack.vecBody?.get(8)?.toInt() ?: -1) + } else { + Response(pack.result, pack.errorString, -1) + } + } + + object New { + operator fun invoke( + client: QQAndroidClient, groupName: String + ) = buildOutgoingUniPacket(client) { + val arr = groupName.toByteArray() + // maybe is constant + val constant: Byte = 0x01 + writeJceRequestPacket( + servantName = "mqq.IMService.FriendListServiceServantObj", + funcName = "SetGroupReq", + serializer = SetGroupReq.serializer(), + body = SetGroupReq(0, client.uin, byteArrayOf(constant, arr.size.toByte()) + arr) + ) + } + } + + object Rename { + operator fun invoke(client: QQAndroidClient, newName: String, id: Int) = buildOutgoingUniPacket(client) { + val arr = newName.toByteArray() + writeJceRequestPacket( + servantName = "mqq.IMService.FriendListServiceServantObj", + funcName = "SetGroupReq", + serializer = SetGroupReq.serializer(), + body = SetGroupReq(1, client.uin, byteArrayOf(id.toByte(), arr.size.toByte()) + arr) + ) + } + } + + object Delete { + operator fun invoke(client: QQAndroidClient, id: Int) = buildOutgoingUniPacket(client) { + writeJceRequestPacket( + servantName = "mqq.IMService.FriendListServiceServantObj", + funcName = "SetGroupReq", + serializer = SetGroupReq.serializer(), + body = SetGroupReq(2, client.uin, byteArrayOf(id.toByte())) + ) + } + } + } + + // for FriendGroup + internal object MoveGroupMemReqPack : + OutgoingPacketFactory("friendlist.MovGroupMemReq") { + private fun Long.toByteArray2(): ByteArray { + val arr = this.toByteArray() + val index = arr.indexOfFirst { it.toInt() != 0 } + return arr.sliceArray(index until arr.size) + } + + // 如果不成功会自动移动到id = 0的默认好友分组, result 还是会返回0 + class Response( + // Success: result == 0x00 + val result: Byte, + val errStr: String, + val isSuccess: Boolean = result.toInt() == 0 + ) : Packet { + override fun toString(): String { + return "MoveGroupMemReq(isSuccess=$isSuccess,resultCode=$result, errString=$errStr)" + } + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val pack = this.readUniPacket(MovGroupMemResp.serializer()) + return Response(pack.result, pack.errorString) + } + + operator fun invoke( + client: QQAndroidClient, + // friend id + id: Long, + // friend group id + groupId: Int + ) = buildOutgoingUniPacket(client) { + writeJceRequestPacket( + servantName = "mqq.IMService.FriendListServiceServantObj", + funcName = "MovGroupMemReq", + serializer = MovGroupMemReq.serializer(), + body = MovGroupMemReq( + client.uin, + 0, + byteArrayOf( + 0x01, + 0x00, + (id.toByteArray2().size + 1).toByte() + ) + id.toByteArray2() + byteArrayOf(groupId.toByte(), 0x00, 0x00) + ) + ) + } + } } diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/summarycard/SummaryCard.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/summarycard/SummaryCard.kt index 40fc508f2..3ffff31dc 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/summarycard/SummaryCard.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/summarycard/SummaryCard.kt @@ -35,9 +35,10 @@ internal data class UserProfileImpl( override val qLevel: Int, override val sex: UserProfile.Sex, override val sign: String, + override val friendGroupId: Int, ) : Packet, UserProfile { override fun toString(): String { - return "UserProfile(nickname=$nickname, email=$email, age=$age, qLevel=$qLevel, sex=$sex, sign=$sign)" + return "UserProfile(nickname=$nickname, email=$email, age=$age, qLevel=$qLevel, sex=$sex, sign=$sign, friendGroupId=$friendGroupId)" } } @@ -118,7 +119,8 @@ internal object SummaryCard { 1 -> UserProfile.Sex.FEMALE else -> UserProfile.Sex.UNKNOWN }, - sign = sign + sign = sign, + friendGroupId = response.uFriendGroupId?.toInt() ?: 0 ) } } diff --git a/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt b/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt index 62056cc71..d42344dfc 100644 --- a/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt +++ b/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt @@ -120,8 +120,8 @@ internal interface GroupExtensions { friends.delegate.add(friend) } - fun Bot.addFriend(id: Long, nick: String = "friend$id", remark: String = ""): FriendImpl { - return FriendImpl(bot.cast(), bot.coroutineContext, FriendInfoImpl(id, nick, remark)).also { + fun Bot.addFriend(id: Long, nick: String = "friend$id", remark: String = "", friendGroupId: Int = 0): FriendImpl { + return FriendImpl(bot.cast(), bot.coroutineContext, FriendInfoImpl(id, nick, remark, friendGroupId)).also { friends.delegate.add(it) } }