mirai icon indicating copy to clipboard operation
mirai copied to clipboard

Support friend group

Open Nambers opened this issue 3 years ago • 11 comments

close #1243 todo:

  • [x] friendgroup设计
  • for review 确定一些细节设计

protocol:

  • [x] 重命名分组
  • [x] 获取全部分组数据
  • [x] 新建/删除分组
  • [x] 移动friend到指定分组

Nambers avatar Jun 24 '22 16:06 Nambers

mirai 已经有群叫 Group 了,应该会考虑换一个叫法

Him188 avatar Jun 24 '22 16:06 Him188

mirai 已经有群叫 Group 了,应该会考虑换一个叫法

可以叫 category ?

sandtechnology avatar Jun 24 '22 16:06 sandtechnology

我目前先暂时用FriendGroup这个名字写下去,后面定了我再改

Nambers avatar Jun 26 '22 11:06 Nambers

GroupGroup(

LaoLittle avatar Jun 27 '22 08:06 LaoLittle

对于“组”这个概念感觉只有叫 collection 或者 set 比较合适了,但是这两个又同时是比较通用的集合概念

Him188 avatar Jun 27 '22 13:06 Him188

对于“组”这个概念感觉只有叫 collection 或者 set 比较合适了,但是这两个又同时是比较通用的集合概念

classification

cssxsh avatar Jun 27 '22 13:06 cssxsh

对于“组”这个概念感觉只有叫 collection 或者 set 比较合适了,但是这两个又同时是比较通用的集合概念

cluster

Samarium150 avatar Jun 27 '22 13:06 Samarium150

domain?

Karlatemp avatar Jun 27 '22 23:06 Karlatemp

感觉...要不还是就叫 FriendGroup 吧

Him188 avatar Jul 03 '22 03:07 Him188

已过测试

private suspend fun refreshFriendGroupList(bot: QQAndroidBot): MutableList<FriendGroup> {
    val friendGroupInfos = mutableListOf<FriendGroup>()

    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, jceInfo.friendCount, jceInfo.onlineFriendCount
                    )
                )
            )
        }

        count += data.groupList.size
        println("Loading friendGroup list: ${count}/${total}")
        if (count >= total) break
    }
    println("Successfully loaded friendGroup list: $count in total")
    return friendGroupInfos
}
suspend fun main() {
    val bot = BotFactory.newBot(0L, "") .alsoLogin()
    assertEquals("我的好友", bot.friendGroups.first().name)
    // create
    val newGroup = bot.friendGroups.create("111")
    // refresh from server
    refreshFriendGroupList(bot as QQAndroidBot).forEach {
        println(it.name + " for " + it.id)
    }
    val newGroupInList = refreshFriendGroupList(bot).firstOrNull { it.name == "111" }

    assertTrue { newGroupInList != null && newGroupInList.id == newGroup.id }
    // moveIn
    val friend = bot.getFriend(1)!!
    assertTrue {
        bot.friendGroups[friend.friendGroup!!.id]!!.friends.contains(friend)
    }
    val old = bot.friendGroups.friendGroups[0]
    old.moveIn(friend)
    val target = bot.friendGroups.friendGroups[2]
    assertFalse {
        target.friends.contains(friend)
    }
    target.moveIn(friend)
    assertTrue {
        target.friends.contains(friend) && bot.friendGroups[friend.friendGroup!!.id]!!.friends.contains(friend.id) && !old.friends.contains(
            friend
        )
    }
    assertEquals(friend.queryProfile().friendGroupId, friend.friendGroup!!.id)
    newGroup.moveIn(friend)
    // rename
    newGroup.renameTo("222")
    assertTrue { refreshFriendGroupList(bot).first { it.name == "222" }.id == newGroup.id }
    assertTrue { bot.friendGroups.first { it.name == "222" } == newGroup }
    // del
    newGroup.delete()
    assertTrue {
        (newGroup.friends.first() as FriendImpl).info.friendGroupId == newGroup.friends.first()
            .queryProfile().friendGroupId
    }
    // refresh from server
    bot.friendGroups.friendGroups = refreshFriendGroupList(bot)
    assertTrue { bot.friendGroups.firstOrNull { it == newGroup } == null }
    println("-- test end --")
    bot.join()
}

Nambers avatar Jul 04 '22 12:07 Nambers

