Merge remote-tracking branch 'origin'

This commit is contained in:
ryoii 2020-02-20 23:21:44 +08:00
commit 52b848eb66
13 changed files with 172 additions and 58 deletions

View File

@ -2,6 +2,21 @@
开发版本. 频繁更新, 不保证高稳定性
## `0.18.0` 2020/2/20
### mirai-core
- 添加 `MessageSource.time`
- 添加事件监听时额外的 `coroutineContext`
- 为一些带有 `operator` 的事件添加 `.isByBot` 的属性扩展
- 优化事件广播逻辑, 修复可能无法触发监听的问题
- 为所有 `Contact` 添加 `toString()` (#80)
### mirai-core-qqandroid
- 支持成员禁言状态和时间查询 `Member.muteTimeRemaining`
- 修复 `At``display` (#73), 同时修复 `QuoteReply` 无法显示问题 (#54).
- 广播 `BotReloginEvent` (#78)
- 支持机器人自身禁言时间的更新和查询 (#82)
## `0.17.0` 2020/2/20
### mirai-core

View File

@ -1,7 +1,7 @@
# style guide
kotlin.code.style=official
# config
mirai_version=0.17.0
mirai_version=0.18.0
mirai_japt_version=1.0.1
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true

View File

@ -482,7 +482,9 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
val width = terminal.terminalSize.columns - 6
var x = string
while (true) {
if (x == "") break
if (x == "") {
break
}
val toWrite = if (x.actualLength() > width) {
val index = x.getSubStringIndexByActualLength(width)
x.substring(0, index).also {

View File

@ -305,6 +305,7 @@ object MiraiConsole {
if (!CommandManager.runCommand(fullCommand)) {
logger("未知指令 $fullCommand")
}
}
}
}

View File

@ -162,16 +162,11 @@ internal class QQImpl(
TODO("not implemented")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is QQ && other.id == this.id
}
override fun hashCode(): Int = super.hashCode()
override fun toString(): String = "QQ($id)"
}
@Suppress("MemberVisibilityCanBePrivate")
@Suppress("MemberVisibilityCanBePrivate", "DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
internal class MemberImpl(
qq: QQImpl,
group: GroupImpl,
@ -182,9 +177,21 @@ internal class MemberImpl(
val qq: QQImpl by qq.unsafeWeakRef()
override var permission: MemberPermission = memberInfo.permission
@Suppress("PropertyName")
internal var _nameCard: String = memberInfo.nameCard
@Suppress("PropertyName")
internal var _specialTitle: String = memberInfo.specialTitle
@Suppress("PropertyName")
var _muteTimestamp: Int = memberInfo.muteTimestamp
override val muteTimeRemaining: Int =
if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) {
0
} else {
_muteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
}
override var nameCard: String
get() = _nameCard
set(newValue) {
@ -220,7 +227,7 @@ internal class MemberImpl(
newValue
).sendWithoutExpect()
}
MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl).broadcast()
MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast()
}
}
}
@ -279,12 +286,9 @@ internal class MemberImpl(
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is Member && other.id == this.id
override fun toString(): String {
return "Member($id)"
}
override fun hashCode(): Int = super.hashCode()
}
internal class MemberInfoImpl(
@ -301,6 +305,7 @@ internal class MemberInfoImpl(
else -> MemberPermission.MEMBER
}
override val specialTitle: String get() = jceInfo.sSpecialTitle ?: ""
override val muteTimestamp: Int get() = jceInfo.dwShutupTimestap?.toInt() ?: 0
}
/**
@ -323,13 +328,13 @@ internal class GroupImpl(
@UseExperimental(MiraiExperimentalAPI::class)
override lateinit var botPermission: MemberPermission
var _botMuteRemaining: Int = groupInfo.botMuteRemaining
var _botMuteTimestamp: Int = groupInfo.botMuteRemaining
override val botMuteRemaining: Int =
if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) {
if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) {
0
} else {
_botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
_botMuteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
}
override val members: ContactList<Member> = ContactList(members.mapNotNull {
@ -600,10 +605,7 @@ internal class GroupImpl(
image.input.close()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is Group && other.id == this.id
override fun toString(): String {
return "Group($id)"
}
override fun hashCode(): Int = super.hashCode()
}

View File

@ -194,6 +194,7 @@ internal class MessageSvc {
override val nameCard: String get() = ""
override val permission: MemberPermission get() = MemberPermission.MEMBER
override val specialTitle: String get() = ""
override val muteTimestamp: Int get() = 0
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) })

View File

@ -142,9 +142,10 @@ internal class OnlinePush {
@UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet {
val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req")
val packets = reqPushMsg.vMsgInfos.mapNotNull { msgInfo: MsgInfo ->
msgInfo.vMsg!!.read {
@Suppress("USELESS_CAST") // 不要信任 kotlin 类型推断
val packets: List<Packet> = reqPushMsg.vMsgInfos.mapNotNull { msgInfo: MsgInfo ->
msgInfo.vMsg!!.read {
when {
msgInfo.shMsgType.toInt() == 732 -> {
val group = bot.getGroup(this.readUInt().toLong())
@ -162,41 +163,54 @@ internal class OnlinePush {
val target = this.readUInt().toLong()
val time = this.readInt()
return@mapNotNull if (target == 0L) {
if (target == 0L) {
if (time == 0) {
GroupMuteAllEvent(
return@mapNotNull GroupMuteAllEvent(
origin = group.isMuteAll.also { group._muteAll = false },
new = false,
operator = operator,
group = group
)
) as Packet
} else {
GroupMuteAllEvent(
return@mapNotNull GroupMuteAllEvent(
origin = group.isMuteAll.also { group._muteAll = true },
new = true,
operator = operator,
group = group
)
) as Packet
}
} else {
if (target == bot.uin) {
@Suppress("IMPLICIT_CAST_TO_ANY") // false positive
return@mapNotNull if (time == 0) {
BotUnmuteEvent(operator)
} else
BotMuteEvent(durationSeconds = time, operator = operator)
if (group._botMuteTimestamp != time) {
if (time == 0) {
group._botMuteTimestamp = 0
return@mapNotNull BotUnmuteEvent(operator) as Packet
} else {
group._botMuteTimestamp = time
return@mapNotNull BotMuteEvent(durationSeconds = time, operator = operator) as Packet
}
} else {
return@mapNotNull null
}
} else {
val member = group[target]
@Suppress("IMPLICIT_CAST_TO_ANY") // false positive
return@mapNotNull if (time == 0) {
MemberUnmuteEvent(operator = operator, member = member)
member as MemberImpl
if (member._muteTimestamp != time) {
if (time == 0) {
member._muteTimestamp = 0
return@mapNotNull MemberUnmuteEvent(member, operator) as Packet
} else {
member._muteTimestamp = time
return@mapNotNull MemberMuteEvent(member, time, operator) as Packet
}
} else {
MemberMuteEvent(operator = operator, member = member, durationSeconds = time)
return@mapNotNull null
}
}
}
}
3585 -> { // 匿名
3585 -> {
// 匿名
val operator = group[this.readUInt().toLong()]
val switch = this.readInt() == 0
return@mapNotNull GroupAllowAnonymousChatEvent(
@ -239,7 +253,7 @@ internal class OnlinePush {
}
else -> {
bot.network.logger.debug { "Unknown server messages $message" }
return@mapNotNull NoPacket
return@mapNotNull null
}
}
}
@ -250,6 +264,7 @@ internal class OnlinePush {
// }
else -> {
bot.network.logger.debug { "unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " " }
return@mapNotNull null
}
}
}
@ -257,19 +272,18 @@ internal class OnlinePush {
bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
// val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer())
// println(content.contentToString())
return@mapNotNull null
}
else -> {
bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" }
return@mapNotNull null
}
}
}
return@mapNotNull null
}
return MultiPacket(packets)
}
override suspend fun QQAndroidBot.handle(packet: Packet, sequenceId: Int): OutgoingPacket? {
return buildResponseUniPacket(client, sequenceId = sequenceId) {

View File

@ -74,6 +74,16 @@ interface Contact : CoroutineScope {
* [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人.
*/
override fun equals(other: Any?): Boolean
/**
* @return `bot.hashCode() * 31 + id.hashCode()`
*/
override fun hashCode(): Int
/**
* @return "QQ($id)" or "Group($id)" or "Member($id)"
*/
override fun toString(): String
}
suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain())

