Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-02-12 22:29:14 +08:00
commit 240183d5a5
20 changed files with 505 additions and 194 deletions

View File

@ -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<TroopManagement.Mute.Response>()
}
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<TroopManagement.Mute.Response>()
}
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<Member>
groupInfo: GroupInfo,
members: Sequence<MemberInfo>
) : 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<Member> = ContactList(members.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) {

View File

@ -17,11 +17,18 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.filteringGetOrNull
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.event.events.BotEvent
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence
import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class)
@ -31,22 +38,32 @@ internal expect class QQAndroidBot constructor(
configuration: BotConfiguration
) : QQAndroidBotBase
@UseExperimental(MiraiInternalAPI::class)
@UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal abstract class QQAndroidBotBase constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
val client: QQAndroidClient =
QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot, device = configuration.deviceInfo?.invoke(context) ?: SystemDeviceInfo(context))
QQAndroidClient(
context,
account,
bot = @Suppress("LeakingThis") this as QQAndroidBot,
device = configuration.deviceInfo?.invoke(context) ?: SystemDeviceInfo(context)
)
internal var firstLoginSucceed: Boolean = false
override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
override val selfQQ: QQ by lazy { QQ(uin) }
override val selfQQ: QQ by lazy {
QQ(object : FriendInfo {
override val uin: Long get() = this@QQAndroidBotBase.uin
override val nick: String get() = this@QQAndroidBotBase.nick
})
}
override fun QQ(id: Long): QQ {
return QQImpl(this as QQAndroidBot, coroutineContext, id)
override fun QQ(friendInfo: FriendInfo): QQ {
return QQImpl(this as QQAndroidBot, coroutineContext, friendInfo.uin, friendInfo)
}
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
@ -60,6 +77,45 @@ internal abstract class QQAndroidBotBase constructor(
return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin } ?: throw NoSuchElementException("Can not found group with ID=${uin}")
}
fun getGroupByUinOrNull(uin: Long): Group? {
return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin }
}
override suspend fun queryGroupList(): Sequence<Long> {
return network.run {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
}
override suspend fun queryGroupInfo(id: Long): GroupInfo = network.run {
TroopManagement.GetGroupInfo(
client = bot.client,
groupCode = id
).sendAndExpect<GroupInfoImpl>()
}
override suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo> = network.run {
var nextUin = 0L
var sequence = sequenceOf<MemberInfoImpl>()
while (true) {
val data = FriendList.GetTroopMemberList(
client = bot.client,
targetGroupUin = groupUin,
targetGroupCode = groupCode,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
sequence += data.members.asSequence().map { troopMemberInfo ->
MemberInfoImpl(troopMemberInfo, ownerId)
}
nextUin = data.nextUin
if (nextUin == 0L) {
break
}
}
return sequence
}
override fun onEvent(event: BotEvent): Boolean {
return firstLoginSucceed
}

View File

@ -18,22 +18,19 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.use
import net.mamoe.mirai.contact.ContactList
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.FriendInfoImpl
import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.MemberImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.QQImpl
import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
@ -148,7 +145,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
totalFriendCount = data.totalFriendCount
data.friendList.forEach {
// atomic add
bot.qqs.delegate.addLast(QQImpl(bot, bot.coroutineContext, it.friendUin)).also {
bot.qqs.delegate.addLast(QQImpl(bot, bot.coroutineContext, it.friendUin, FriendInfoImpl(it))).also {
currentFriendCount++
}
}
@ -171,32 +168,33 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
troopListData.groups.forEach { troopNum ->
val contactList = ContactList(LockFreeLinkedList<Member>())
val groupInfoResponse =
TroopManagement.GetGroupOperationInfo(
client = bot.client,
groupCode = troopNum.groupCode
).sendAndExpect<TroopManagement.GetGroupOperationInfo.Response>()
val group =
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
uin = troopNum.groupUin,
_name = troopNum.groupName,
_announcement = troopNum.groupMemo,
_allowMemberInvite = groupInfoResponse.allowMemberInvite,
_confessTalk = groupInfoResponse.confessTalk,
_muteAll = troopNum.dwShutUpTimestamp != 0L,
_autoApprove = groupInfoResponse.autoApprove,
_anonymousChat = groupInfoResponse.allowAnonymousChat,
members = contactList
)
bot.groups.delegate.addLast(group)
launch {
try {
fillTroopMemberList(group, contactList, troopNum.dwGroupOwnerUin)
bot.groups.delegate.addLast(
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl
if (this.delegate.groupName == null) {
this.delegate.groupName = troopNum.groupName
}
if (this.delegate.groupMemo == null) {
this.delegate.groupMemo = troopNum.groupMemo
}
if (this.delegate.groupUin == null) {
this.delegate.groupUin = troopNum.groupUin
}
this.delegate.groupCode = troopNum.groupCode
},
members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin)
)
)
} catch (e: Exception) {
bot.logger.error("${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试")
bot.logger.error(e)
@ -242,47 +240,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
return lastException
}
suspend fun fillTroopMemberList(group: GroupImpl, list: ContactList<Member>, owner: Long) {
bot.logger.verbose("开始获取群[${group.uin}]成员列表")
var size = 0
var nextUin = 0L
while (true) {
val data = FriendList.GetTroopMemberList(
client = bot.client,
targetGroupUin = group.uin,
targetGroupCode = group.id,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
data.members.forEach { troopMemberInfo ->
val member = MemberImpl(
qq = (bot.QQ(troopMemberInfo.memberUin) as QQImpl).also { it.nick = troopMemberInfo.nick },
_groupCard = troopMemberInfo.sName ?: "",
_specialTitle = troopMemberInfo.sSpecialTitle ?: "",
group = group,
coroutineContext = group.coroutineContext,
permission = when {
troopMemberInfo.memberUin == owner -> MemberPermission.OWNER
troopMemberInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
}
)
if (member.permission == MemberPermission.OWNER) {
group.owner = member
}
if (troopMemberInfo.memberUin != bot.uin) {
list.delegate.addLast(member)
} else {
group.botPermission = member.permission
}
size += data.members.size
nextUin = data.nextUin
}
if (nextUin == 0L) {
break
}
}
}
/**
* 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理
*/
@ -360,7 +317,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
if (packet is CancellableEvent && packet.isCancelled) return
}
bot.logger.info("Received packet: ${packet.toString().replace("\n", """\n""").replace("\r", "")}")
bot.logger.info("Received: ${packet.toString().replace("\n", """\n""").replace("\r", "")}")
packetFactory?.run {
when (this) {

View File

@ -180,32 +180,32 @@ class Oidb0x88d : ProtoBuf {
@SerialId(12) val groupDefaultPage: Int? = null,
@SerialId(13) val groupInfoSeq: Int? = null,
@SerialId(14) val groupRoamingTime: Int? = null,
@SerialId(15) val ingGroupName: ByteArray? = null,
@SerialId(16) val ingGroupMemo: ByteArray? = null,
@SerialId(17) val ingGroupFingerMemo: ByteArray? = null,
@SerialId(18) val ingGroupClassText: ByteArray? = null,
@SerialId(15) var groupName: String? = null,
@SerialId(16) var groupMemo: String? = null,
@SerialId(17) val ingGroupFingerMemo: String? = null,
@SerialId(18) val ingGroupClassText: String? = null,
@SerialId(19) val groupAllianceCode: List<Int>? = null,
@SerialId(20) val groupExtraAdmNum: Int? = null,
@SerialId(21) val groupUin: Long? = null,
@SerialId(21) var groupUin: Long? = null,
@SerialId(22) val groupCurMsgSeq: Int? = null,
@SerialId(23) val groupLastMsgTime: Int? = null,
@SerialId(24) val ingGroupQuestion: ByteArray? = null,
@SerialId(25) val ingGroupAnswer: ByteArray? = null,
@SerialId(24) val ingGroupQuestion: String? = null,
@SerialId(25) val ingGroupAnswer: String? = null,
@SerialId(26) val groupVisitorMaxNum: Int? = null,
@SerialId(27) val groupVisitorCurNum: Int? = null,
@SerialId(28) val levelNameSeq: Int? = null,
@SerialId(29) val groupAdminMaxNum: Int? = null,
@SerialId(30) val groupAioSkinTimestamp: Int? = null,
@SerialId(31) val groupBoardSkinTimestamp: Int? = null,
@SerialId(32) val ingGroupAioSkinUrl: ByteArray? = null,
@SerialId(33) val ingGroupBoardSkinUrl: ByteArray? = null,
@SerialId(32) val ingGroupAioSkinUrl: String? = null,
@SerialId(33) val ingGroupBoardSkinUrl: String? = null,
@SerialId(34) val groupCoverSkinTimestamp: Int? = null,
@SerialId(35) val ingGroupCoverSkinUrl: ByteArray? = null,
@SerialId(35) val ingGroupCoverSkinUrl: String? = null,
@SerialId(36) val groupGrade: Int? = null,
@SerialId(37) val activeMemberNum: Int? = null,
@SerialId(38) val certificationType: Int? = null,
@SerialId(39) val ingCertificationText: ByteArray? = null,
@SerialId(40) val ingGroupRichFingerMemo: ByteArray? = null,
@SerialId(39) val ingCertificationText: String? = null,
@SerialId(40) val ingGroupRichFingerMemo: String? = null,
@SerialId(41) val tagRecord: List<TagRecord>? = null,
@SerialId(42) val groupGeoInfo: GroupGeoInfo? = null,
@SerialId(43) val headPortraitSeq: Int? = null,
@ -254,7 +254,7 @@ class Oidb0x88d : ProtoBuf {
@SerialId(86) val isAllowConfGroupMemberNick: Int? = null,
@SerialId(87) val isAllowConfGroupMemberAtAll: Int? = null,
@SerialId(88) val isAllowConfGroupMemberModifyGroupName: Int? = null,
@SerialId(89) val ingLongGroupName: ByteArray? = null,
@SerialId(89) val longGroupName: String? = null,
@SerialId(90) val cmduinJoinRealMsgSeq: Int? = null,
@SerialId(91) val isGroupFreeze: Int? = null,
@SerialId(92) val msgLimitFrequency: Int? = null,
@ -265,7 +265,8 @@ class Oidb0x88d : ProtoBuf {
@SerialId(97) val isAllowHlGuildBinary: Int? = null,
@SerialId(98) val cmduinRingtoneId: Int? = null,
@SerialId(99) val groupFlagext4: Int? = null,
@SerialId(100) val groupFreezeReason: Int? = null
@SerialId(100) val groupFreezeReason: Int? = null,
@SerialId(101) var groupCode: Long? = null // mirai 添加
) : ProtoBuf
@Serializable

View File

@ -135,7 +135,7 @@ internal object KnownPacketFactories {
TroopManagement.EditSpecialTitle,
TroopManagement.Mute,
TroopManagement.GroupOperation,
TroopManagement.GetGroupOperationInfo,
TroopManagement.GetGroupInfo,
TroopManagement.EditGroupNametag,
TroopManagement.Kick,
Heartbeat.Alive

View File

@ -14,6 +14,7 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.toByteArray
import kotlinx.serialization.toUtf8Bytes
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot
@ -23,12 +24,27 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.ModifyGroupCardReq
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.stUinInfo
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.utils.daysToSeconds
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
internal inline class GroupInfoImpl(
internal val delegate: Oidb0x88d.GroupInfo
) : MiraiGroupInfo, Packet {
override val uin: Long get() = delegate.groupUin ?: error("cannot find groupUin")
override val owner: Long get() = delegate.groupOwner ?: error("cannot find groupOwner")
override val groupCode: Long get() = Group.calculateGroupCodeByGroupUin(uin)
override val memo: String get() = delegate.groupMemo ?: error("cannot find groupMemo")
override val name: String get() = delegate.groupName ?: delegate.longGroupName ?: error("cannot find groupName")
override val allowMemberInvite get() = delegate.groupFlagExt?.and(0x000000c0) != 0
override val allowAnonymousChat get() = delegate.groupFlagExt?.and(0x40000000) == 0
override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0
override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0
override val muteAll: Boolean get() = delegate.shutupTimestamp != 0
}
internal class TroopManagement {
@ -70,16 +86,7 @@ internal class TroopManagement {
}
internal object GetGroupOperationInfo : OutgoingPacketFactory<GetGroupOperationInfo.Response>("OidbSvc.0x88d_7") {
class Response(
val allowAnonymousChat: Boolean,
val allowMemberInvite: Boolean,
val autoApprove: Boolean,
val confessTalk: Boolean
) : Packet {
override fun toString(): String = "Response(GroupInfo)"
}
internal object GetGroupInfo : OutgoingPacketFactory<GroupInfoImpl>("OidbSvc.0x88d_7") {
operator fun invoke(
client: QQAndroidClient,
groupCode: Long
@ -109,8 +116,13 @@ internal class TroopManagement {
cmduinUinFlag = 0,
createSourceFlag = 0,
noCodeFingerOpenFlag = 0,
ingGroupQuestion = EMPTY_BYTE_ARRAY,
ingGroupAnswer = EMPTY_BYTE_ARRAY
ingGroupQuestion = "",
ingGroupAnswer = "",
groupName = "",
longGroupName = "",
groupMemo = "",
groupUin = 0,
groupOwner = 0
),
groupCode = groupCode
)
@ -121,14 +133,9 @@ internal class TroopManagement {
}
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupInfoImpl {
with(this.readBytes().loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!!) {
return Response(
allowMemberInvite = (this.groupFlagExt?.and(0x000000c0) != 0),
allowAnonymousChat = (this.groupFlagExt?.and(0x40000000) == 0),
autoApprove = (this.groupFlagext3?.and(0x00100000) == 0),
confessTalk = (this.groupFlagext3?.and(0x00002000) == 0)
)
return GroupInfoImpl(this)
}
}
}

View File

@ -133,6 +133,15 @@ internal class MessageSvc {
val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull {
when (it.msgHead.msgType) {
33 -> {
if (it.msgHead.authUin == bot.uin) {
val group = bot.getGroupByUinOrNull(it.msgHead.fromUin)
if (group == null) {
TODO("查询群信息, 添加群")
}
}
TODO("为 group 添加一个 fun Member() 来构造 member")
// bot.getGroupByUin(it.msgHead.fromUin).members.delegate.addLast()
println("GroupUin" + it.msgHead.fromUin + "新群员" + it.msgHead.authUin + " 出现了[" + it.msgHead.authNick + "] 添加刷新")
null
}

View File

@ -20,10 +20,10 @@ import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.MemberImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgInfo
@ -34,7 +34,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OnlinePushTrans
import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readString
@ -96,6 +96,7 @@ internal class OnlinePush {
internal object PbPushTransMsg : IncomingPacketFactory<Packet>("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") {
@UseExperimental(MiraiInternalAPI::class)
@ExperimentalUnsignedTypes
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet {
val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer())
@ -109,17 +110,16 @@ internal class OnlinePush {
if (var4 != 0 && var4 != 1) {
var5 = readUInt().toLong()
}
if (var5 == 0L && this.remaining == 1L) {//管理员变更
val groupUin = content.fromUin
val member = bot.getGroupByUin(groupUin)[target] as MemberImpl
val old = member.permission
return if (this.readByte().toInt() == 1) {
member.permission = MemberPermission.ADMINISTRATOR
MemberPermissionChangeEvent(member, old, MemberPermission.ADMINISTRATOR)
val group = bot.getGroupByUin(content.fromUin) as GroupImpl
if (var5 == 0L && this.remaining == 1L) {//管理员变更
val newPermission = if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR else MemberPermission.MEMBER
return if (target == bot.uin) {
BotGroupPermissionChangeEvent(group, group.botPermission.also { group.botPermission = newPermission }, newPermission)
} else {
member.permission = MemberPermission.MEMBER
MemberPermissionChangeEvent(member, old, MemberPermission.ADMINISTRATOR)
val member = group[target] as MemberImpl
MemberPermissionChangeEvent(member, member.permission.also { member.permission = newPermission }, newPermission)
}
}
}
@ -129,10 +129,12 @@ internal class OnlinePush {
val target = readUInt().toLong()
val groupUin = content.fromUin
bot.getGroupByUin(groupUin).let {
val member = it[target] as MemberImpl
bot.getGroupByUin(groupUin).let { group ->
val member = group[target] as MemberImpl
this.discardExact(1)
return MemberLeaveEvent.Kick(member, it.members[readUInt().toLong()])
return MemberLeaveEvent.Kick(member, group.members[readUInt().toLong()].also {
group.members.delegate.remove(it)
})
}
}
}
@ -158,6 +160,7 @@ internal class OnlinePush {
when {
msgInfo.shMsgType.toInt() == 732 -> {
val group = bot.getGroup(this.readUInt().toLong())
group as GroupImpl
when (val internalType = this.readShort().toInt()) {
3073 -> { // mute
@ -169,9 +172,19 @@ internal class OnlinePush {
return if (target == 0L) {
if (time == 0) {
GroupMuteAllEvent(origin = true, new = false, operator = operator, group = group)
GroupMuteAllEvent(
origin = group.muteAll.also { group._muteAll = false },
new = false,
operator = operator,
group = group
)
} else {
GroupMuteAllEvent(origin = false, new = true, operator = operator, group = group)
GroupMuteAllEvent(
origin = group.muteAll.also { group._muteAll = true },
new = true,
operator = operator,
group = group
)
}
} else {
val member = group[target]
@ -184,9 +197,10 @@ internal class OnlinePush {
}
3585 -> { // 匿名
val operator = group[this.readUInt().toLong()]
val switch = this.readInt() == 0
return GroupAllowAnonymousChatEvent(
origin = group.anonymousChat,
new = this.readInt() == 0,
origin = group.anonymousChat.also { group._anonymousChat = switch },
new = switch,
operator = operator,
group = group
)
@ -196,23 +210,30 @@ internal class OnlinePush {
val message = this.readString(this.readByte().toInt())
println(dataBytes.toUHexString())
return if (dataBytes[0].toInt() != 59) {
return GroupNameChangeEvent(origin = group.name, new = message, group = group)
if (dataBytes[0].toInt() != 59) {
return GroupNameChangeEvent(
origin = group.name.also { group._name = message },
new = message,
group = group,
isByBot = false
)
} else {
//println(message + ":" + dataBytes.toUHexString())
when (message) {
"管理员已关闭群聊坦白说" -> {
return GroupAllowConfessTalkEvent(
origin = group.confessTalk,
origin = group.confessTalk.also { group._confessTalk = false },
new = false,
group = group
group = group,
isByBot = false
)
}
"管理员已开启群聊坦白说" -> {
return GroupAllowConfessTalkEvent(
origin = group.confessTalk,
new = false,
group = group
origin = group.confessTalk.also { group._confessTalk = true },
new = true,
group = group,
isByBot = false
)
}
else -> {
@ -222,18 +243,19 @@ internal class OnlinePush {
}
}
}
4352 -> {
println(msgInfo.contentToString())
println(msgInfo.vMsg.toUHexString())
}
// 4352 -> {
// println(msgInfo.contentToString())
// println(msgInfo.vMsg.toUHexString())
// }
else -> {
println("unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " ")
}
}
}
msgInfo.shMsgType.toInt() == 528 -> {
val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
println(content.contentToString())
println("unknown shtype ${msgInfo.shMsgType.toInt()}")
// val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
// println(content.contentToString())
}
else -> {
println("unknown shtype ${msgInfo.shMsgType.toInt()}")

View File

@ -72,7 +72,7 @@ internal class FriendList {
val members: List<StTroopMemberInfo>,
val nextUin: Long
) : Packet {
override fun toString(): String = "Friendlist.GetTroopMemberList.Response"
override fun toString(): String = "FriendList.GetTroopMemberList.Response"
}
}

View File

@ -261,6 +261,12 @@ fun String.generateProtoBufDataClass(): GeneratedClass {
@Suppress("NAME_SHADOWING")
var name = _name
when {
name.startsWith("string") -> {
name = name.substringAfter("string").takeIf { it.isNotBlank() }?.adjustName() ?: "string"
if (defaultValue == "EMPTY_BYTE_ARRAY")
defaultValue = "\"\""
}
name.startsWith("str") -> {
name = name.substringAfter("str").takeIf { it.isNotBlank() }?.adjustName() ?: "str"
if (defaultValue == "EMPTY_BYTE_ARRAY")

View File

@ -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<Long>
/**
* 查询群资料. 获得的仅为当前时刻的资料.
* 请优先使用 [getGroup] 然后查看群资料.
*/
abstract suspend fun queryGroupInfo(id: Long): GroupInfo
/**
* 查询群成员列表.
* 请优先使用 [getGroup], [Group.members] 查看群成员.
*
* 这个函数很慢. 请不要频繁使用.
*/
abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo>
// TODO 目前还不能构造群对象. 这将在以后支持
// endregion

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -74,6 +74,10 @@ interface Listener<in E : Event> : CompletableJob {
* ```kotlin
* bot.subscribe<Subscribe> { /* 一些处理 */ }
* ```
*
* @see subscribeMessages 监听消息 DSL
* @see subscribeGroupMessages 监听群消息
* @see subscribeFriendMessages 监听好友消息
*/
inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
E::class.subscribeInternal(Handler { it.handler(it) })

View File

@ -13,6 +13,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.AbstractCancellableEvent
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.CancellableEvent
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
@ -120,16 +121,19 @@ data class BotGroupPermissionChangeEvent(
override val group: Group,
val origin: MemberPermission,
val new: MemberPermission
) : BotPassiveEvent, GroupEvent
) : BotPassiveEvent, GroupEvent, Packet
// region 群设置
/**
* 群设置改变. 此事件广播前修改就已经完成.
*/
interface GroupSettingChangeEvent<T> : GroupEvent, BotPassiveEvent {
interface GroupSettingChangeEvent<T> : GroupEvent, BotPassiveEvent, BroadcastControllable {
val origin: T
val new: T
override val shouldBroadcast: Boolean
get() = origin != new
}
/**
@ -138,7 +142,8 @@ interface GroupSettingChangeEvent<T> : GroupEvent, BotPassiveEvent {
data class GroupNameChangeEvent(
override val origin: String,
override val new: String,
override val group: Group
override val group: Group,
val isByBot: Boolean
) : GroupSettingChangeEvent<String>, Packet
/**
@ -187,7 +192,8 @@ data class GroupAllowAnonymousChatEvent(
data class GroupAllowConfessTalkEvent(
override val origin: Boolean,
override val new: Boolean,
override val group: Group
override val group: Group,
val isByBot: Boolean
) : GroupSettingChangeEvent<Boolean>, Packet
/**
@ -299,7 +305,7 @@ data class MemberPermissionChangeEvent(
// region 禁言
/**
* 群成员被禁言事件. 操作人和被禁言的成员都不可能是机器人本人
* 群成员被禁言事件. 被禁言的成员都不可能是机器人本人
*/
data class MemberMuteEvent(
override val member: Member,
@ -311,7 +317,7 @@ data class MemberMuteEvent(
) : GroupMemberEvent, Packet
/**
* 群成员被取消禁言事件. 操作人和被禁言的成员都不可能是机器人本人
* 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人
*/
data class MemberUnmuteEvent(
override val member: Member,

View File

@ -12,7 +12,6 @@
package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.groupCardOrNick
import net.mamoe.mirai.utils.MiraiInternalAPI
@ -21,7 +20,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
*/
class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message {
@UseExperimental(MiraiInternalAPI::class)
constructor(member: Member) : this(member.id, "@${member.groupCardOrNick}")
constructor(member: Member) : this(member.id, "@${member.nick}")
override fun toString(): String = display

View File

@ -68,6 +68,20 @@ fun <E> LockFreeLinkedList<E>.asSequence(): Sequence<E> {
}
}
/**
* 构建链表结构然后转为 [LockFreeLinkedList]
*/
fun <E> Iterable<E>.toLockFreeLinkedList(): LockFreeLinkedList<E> {
return LockFreeLinkedList<E>().apply { addAll(this@toLockFreeLinkedList) }
}
/**
* 构建链表结构然后转为 [LockFreeLinkedList]
*/
fun <E> Sequence<E>.toLockFreeLinkedList(): LockFreeLinkedList<E> {
return LockFreeLinkedList<E>().apply { addAll(this@toLockFreeLinkedList) }
}
/**
* Implementation of lock-free LinkedList.
*
@ -111,8 +125,10 @@ open class LockFreeLinkedList<E> {
}
open fun addLast(element: E) {
val node = element.asNode(tail)
addLastNode(element.asNode(tail))
}
private fun addLastNode(node: Node<E>) {
while (true) {
val tail = head.iterateBeforeFirst { it === tail } // find the last node.
if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node
@ -121,6 +137,46 @@ open class LockFreeLinkedList<E> {
}
}
/**
* 先把元素建立好链表, 再加入到 list.
*/
@Suppress("DuplicatedCode")
open fun addAll(iterable: Iterable<E>) {
var firstNode: Node<E>? = null
var currentNode: Node<E>? = null
iterable.forEach {
val nextNode = it.asNode(tail)
if (firstNode == null) {
firstNode = nextNode
}
currentNode?.nextNode = nextNode
currentNode = nextNode
}
firstNode?.let { addLastNode(it) }
}
/**
* 先把元素建立好链表, 再加入到 list.
*/
@Suppress("DuplicatedCode")
open fun addAll(iterable: Sequence<E>) {
var firstNode: Node<E>? = null
var currentNode: Node<E>? = null
iterable.forEach {
val nextNode = it.asNode(tail)
if (firstNode == null) {
firstNode = nextNode
}
currentNode?.nextNode = nextNode
currentNode = nextNode
}
firstNode?.let { addLastNode(it) }
}
open operator fun plusAssign(element: E) = this.addLast(element)
/**
@ -243,8 +299,6 @@ open class LockFreeLinkedList<E> {
}
}
open fun addAll(elements: Collection<E>) = elements.forEach { addLast(it) }
@Suppress("unused")
open fun clear() {
val first = head.nextNode

View File

@ -17,7 +17,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-io:$coroutinesIoVersion")
implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.62'
implementation 'org.jsoup:jsoup:1.12.1'
api 'org.jsoup:jsoup:1.12.1'
}
run{