已经 rebase 到 dev 测试代码:

/*
 * 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.local

//import net.mamoe.mirai.internal.network.protocol.data.jce.MovGroupMemReq
import kotlinx.serialization.DeserializationStrategy
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.data.FriendGroup
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.FriendGroupImpl
import net.mamoe.mirai.internal.contact.FriendGroupsImpl
import net.mamoe.mirai.internal.contact.FriendImpl
import net.mamoe.mirai.internal.contact.info
import net.mamoe.mirai.internal.contact.info.FriendGroupInfo
import net.mamoe.mirai.internal.network.protocol.data.jce.GetFriendListReq
import net.mamoe.mirai.internal.network.protocol.data.jce.GetFriendListResp
import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210
import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack
import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.internal.utils.structureToString
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.info
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.verbose
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue

fun String.decodeHex(): ByteArray {
    check(length % 2 == 0) { "Must have an even length" }

    return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
}

private fun Long.toByteArray2(): ByteArray {
    val arr = this.toByteArray()
    val index = arr.indexOfFirst { it.toInt() != 0 }
    return arr.sliceArray(index until arr.size)
}

fun ByteArray.toHex(): String = joinToString(separator = " ") { eachByte -> "%02x".format(eachByte) }
fun <T> ByteArray.decode(deserializer: DeserializationStrategy<T>) = Tars.UTF_8.decodeFromByteArray(deserializer, this)
private suspend fun refreshFriendGroupList(bot: QQAndroidBot): List<FriendGroup> {
    val friendGroupInfos = mutableListOf<FriendGroup>()

    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
        println("Loading friendGroup list: ${count}/${total}")
        if (count >= total) break
    }
    println("Successfully loaded friendGroup list: $count in total")
    return friendGroupInfos
}

suspend fun main() {
    val bot = BotFactory.newBot(id, "pwd") {
        fileBasedDeviceInfo("device.json")
        this.protocol = BotConfiguration.MiraiProtocol.MACOS
    }.alsoLogin()
    assertEquals("我的好友", bot.friendGroups.first().name)
    // create
    val newGroup = bot.friendGroups.create("111")
    val newGroup2 = bot.friendGroups.create("111")
    assertTrue { refreshFriendGroupList(bot as QQAndroidBot).count { it.name == "111" } == 2 && newGroup.id != newGroup2.id }
    newGroup2.delete()
    // refresh from server
    refreshFriendGroupList(bot as QQAndroidBot).forEach {
        println(it.name + " for " + it.id)
    }
    val newGroupInList = refreshFriendGroupList(bot).firstOrNull { it.name == "111" }

    assertTrue { newGroupInList != null && newGroupInList.id == newGroup.id }
    // moveIn
    val friend = bot.getFriend(1930893235)!!
    assertTrue {
        bot.friendGroups[friend.friendGroup.id]!!.friends.contains(friend)
    }
    val old = bot.friendGroups.friendGroups.first()
    old.moveIn(friend)
    val target = bot.friendGroups.friendGroups.last()
    assertFalse {
        target.friends.contains(friend)
    }
    target.moveIn(friend)
    assertTrue {
        target.friends.contains(friend) && bot.friendGroups[friend.friendGroup.id]!!.friends.contains(friend) && !old.friends.contains(
            friend
        )
    }
    assertEquals(friend.queryProfile().friendGroupId, friend.friendGroup.id)
    newGroup.moveIn(friend)
    assertTrue { newGroup.friends.contains(friend) && friend.queryProfile().friendGroupId == newGroup.id }
    // rename
    newGroup.renameTo("222")
    assertTrue { refreshFriendGroupList(bot).first { it.name == "222" }.id == newGroup.id }
    assertTrue { bot.friendGroups.first { it.name == "222" } == newGroup }
    // del
    newGroup.delete()
    assertTrue { refreshFriendGroupList(bot).first { it.id == 0 }.friends.contains(friend) }
    // refresh from server
    bot.friendGroups.friendGroups.clear()
    bot.friendGroups.friendGroups.addAll(refreshFriendGroupList(bot))
    assertTrue { bot.friendGroups.firstOrNull { it == newGroup } == null }
    println("-- test end --")
    bot.join()
}

Nambers avatar Jul 21 '22 01:07 Nambers