View File

@ -12,6 +12,7 @@
package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@ -89,19 +90,19 @@ interface Group : Contact, CoroutineScope {
/**
* 机器人被禁言还剩余多少秒
*
* @see BotMuteEvent
* @see isBotMuted
* @see BotMuteEvent 机器人被禁言事件
* @see isBotMuted 判断机器人是否正在被禁言
*/
val botMuteRemaining: Int
/**
* 机器人在这个群里的权限
*
* **MiraiExperimentalAPI**: 在未来可能会被修改
* @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限
* @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator]
*
* @see BotGroupPermissionChangeEvent
* @see BotGroupPermissionChangeEvent 机器人群员修改
*/
@MiraiExperimentalAPI
val botPermission: MemberPermission
@ -129,6 +130,7 @@ interface Group : Contact, CoroutineScope {
/**
* 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
*/
@MiraiExperimentalAPI("还未支持")
suspend fun quit(): Boolean
/**

View File

@ -1,7 +1,7 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 在以下链接找到该许可证.
* 此源代码的使用受 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
@ -29,33 +29,50 @@ interface Member : QQ, Contact {
/**
* 成员的权限, 动态更新.
*
* @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发.
*/
val permission: MemberPermission
/**
* 群名片. 可能为空. 修改时将会触发事件
* 群名片. 可能为空.
*
* 管理员和群主都可修改任何人包括群主的群名片.
*
* 在修改时将会异步上传至服务器.
*
* @see [nameCardOrNick] 获取非空群名片或昵称
*
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
*/
var nameCard: String
/**
* 群头衔
* 群头衔.
*
* 仅群主可以修改群头衔.
*
* 在修改时将会异步上传至服务器.
*
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
*/
var specialTitle: String
/**
* 禁言
* 被禁言剩余时长. 单位为秒.
*
* @see isMuted 判断改成员是否处于禁言状态
* @see mute 设置禁言
* @see unmute 取消禁言
*/
val muteTimeRemaining: Int
/**
* 禁言.
*
* 管理员可禁言成员, 群主可禁言管理员和群员.
*
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
* @return 机器人无权限时返回 `false`
@ -72,6 +89,8 @@ interface Member : QQ, Contact {
/**
* 解除禁言.
*
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
*
* @see MemberUnmuteEvent 成员被取消禁言事件.
* @throws PermissionDeniedException 无权限修改时
*/
@ -80,6 +99,8 @@ interface Member : QQ, Contact {
/**
* 踢出该成员.
*
* 管理员可踢出成员, 群主可踢出管理员和群员.
*
* @see MemberLeaveEvent.Kick 成员被踢出事件.
* @throws PermissionDeniedException 无权限修改时
*/
@ -98,6 +119,13 @@ interface Member : QQ, Contact {
*/
val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick
/**
* 判断改成员是否处于禁言状态.
*/
fun Member.isMuted(): Boolean {
return muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt()
}
@ExperimentalTime
suspend inline fun Member.mute(duration: Duration) {
require(duration.inDays <= 30) { "duration must be at most 1 month" }

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@ -68,7 +69,6 @@ inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator()
inline fun Member.isOperator(): Boolean = this.permission.isOperator()
/**
* 权限不足
*/
@ -77,6 +77,11 @@ expect class PermissionDeniedException : IllegalStateException {
constructor(message: String?)
}
/**
* 要求 [Bot] 在这个群里的权限为 [required], 否则抛出异常 [PermissionDeniedException]
*
* @throws PermissionDeniedException
*/
@UseExperimental(MiraiExperimentalAPI::class)
inline fun Group.checkBotPermission(
required: MemberPermission,
@ -89,6 +94,11 @@ inline fun Group.checkBotPermission(
}
}
/**
* 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator], 否则抛出异常 [PermissionDeniedException]
*
* @throws PermissionDeniedException
*/
@UseExperimental(MiraiExperimentalAPI::class)
inline fun Group.checkBotPermissionOperator(
lazyMessage: () -> String = {

View File

@ -17,4 +17,6 @@ interface MemberInfo : FriendInfo {
val permission: MemberPermission
val specialTitle: String
val muteTimestamp: Int
}

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
@ -194,7 +196,7 @@ data class GroupNameChangeEvent(
override val origin: String,
override val new: String,
override val group: Group,
val isByBot: Boolean
val isByBot: Boolean // 无法获取 operator
) : GroupSettingChangeEvent<String>, Packet
/**
@ -210,6 +212,8 @@ data class GroupEntranceAnnouncementChangeEvent(
val operator: Member?
) : GroupSettingChangeEvent<String>, Packet
val GroupEntranceAnnouncementChangeEvent.isByBot: Boolean get() = operator != null
/**
* "全员禁言" 功能状态改变. 此事件广播前修改就已经完成.
@ -224,6 +228,8 @@ data class GroupMuteAllEvent(
val operator: Member?
) : GroupSettingChangeEvent<Boolean>, Packet
val GroupMuteAllEvent.isByBot: Boolean get() = operator != null
/**
* "匿名聊天" 功能状态改变. 此事件广播前修改就已经完成.
*/
@ -237,6 +243,8 @@ data class GroupAllowAnonymousChatEvent(
val operator: Member?
) : GroupSettingChangeEvent<Boolean>, Packet
val GroupAllowAnonymousChatEvent.isByBot: Boolean get() = operator != null
/**
* "坦白说" 功能状态改变. 此事件广播前修改就已经完成.
*/
@ -260,6 +268,8 @@ data class GroupAllowMemberInviteEvent(
val operator: Member?
) : GroupSettingChangeEvent<Boolean>, Packet
val GroupAllowMemberInviteEvent.isByBot: Boolean get() = operator != null
// endregion
@ -293,6 +303,8 @@ sealed class MemberLeaveEvent : GroupMemberEvent {
data class Quit(override val member: Member) : MemberLeaveEvent()
}
val MemberLeaveEvent.Kick.isByBot: Boolean get() = operator != null
// endregion
// region 名片和头衔
@ -319,6 +331,8 @@ data class MemberCardChangeEvent(
val operator: Member?
) : GroupMemberEvent
val MemberCardChangeEvent.isByBot: Boolean get() = operator != null
/**
* 群头衔改动. 一定为群主操作
*/
@ -333,9 +347,18 @@ data class MemberSpecialTitleChangeEvent(
*/
val new: String,
override val member: Member
override val member: Member,
/**
* 操作人.
* 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改.
* null 时则是机器人操作.
*/
val operator: Member?
) : GroupMemberEvent
val MemberSpecialTitleChangeEvent.isByBot: Boolean get() = operator != null
// endregion
@ -367,6 +390,8 @@ data class MemberMuteEvent(
val operator: Member?
) : GroupMemberEvent, Packet
val MemberMuteEvent.isByBot: Boolean get() = operator != null
/**
* 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人
*/
@ -378,6 +403,8 @@ data class MemberUnmuteEvent(
val operator: Member?
) : GroupMemberEvent, Packet
val MemberUnmuteEvent.isByBot: Boolean get() = operator != null
// endregion
// endregion