Merge remote-tracking branch 'origin/master'

This commit is contained in:
jiahua.liu 2020-02-12 23:14:30 +08:00
commit ab3d524dea
10 changed files with 166 additions and 40 deletions

View File

@ -325,6 +325,15 @@ internal class GroupImpl(
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(MiraiExperimentalAPI::class)
override lateinit var botPermission: MemberPermission override lateinit var botPermission: MemberPermission
var _botMuteRemaining: Int = groupInfo.botMuteRemaining
override val botMuteRemaining: Int =
if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) {
0
} else {
_botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
}
override val members: ContactList<Member> = ContactList(members.mapNotNull { override val members: ContactList<Member> = ContactList(members.mapNotNull {
if (it.uin == bot.uin) { if (it.uin == bot.uin) {
botPermission = it.permission botPermission = it.permission
@ -487,6 +496,7 @@ internal class GroupImpl(
} }
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" }
val event = GroupMessageSendEvent(this, message).broadcast() val event = GroupMessageSendEvent(this, message).broadcast()
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by FriendMessageSendEvent")

View File

@ -44,6 +44,7 @@ internal inline class GroupInfoImpl(
override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0 override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0
override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0 override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0
override val muteAll: Boolean get() = delegate.shutupTimestamp != 0 override val muteAll: Boolean get() = delegate.shutupTimestamp != 0
override val botMuteRemaining: Int get() = delegate.shutupTimestampMe ?: 0
} }
internal class TroopManagement { internal class TroopManagement {

View File

@ -11,12 +11,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
@ -32,6 +38,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -94,13 +103,13 @@ internal class MessageSvc {
} }
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
internal class GetMsgSuccess(delegate: List<FriendMessage>) : Response(MsgSvc.SyncFlag.STOP, delegate) internal class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate)
/** /**
* 不要直接 expect 这个 class. 它可能 * 不要直接 expect 这个 class. 它可能
*/ */
@MiraiInternalAPI @MiraiInternalAPI
open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<FriendMessage>) : MultiPacket<FriendMessage>(delegate), open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List<Packet>) : MultiPacket<Packet>(delegate),
BroadcastControllable { BroadcastControllable {
override val shouldBroadcast: Boolean override val shouldBroadcast: Boolean
get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP
@ -112,7 +121,7 @@ internal class MessageSvc {
object EmptyResponse : Response(MsgSvc.SyncFlag.STOP, emptyList()) object EmptyResponse : Response(MsgSvc.SyncFlag.STOP, emptyList())
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
// 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00
val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer()) val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer())
@ -130,40 +139,86 @@ internal class MessageSvc {
return EmptyResponse return EmptyResponse
} }
val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull { val messages = resp.uinPairMsgs.asSequence()
when (it.msgHead.msgType) { .filterNot { it.msg == null }
33 -> { .flatMap { it.msg!!.asSequence() }
if (it.msgHead.authUin == bot.uin) { .toList() // so as to inline
val group = bot.getGroupByUinOrNull(it.msgHead.fromUin) .mapNotNull<MsgComm.Msg, Packet> { msg ->
if (group == null) { when (msg.msgHead.msgType) {
TODO("查询群信息, 添加群") 33 -> {
val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin)
if (msg.msgHead.authUin == bot.uin) {
if (group != null) {
error("group is not null while bot is invited to the group")
}
// 新群
val troopNum = bot.network.run {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.first { it.groupUin == msg.msgHead.fromUin }
val newGroup = GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = Group.calculateGroupCodeByGroupUin(msg.msgHead.fromUin),
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)
)
bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup)
} else {
checkNotNull(group) { "group is null while a member is joining to" }
if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null
} else {
return@mapNotNull MemberJoinEvent(group.Member(object : MemberInfo {
override val nameCard: String get() = ""
override val permission: MemberPermission get() = MemberPermission.MEMBER
override val specialTitle: String get() = ""
override val uin: Long get() = msg.msgHead.authUin
override val nick: String get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromNick
}).also { group.members.delegate.addLast(it) })
}
} }
} }
166 -> {
TODO("为 group 添加一个 fun Member() 来构造 member") return@mapNotNull when {
// bot.getGroupByUin(it.msgHead.fromUin).members.delegate.addLast() msg.msgHead.fromUin == bot.uin -> null
println("GroupUin" + it.msgHead.fromUin + "新群员" + it.msgHead.authUin + " 出现了[" + it.msgHead.authNick + "] 添加刷新") !bot.firstLoginSucceed -> null
null else -> FriendMessage(
} bot,
166 -> { bot.getFriend(msg.msgHead.fromUin),
when { msg.toMessageChain()
it.msgHead.fromUin == bot.uin -> null )
!bot.firstLoginSucceed -> null }
else -> FriendMessage(
bot,
bot.getFriend(it.msgHead.fromUin),
it.toMessageChain()
)
} }
else -> return@mapNotNull null
} }
else -> null
} }
}.toMutableList()
if (resp.syncFlag == MsgSvc.SyncFlag.STOP) { if (resp.syncFlag == MsgSvc.SyncFlag.STOP) {
messages.ifEmpty { messages.ifEmpty {
return EmptyResponse return EmptyResponse
} }
return GetMsgSuccess(mutableListOf(messages.last())) return GetMsgSuccess(listOf(messages.last()))
} }
return Response(resp.syncFlag, messages) return Response(resp.syncFlag, messages)
} }
@ -201,7 +256,8 @@ internal class MessageSvc {
} }
data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() { data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() {
override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)" override fun toString(): String =
"MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)"
} }
} }

