diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 08eefee5c..e986e74a1 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -11,9 +11,7 @@ package net.mamoe.mirai.qqandroid import kotlinx.coroutines.launch import net.mamoe.mirai.contact.* -import net.mamoe.mirai.data.FriendNameRemark -import net.mamoe.mirai.data.PreviousNameList -import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.data.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent @@ -24,6 +22,7 @@ import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.NotOnlineImageFromFile import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.postImage +import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore @@ -33,6 +32,7 @@ import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.toUHexString import kotlin.coroutines.CoroutineContext +import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo internal abstract class ContactImpl : Contact { override fun hashCode(): Int { @@ -49,10 +49,22 @@ internal abstract class ContactImpl : Contact { } } -internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long) : ContactImpl(), QQ { - override val bot: QQAndroidBot by bot.unsafeWeakRef() +internal inline class FriendInfoImpl( + private val jceFriendInfo: JceFriendInfo +) : FriendInfo { + override val nick: String get() = jceFriendInfo.nick ?: "" + override val uin: Long get() = jceFriendInfo.friendUin +} - override lateinit var nick: String +internal class QQImpl( + bot: QQAndroidBot, + override val coroutineContext: CoroutineContext, + override val id: Long, + private val friendInfo: FriendInfo +) : ContactImpl(), QQ { + override val bot: QQAndroidBot by bot.unsafeWeakRef() + override val nick: String + get() = friendInfo.nick override suspend fun sendMessage(message: MessageChain) { val event = FriendMessageSendEvent(this, message).broadcast() @@ -135,14 +147,17 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin image.input.close() } + @MiraiExperimentalAPI override suspend fun queryProfile(): Profile { TODO("not implemented") } + @MiraiExperimentalAPI override suspend fun queryPreviousNameList(): PreviousNameList { TODO("not implemented") } + @MiraiExperimentalAPI override suspend fun queryRemark(): FriendNameRemark { TODO("not implemented") } @@ -156,24 +171,27 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin } +@Suppress("MemberVisibilityCanBePrivate") internal class MemberImpl( qq: QQImpl, - var _groupCard: String, - var _specialTitle: String, group: GroupImpl, override val coroutineContext: CoroutineContext, - override var permission: MemberPermission + memberInfo: MemberInfo ) : ContactImpl(), Member, QQ by qq { override val group: GroupImpl by group.unsafeWeakRef() val qq: QQImpl by qq.unsafeWeakRef() + override var permission: MemberPermission = memberInfo.permission + internal var _nameCard: String = memberInfo.nameCard + internal var _specialTitle: String = memberInfo.specialTitle + override var nameCard: String - get() = _groupCard + get() = _nameCard set(newValue) { group.checkBotPermissionOperator() - if (_groupCard != newValue) { - val oldValue = _groupCard - _groupCard = newValue + if (_nameCard != newValue) { + val oldValue = _nameCard + _nameCard = newValue launch { bot.network.run { TroopManagement.EditGroupNametag( @@ -223,7 +241,8 @@ internal class MemberImpl( ).sendAndExpect() } - MemberMuteEvent(this@MemberImpl, durationSeconds, null).broadcast() + @Suppress("RemoveRedundantQualifierName") // or unresolved reference + net.mamoe.mirai.event.events.MemberMuteEvent(this@MemberImpl, durationSeconds, null).broadcast() return true } @@ -241,7 +260,8 @@ internal class MemberImpl( ).sendAndExpect() } - MemberUnmuteEvent(this@MemberImpl, null).broadcast() + @Suppress("RemoveRedundantQualifierName") // or unresolved reference + net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast() return true } @@ -269,25 +289,60 @@ internal class MemberImpl( override fun hashCode(): Int = super.hashCode() } +internal class MemberInfoImpl( + private val jceInfo: StTroopMemberInfo, + private val groupOwnerId: Long +) : MemberInfo { + override val uin: Long get() = jceInfo.memberUin + override val nameCard: String get() = jceInfo.sName ?: "" + override val nick: String get() = jceInfo.nick + override val permission: MemberPermission + get() = when { + jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER + jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR + else -> MemberPermission.MEMBER + } + override val specialTitle: String get() = jceInfo.sSpecialTitle ?: "" +} /** * 对GroupImpl * 中name/announcement的更改会直接向服务器异步汇报 */ +@Suppress("PropertyName") @UseExperimental(MiraiInternalAPI::class) internal class GroupImpl( bot: QQAndroidBot, override val coroutineContext: CoroutineContext, override val id: Long, - val uin: Long, - var _name: String, - var _announcement: String, - var _allowMemberInvite: Boolean, - var _confessTalk: Boolean, - var _muteAll: Boolean, - var _autoApprove: Boolean, - var _anonymousChat: Boolean, - override val members: ContactList + groupInfo: GroupInfo, + members: Sequence ) : ContactImpl(), Group { + override val bot: QQAndroidBot by bot.unsafeWeakRef() + val uin: Long = groupInfo.uin + + override lateinit var owner: Member + + @UseExperimental(MiraiExperimentalAPI::class) + override lateinit var botPermission: MemberPermission + + override val members: ContactList = ContactList(members.asSequence().mapNotNull { + if (it.uin == bot.uin) { + botPermission = it.permission + null + } else Member(it).also { member -> + if (member.permission == MemberPermission.OWNER) { + owner = member + } + } + }.toLockFreeLinkedList()) + + internal var _name: String = groupInfo.name + internal var _announcement: String = groupInfo.memo + internal var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite + internal var _confessTalk: Boolean = groupInfo.confessTalk + internal var _muteAll: Boolean = groupInfo.muteAll + internal var _autoApprove: Boolean = groupInfo.autoApprove + internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat override var name: String get() = _name @@ -304,7 +359,7 @@ internal class GroupImpl( newName = newValue ).sendWithoutExpect() } - GroupNameChangeEvent(oldValue, newValue, this@GroupImpl, null).broadcast() + GroupNameChangeEvent(oldValue, newValue, this@GroupImpl, true).broadcast() } } } @@ -377,7 +432,7 @@ internal class GroupImpl( switch = newValue ).sendWithoutExpect() } - GroupAllowConfessTalkEvent(oldValue, newValue, this@GroupImpl, null).broadcast() + GroupAllowConfessTalkEvent(oldValue, newValue, this@GroupImpl, true).broadcast() } } } @@ -403,16 +458,21 @@ internal class GroupImpl( } } - - override lateinit var owner: Member - @UseExperimental(MiraiExperimentalAPI::class) - override var botPermission: MemberPermission = MemberPermission.MEMBER - override suspend fun quit(): Boolean { check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" } TODO("not implemented") } + @UseExperimental(MiraiExperimentalAPI::class) + override fun Member(memberInfo: MemberInfo): Member { + return MemberImpl( + bot.QQ(memberInfo) as QQImpl, + this, + this.coroutineContext, + memberInfo + ) + } + override operator fun get(id: Long): Member { return members.delegate.filteringGetOrNull { it.id == id } ?: throw NoSuchElementException("member $id not found in group $uin") @@ -426,8 +486,6 @@ internal class GroupImpl( return members.delegate.filteringGetOrNull { it.id == id } } - override val bot: QQAndroidBot by bot.unsafeWeakRef() - override suspend fun sendMessage(message: MessageChain) { val event = GroupMessageSendEvent(this, message).broadcast() if (event.isCancelled) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index c87295985..c8fac4b3f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -19,13 +19,13 @@ import kotlinx.io.core.IoBuffer import kotlinx.io.core.use import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.AddFriendResult +import net.mamoe.mirai.data.FriendInfo +import net.mamoe.mirai.data.GroupInfo +import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.WeakRef +import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.transferTo -import net.mamoe.mirai.utils.toList /** * 机器人对象. 一个机器人实例登录一个 QQ 账号. @@ -65,6 +65,13 @@ abstract class Bot : CoroutineScope { */ abstract val uin: Long + /** + * 昵称 + */ + @MiraiExperimentalAPI("还未支持") + val nick: String + get() = TODO("bot 昵称获取") + /** * 日志记录器 */ @@ -116,7 +123,7 @@ abstract class Bot : CoroutineScope { * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job. * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭. */ - abstract fun QQ(id: Long): QQ + abstract fun QQ(friendInfo: FriendInfo): QQ /** * 机器人加入的群列表. @@ -131,6 +138,25 @@ abstract class Bot : CoroutineScope { ?: throw NoSuchElementException("No such group $id for bot ${this.uin}") } + /** + * 获取群列表. 返回值前 32 bits 为 uin, 后 32 bits 为 groupCode + */ + abstract suspend fun queryGroupList(): Sequence + + /** + * 查询群资料. 获得的仅为当前时刻的资料. + * 请优先使用 [getGroup] 然后查看群资料. + */ + abstract suspend fun queryGroupInfo(id: Long): GroupInfo + + /** + * 查询群成员列表. + * 请优先使用 [getGroup], [Group.members] 查看群成员. + * + * 这个函数很慢. 请不要频繁使用. + */ + abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence + // TODO 目前还不能构造群对象. 这将在以后支持 // endregion diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index d4448a6ea..ff0a60346 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -12,8 +12,10 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.events.* import net.mamoe.mirai.utils.MiraiExperimentalAPI +import kotlin.jvm.JvmName /** * 群. 在 QQ Android 中叫做 "Troop" @@ -117,6 +119,14 @@ interface Group : Contact, CoroutineScope { */ suspend fun quit(): Boolean + /** + * 构造一个 [Member]. + * 非特殊情况请不要使用这个函数. 优先使用 [get]. + */ + @MiraiExperimentalAPI("dangerous") + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("newMember") + fun Member(memberInfo: MemberInfo): Member companion object { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt new file mode 100644 index 000000000..4eeb0cfff --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendInfo.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2020 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/master/LICENSE + */ + +package net.mamoe.mirai.data + +interface FriendInfo { + val uin: Long + + val nick: String +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt new file mode 100644 index 000000000..20a1e3db5 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt @@ -0,0 +1,60 @@ +package net.mamoe.mirai.data + +import net.mamoe.mirai.Bot + +/** + * 群资料. + * + * 通过 [Bot.queryGroupInfo] 得到 + */ +interface GroupInfo { + /** + * Uin + */ + val uin: Long + + /** + * 群号码 + */ // 由 uin 计算得到 + val groupCode: Long + + /** + * 名称 + */ + val name: String + + /** + * 群主 + */ + val owner: Long + + /** + * 入群公告 + */ + val memo: String + + /** + * 允许群员邀请其他人加入群 + */ + val allowMemberInvite: Boolean + + /** + * 允许匿名聊天 + */ + val allowAnonymousChat: Boolean + + /** + * 自动审批加群请求 + */ + val autoApprove: Boolean + + /** + * 坦白说开启状态 + */ + val confessTalk: Boolean + + /** + * 全员禁言 + */ + val muteAll: Boolean +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt new file mode 100644 index 000000000..3ed39ef10 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2020 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/master/LICENSE + */ + +package net.mamoe.mirai.data + +import net.mamoe.mirai.contact.MemberPermission + +interface MemberInfo : FriendInfo { + val nameCard: String + + val permission: MemberPermission + + val specialTitle: String +} \ No newline at end of file