View File

@ -187,6 +187,9 @@ internal class OnlinePush {
) )
} }
} else { } else {
if (target == bot.uin) {
}
val member = group[target] val member = group[target]
if (time == 0) { if (time == 0) {
MemberUnmuteEvent(operator = operator, member = member) MemberUnmuteEvent(operator = operator, member = member)

View File

@ -94,3 +94,7 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
} }
} }
/**
* 时间戳
*/
actual val currentTimeMillis: Long get() = System.currentTimeMillis()

View File

@ -14,6 +14,7 @@ package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.BeforeImageUploadEvent
import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.ImageUploadEvent
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
@ -47,16 +48,20 @@ interface Contact : CoroutineScope {
* *
* @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable
*
* @throws EventCancelledException 当发送消息事件被取消
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
*/ */
suspend fun sendMessage(message: MessageChain) suspend fun sendMessage(message: MessageChain)
/** /**
* 上传一个图片以备发送. * 上传一个图片以备发送.
* TODO: 群图片与好友图片之间是否通用还不确定. * TODO 群图片与好友图片在服务器上是通用的, mirai 目前不通用.
* TODO: 好友之间图片是否通用还不确定.
* *
* @see BeforeImageUploadEvent 图片发送前事件, cancellable * @see BeforeImageUploadEvent 图片发送前事件, cancellable
* @see ImageUploadEvent 图片发送完成事件 * @see ImageUploadEvent 图片发送完成事件
*
* @throws EventCancelledException 当发送消息事件被取消
*/ */
suspend fun uploadImage(image: ExternalImage): Image suspend fun uploadImage(image: ExternalImage): Image

View File

@ -24,18 +24,20 @@ interface Group : Contact, CoroutineScope {
/** /**
* 群名称. * 群名称.
* *
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] * 在修改时将会异步上传至服务器.
* 频繁修改可能会被服务器拒绝. * 频繁修改可能会被服务器拒绝.
* *
* @see MemberPermissionChangeEvent * @see MemberPermissionChangeEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var name: String var name: String
/** /**
* 入群公告, 没有时为空字符串. * 入群公告, 没有时为空字符串.
* *
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] * 在修改时将会异步上传至服务器.
* *
* @see GroupEntranceAnnouncementChangeEvent * @see GroupEntranceAnnouncementChangeEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var entranceAnnouncement: String var entranceAnnouncement: String
/** /**
@ -49,17 +51,19 @@ interface Group : Contact, CoroutineScope {
/** /**
* 坦白说状态. `true` 为允许. * 坦白说状态. `true` 为允许.
* *
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] * 在修改时将会异步上传至服务器.
*
* @see GroupAllowConfessTalkEvent * @see GroupAllowConfessTalkEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var confessTalk: Boolean var confessTalk: Boolean
/** /**
* 允许群员邀请好友入群的状态. `true` 为允许 * 允许群员邀请好友入群的状态. `true` 为允许
* *
* 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] * 在修改时将会异步上传至服务器.
* *
* @see GroupAllowMemberInviteEvent * @see GroupAllowMemberInviteEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var allowMemberInvite: Boolean var allowMemberInvite: Boolean
/** /**
@ -77,10 +81,17 @@ interface Group : Contact, CoroutineScope {
override val id: Long override val id: Long
/** /**
* 群主 (同步事件更新) * 群主
*/ */
val owner: Member val owner: Member
/**
* 机器人被禁言还剩余多少秒
*
* @see BotMuteEvent
* @see isBotMuted
*/
val botMuteRemaining: Int
/** /**
* 机器人在这个群里的权限 * 机器人在这个群里的权限
@ -124,7 +135,7 @@ interface Group : Contact, CoroutineScope {
* 非特殊情况请不要使用这个函数. 优先使用 [get]. * 非特殊情况请不要使用这个函数. 优先使用 [get].
*/ */
@MiraiExperimentalAPI("dangerous") @MiraiExperimentalAPI("dangerous")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName")
@JvmName("newMember") @JvmName("newMember")
fun Member(memberInfo: MemberInfo): Member fun Member(memberInfo: MemberInfo): Member
@ -168,4 +179,9 @@ interface Group : Contact, CoroutineScope {
@MiraiExperimentalAPI @MiraiExperimentalAPI
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
} }
/**
* 返回机器人是否正在被禁言
*/
val Group.isBotMuted: Boolean get() = this.botMuteRemaining == 0

View File

@ -57,4 +57,9 @@ interface GroupInfo {
* 全员禁言 * 全员禁言
*/ */
val muteAll: Boolean val muteAll: Boolean
/**
* 机器人被禁言还剩时间, .
*/
val botMuteRemaining: Int
} }

View File

@ -18,6 +18,7 @@ import net.mamoe.mirai.event.CancellableEvent
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@Suppress("unused") @Suppress("unused")
@ -123,6 +124,26 @@ data class BotGroupPermissionChangeEvent(
val new: MemberPermission val new: MemberPermission
) : BotPassiveEvent, GroupEvent, Packet ) : BotPassiveEvent, GroupEvent, Packet
/**
* Bot 被禁言
*/
data class BotMuteEvent(
val durationSeconds: Int,
override val group: Group,
/**
* 操作人. null 则为机器人操作
*/
val operator: Member?
) : GroupEvent, Packet, BotPassiveEvent
/**
* Bot 加入了一个新群
*/
@MiraiExperimentalAPI
data class BotJoinGroupEvent(
override val group: Group
) : BotPassiveEvent, GroupEvent, Packet
// region 群设置 // region 群设置
/** /**
@ -219,7 +240,7 @@ data class GroupAllowMemberInviteEvent(
/** /**
* 成员加入群的事件 * 成员加入群的事件
*/ */
data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet
/** /**
* 成员离开群的事件 * 成员离开群的事件

View File

@ -74,3 +74,8 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
return output.toByteArray() return output.toByteArray()
} }
} }
/**
* 时间戳
*/
actual val currentTimeMillis: Long get() = System.currentTimeMillis()