diff --git a/CHANGELOG.md b/CHANGELOG.md index 738c3690e..a6c905bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ 开发版本. 频繁更新, 不保证高稳定性 +## `0.15.1` Unreleased + +### mirai-core +- 统一异常处理: 所有群成员相关操作无权限时均抛出异常而不返回 `false`. + +### mirai-core-qqandroid +- 初始化未完成时缓存接收的所有事件包 (#46) + ## `0.15.0` 2020/2/14 ### mirai-core diff --git a/gradle.properties b/gradle.properties index 98a69c925..eb4f13c0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,6 +2,7 @@ kotlin.code.style=official # config mirai_version=0.15.0 +mirai_japt_version=1.0.0 kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true # kotlin diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt index 2f6113472..23c0f538b 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt @@ -27,11 +27,6 @@ object NotVerifiedSessionException : IllegalAccessException("Session未激活") */ object NoSuchBotException: IllegalAccessException("指定Bot不存在") -/** - * 指定Bot不存在 - */ -object PermissionDeniedException: IllegalAccessException("无操作限权") - /** * 错误参数 */ diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt index 6b2c7a3a6..4292c972a 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt @@ -35,6 +35,7 @@ import net.mamoe.mirai.api.http.data.common.DTO import net.mamoe.mirai.api.http.data.common.VerifyDTO import net.mamoe.mirai.api.http.util.jsonParseOrNull import net.mamoe.mirai.api.http.util.toJson +import net.mamoe.mirai.contact.PermissionDeniedException import org.slf4j.Logger import org.slf4j.helpers.NOPLogger import org.slf4j.helpers.NOPLoggerFactory diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt index 645cb741e..579be924e 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt @@ -4,7 +4,6 @@ import io.ktor.application.Application import io.ktor.application.call import io.ktor.routing.routing import kotlinx.serialization.Serializable -import net.mamoe.mirai.api.http.data.PermissionDeniedException import net.mamoe.mirai.api.http.data.StateCode import net.mamoe.mirai.api.http.data.common.DTO import net.mamoe.mirai.api.http.data.common.VerifyDTO @@ -19,37 +18,31 @@ fun Application.groupManageModule() { * 禁言(需要相关权限) */ miraiVerify("/muteAll") { - it.session.bot.getGroup(it.target).muteAll = true + it.session.bot.getGroup(it.target).isMuteAll = true call.respondStateCode(StateCode.Success) } miraiVerify("/unmuteAll") { - it.session.bot.getGroup(it.target).muteAll = false + it.session.bot.getGroup(it.target).isMuteAll = false call.respondStateCode(StateCode.Success) } miraiVerify("/mute") { - when (it.session.bot.getGroup(it.target)[it.memberId].mute(it.time)) { - true -> call.respondStateCode(StateCode.Success) - else -> throw PermissionDeniedException - } + it.session.bot.getGroup(it.target)[it.memberId].mute(it.time) + call.respondStateCode(StateCode.Success) } miraiVerify("/unmute") { - when (it.session.bot.getGroup(it.target).members[it.memberId].unmute()) { - true -> call.respondStateCode(StateCode.Success) - else -> throw PermissionDeniedException - } + it.session.bot.getGroup(it.target).members[it.memberId].unmute() + call.respondStateCode(StateCode.Success) } /** * 移出群聊(需要相关权限) */ miraiVerify("/kick") { - when (it.session.bot.getGroup(it.target)[it.memberId].kick(it.msg)) { - true -> call.respondStateCode(StateCode.Success) - else -> throw PermissionDeniedException - } + it.session.bot.getGroup(it.target)[it.memberId].kick(it.msg) + call.respondStateCode(StateCode.Success) } /** @@ -65,8 +58,8 @@ fun Application.groupManageModule() { with(dto.config) { name?.let { group.name = it } announcement?.let { group.entranceAnnouncement = it } - confessTalk?.let { group.confessTalk = it } - allowMemberInvite?.let { group.allowMemberInvite = it } + confessTalk?.let { group.isConfessTalkEnabled = it } + allowMemberInvite?.let { group.isAllowMemberInvite = it } // TODO: 待core接口实现设置可改 // autoApprove?.let { group.autoApprove = it } // anonymousChat?.let { group.anonymousChat = it } @@ -128,8 +121,8 @@ private data class GroupDetailDTO( val anonymousChat: Boolean? = null ) : DTO { constructor(group: Group) : this( - group.name, group.entranceAnnouncement, group.confessTalk, group.allowMemberInvite, - group.autoApprove, group.anonymousChat + group.name, group.entranceAnnouncement, group.isConfessTalkEnabled, group.isAllowMemberInvite, + group.isAutoApproveEnabled, group.isAnonymousChatEnabled ) } 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 259fe6591..5cad965a2 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 @@ -227,9 +227,9 @@ internal class MemberImpl( override val bot: QQAndroidBot get() = qq.bot - override suspend fun mute(durationSeconds: Int): Boolean { + override suspend fun mute(durationSeconds: Int) { if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) { - return false + throw PermissionDeniedException() } bot.network.run { @@ -243,12 +243,11 @@ internal class MemberImpl( @Suppress("RemoveRedundantQualifierName") // or unresolved reference net.mamoe.mirai.event.events.MemberMuteEvent(this@MemberImpl, durationSeconds, null).broadcast() - return true } - override suspend fun unmute(): Boolean { + override suspend fun unmute() { if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) { - return false + throw PermissionDeniedException() } bot.network.run { @@ -262,16 +261,15 @@ internal class MemberImpl( @Suppress("RemoveRedundantQualifierName") // or unresolved reference net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast() - return true } - override suspend fun kick(message: String): Boolean { + override suspend fun kick(message: String) { if (group.botPermission != MemberPermission.OWNER && (!group.botPermission.isOperator() || this.isOperator())) { - return false + throw PermissionDeniedException() } bot.network.run { - return TroopManagement.Kick( + TroopManagement.Kick( client = bot.client, member = this@MemberImpl, message = message @@ -394,7 +392,7 @@ internal class GroupImpl( } - override var allowMemberInvite: Boolean + override var isAllowMemberInvite: Boolean get() = _allowMemberInvite set(newValue) { this.checkBotPermissionOperator() @@ -414,19 +412,19 @@ internal class GroupImpl( } } - override var autoApprove: Boolean + override var isAutoApproveEnabled: Boolean get() = _autoApprove set(newValue) { TODO() } - override var anonymousChat: Boolean + override var isAnonymousChatEnabled: Boolean get() = _anonymousChat set(newValue) { TODO() } - override var confessTalk: Boolean + override var isConfessTalkEnabled: Boolean get() = _confessTalk set(newValue) { this.checkBotPermissionOperator() @@ -447,7 +445,7 @@ internal class GroupImpl( } - override var muteAll: Boolean + override var isMuteAll: Boolean get() = _muteAll set(newValue) { this.checkBotPermissionOperator() diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index 5b2bd3fe2..87afe2054 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -180,14 +180,14 @@ internal class OnlinePush { return if (target == 0L) { if (time == 0) { GroupMuteAllEvent( - origin = group.muteAll.also { group._muteAll = false }, + origin = group.isMuteAll.also { group._muteAll = false }, new = false, operator = operator, group = group ) } else { GroupMuteAllEvent( - origin = group.muteAll.also { group._muteAll = true }, + origin = group.isMuteAll.also { group._muteAll = true }, new = true, operator = operator, group = group @@ -213,7 +213,7 @@ internal class OnlinePush { val operator = group[this.readUInt().toLong()] val switch = this.readInt() == 0 return GroupAllowAnonymousChatEvent( - origin = group.anonymousChat.also { group._anonymousChat = switch }, + origin = group.isAnonymousChatEnabled.also { group._anonymousChat = switch }, new = switch, operator = operator, group = group @@ -236,7 +236,7 @@ internal class OnlinePush { when (message) { "管理员已关闭群聊坦白说" -> { return GroupAllowConfessTalkEvent( - origin = group.confessTalk.also { group._confessTalk = false }, + origin = group.isConfessTalkEnabled.also { group._confessTalk = false }, new = false, group = group, isByBot = false @@ -244,7 +244,7 @@ internal class OnlinePush { } "管理员已开启群聊坦白说" -> { return GroupAllowConfessTalkEvent( - origin = group.confessTalk.also { group._confessTalk = true }, + origin = group.isConfessTalkEnabled.also { group._confessTalk = true }, new = true, group = group, isByBot = false diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt index 0baf4205c..57392eb79 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt @@ -215,7 +215,7 @@ fun ByteReadPacket.readIoBuffer( * 解析 SSO 层包装 */ @UseExperimental(ExperimentalUnsignedTypes::class) -private fun parseSsoFrame(flag3: Int, input: ByteReadPacket): KnownPacketFactories.IncomingPacket { +private fun parseSsoFrame(flag3: Int, input: ByteReadPacket): KnownPacketFactories.IncomingPacket<*> { val commandName: String val ssoSequenceId: Int @@ -257,7 +257,7 @@ private fun parseSsoFrame(flag3: Int, input: ByteReadPacket): KnownPacketFactori * 解析 Uni 层包装 */ @UseExperimental(ExperimentalUnsignedTypes::class) -private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingPacket { +private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingPacket<*> { // 00 00 00 30 00 01 2F 7C 00 00 00 00 00 00 00 04 00 00 00 14 67 78 68 72 65 70 6F 72 74 2E 72 65 70 6F 72 74 00 00 00 08 66 82 D3 0B 00 00 00 00 // 00 00 00 06 08 00 diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/PermissionDeniedException.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/PermissionDeniedException.kt new file mode 100644 index 000000000..8f5320566 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/PermissionDeniedException.kt @@ -0,0 +1,9 @@ +package net.mamoe.mirai.contact + +/** + * 权限不足 + */ +actual class PermissionDeniedException : IllegalStateException { + actual constructor() : super("Permission denied") + actual constructor(message: String?) : super(message) +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt new file mode 100644 index 000000000..0e62c4240 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt @@ -0,0 +1,9 @@ +package net.mamoe.mirai.event.events + +@Suppress("unused") +actual class EventCancelledException : RuntimeException { + actual constructor() : super() + actual constructor(message: String?) : super(message) + actual constructor(message: String?, cause: Throwable?) : super(message, cause) + actual constructor(cause: Throwable?) : super(cause) +} \ No newline at end of file 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 759c5041c..4430c4514 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -27,6 +27,7 @@ import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.transferTo +import kotlin.jvm.JvmStatic /** * 机器人对象. 一个机器人实例登录一个 QQ 账号. @@ -42,6 +43,7 @@ abstract class Bot : CoroutineScope { /** * 复制一份此时的 [Bot] 实例列表. */ + @JvmStatic val instances: List> get() = BotImpl.instances.toList() /** @@ -52,6 +54,7 @@ abstract class Bot : CoroutineScope { /** * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] */ + @JvmStatic fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt index 01e2c741c..4a453234c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt @@ -26,6 +26,24 @@ data class BotAccount( val passwordMd5: ByteArray // md5 ) { constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as BotAccount + + if (id != other.id) return false + if (!passwordMd5.contentEquals(other.passwordMd5)) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + passwordMd5.contentHashCode() + return result + } } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index 42a96bec4..80c2900bc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -38,8 +38,8 @@ interface Contact : CoroutineScope { /** * 可以是 QQ 号码或者群号码. * - * 对于 QQ, `uin` 与 `id` 是相同的意思. - * 对于 Group, `groupCode` 与 `id` 是相同的意思. + * 对于 [QQ], `uin` 与 `id` 是相同的意思. + * 对于 [Group], `groupCode` 与 `id` 是相同的意思. */ val id: Long 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 b5dace4d2..a3c2a8ff2 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 @@ -46,8 +46,9 @@ interface Group : Contact, CoroutineScope { * 当前仅能修改状态. * * @see GroupMuteAllEvent - */// TODO: 2020/2/5 实现 muteAll 的查询 - var muteAll: Boolean + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + var isMuteAll: Boolean /** * 坦白说状态. `true` 为允许. * @@ -56,7 +57,7 @@ interface Group : Contact, CoroutineScope { * @see GroupAllowConfessTalkEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var confessTalk: Boolean + var isConfessTalkEnabled: Boolean /** * 允许群员邀请好友入群的状态. `true` 为允许 * @@ -65,15 +66,15 @@ interface Group : Contact, CoroutineScope { * @see GroupAllowMemberInviteEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var allowMemberInvite: Boolean + var isAllowMemberInvite: Boolean /** * 自动加群审批 */ - val autoApprove: Boolean + val isAutoApproveEnabled: Boolean /** * 匿名聊天 */ - val anonymousChat: Boolean + val isAnonymousChatEnabled: Boolean /** * 同为 groupCode, 用户看到的群号码. @@ -143,7 +144,7 @@ interface Group : Contact, CoroutineScope { /** * by @kar98k - */ + */ // don't @JvmStatic: JDK 1.8 required fun calculateGroupUinByGroupCode(groupCode: Long): Long { var left: Long = groupCode / 1000000L diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index 2e8f9b859..66e8f2ca3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -35,20 +35,22 @@ interface Member : QQ, Contact { /** * 群名片. 可能为空. 修改时将会触发事件 * - * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] + * 在修改时将会异步上传至服务器. * * @see [groupCardOrNick] 获取非空群名片或昵称 * * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + * @throws PermissionDeniedException 无权限修改时 */ var nameCard: String /** * 群头衔 * - * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] + * 在修改时将会异步上传至服务器. * * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + * @throws PermissionDeniedException 无权限修改时 */ var specialTitle: String @@ -63,22 +65,25 @@ interface Member : QQ, Contact { * @see Int.daysToSeconds * * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 */ - suspend fun mute(durationSeconds: Int): Boolean + suspend fun mute(durationSeconds: Int) /** - * 解除禁言. 机器人无权限时返回 `false`. + * 解除禁言. * * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 */ - suspend fun unmute(): Boolean + suspend fun unmute() /** - * 踢出该成员. 机器人无权限时返回 `false`. + * 踢出该成员. * * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 */ - suspend fun kick(message: String = ""): Boolean + suspend fun kick(message: String = "") /** * 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true @@ -94,10 +99,10 @@ interface Member : QQ, Contact { val Member.groupCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick @ExperimentalTime -suspend inline fun Member.mute(duration: Duration): Boolean { +suspend inline fun Member.mute(duration: Duration) { require(duration.inDays <= 30) { "duration must be at most 1 month" } require(duration.inSeconds > 0) { "duration must be greater than 0 second" } - return this.mute(duration.inSeconds.toInt()) + this.mute(duration.inSeconds.toInt()) } suspend inline fun Member.mute(durationSeconds: Long) = this.mute(durationSeconds.toInt()) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt index c064535e6..ac1137744 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt @@ -72,9 +72,9 @@ inline fun Member.isOperator(): Boolean = this.permission.isOperator() /** * 权限不足 */ -class PermissionDeniedException : IllegalStateException { - constructor() : super("Permission denied") - constructor(message: String?) : super(message) +expect class PermissionDeniedException : IllegalStateException { + constructor() + constructor(message: String?) } @UseExperimental(MiraiExperimentalAPI::class) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index 5fe31559e..bc4a87a1b 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -438,14 +438,14 @@ class MessageSubscribersBuilder>( */ @MessageDsl inline fun has(): ListeningFilter = - content { message.any { it::class == M::class } } + content { message.any { it is M } } /** * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent] */ @MessageDsl inline fun has(crossinline onEvent: MessageListener): Listener = - content({ message.any { it::class == M::class } }, onEvent) + content({ message.any { it is M } }, onEvent) /** * 如果 [filter] 返回 `true` diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt index 9047df8e0..a855e40b2 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt @@ -22,11 +22,11 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI @Suppress("unused") -class EventCancelledException : RuntimeException { - constructor() : super() - constructor(message: String?) : super(message) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) +expect class EventCancelledException : RuntimeException { + constructor() + constructor(message: String?) + constructor(message: String?, cause: Throwable?) + constructor(cause: Throwable?) } // note: 若你使用 IntelliJ IDEA, 按 alt + 7 可打开结构 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt index 8261aa280..0232fcecf 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt @@ -13,6 +13,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.event.Event import net.mamoe.mirai.message.data.At import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain @@ -31,7 +32,7 @@ class GroupMessage( val permission: MemberPermission, sender: Member, override val message: MessageChain -) : MessagePacket(bot) { +) : MessagePacket(bot), Event { val group: Group by group.unsafeWeakRef() override val sender: Member by sender.unsafeWeakRef() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt index dc2b7bc78..fb2a35ca0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.groupCardOrNick import net.mamoe.mirai.utils.MiraiInternalAPI @@ -22,7 +23,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.nick}") + constructor(member: Member) : this(member.id, "@${member.groupCardOrNick}") override fun toString(): String = display diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt index fdf712739..e1fff684c 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt @@ -44,6 +44,7 @@ internal val factory: BotFactory = run { /** * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmOverloads fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = factory.Bot(context, qq, password, configuration) @@ -57,6 +58,7 @@ inline fun Bot(context: Context, qq: Long, password: String, configuration: (Bot /** * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmOverloads fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = factory.Bot(ContextImpl(), qq, password, configuration) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/PermissionDeniedException.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/PermissionDeniedException.kt new file mode 100644 index 000000000..01ff0cef3 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/PermissionDeniedException.kt @@ -0,0 +1,9 @@ +package net.mamoe.mirai.contact + +/** + * 权限不足 + */ // 不要删除多平台结构 +actual class PermissionDeniedException : IllegalStateException { + actual constructor() : super("Permission denied") + actual constructor(message: String?) : super(message) +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt new file mode 100644 index 000000000..7b17dc366 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt @@ -0,0 +1,12 @@ +package net.mamoe.mirai.event.events + + +// 不要删除跨平台结构. +// 否则在 Java 中这个 class 不会被认为是 java.lang.RuntimeException (Kotlin bug) +@Suppress("unused") +actual class EventCancelledException : RuntimeException { + actual constructor() : super() + actual constructor(message: String?) : super(message) + actual constructor(message: String?, cause: Throwable?) : super(message, cause) + actual constructor(cause: Throwable?) : super(cause) +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt index 7ca0f9173..f76ed73fa 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt @@ -62,7 +62,7 @@ internal class DefaultLoginSolver : LoginSolver() { } } bot.logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") - return readLine()?.takeUnless { it.isEmpty() || it.length != 4 }.also { + return readLine()!!.takeUnless { it.isEmpty() || it.length != 4 }.also { bot.logger.info("正在提交[$it]中...") } } diff --git a/mirai-demos/mirai-demo-java/build.gradle b/mirai-demos/mirai-demo-java/build.gradle new file mode 100644 index 000000000..e5e8fc5a1 --- /dev/null +++ b/mirai-demos/mirai-demo-java/build.gradle @@ -0,0 +1,18 @@ +apply plugin: "java" +apply plugin: "kotlin" + +dependencies { + implementation files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug + + implementation files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug + implementation project(":mirai-core-qqandroid") + implementation project(":mirai-japt") +} + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +compileJava.options.encoding = 'UTF-8' + +compileTestJava.options.encoding = 'UTF-8' \ No newline at end of file diff --git a/mirai-demos/mirai-demo-java/src/main/java/demo/BlockingTest.java b/mirai-demos/mirai-demo-java/src/main/java/demo/BlockingTest.java new file mode 100644 index 000000000..b2f8614cf --- /dev/null +++ b/mirai-demos/mirai-demo-java/src/main/java/demo/BlockingTest.java @@ -0,0 +1,28 @@ +package demo; + +import net.mamoe.mirai.japt.BlockingBot; +import net.mamoe.mirai.japt.BlockingContacts; +import net.mamoe.mirai.japt.BlockingQQ; +import net.mamoe.mirai.japt.Events; +import net.mamoe.mirai.message.GroupMessage; + +class BlockingTest { + + public static void main(String[] args) throws InterruptedException { + BlockingBot bot = BlockingBot.newInstance(123456, ""); + + bot.login(); + + bot.getFriendList().forEach(friend -> { + System.out.println(friend.getNick()); + }); + + Events.subscribeAlways(GroupMessage.class, (GroupMessage message) -> { + final BlockingQQ sender = BlockingContacts.createBlocking(message.getSender()); + + sender.sendMessage("Hello"); + }); + + Thread.sleep(999999999); + } +} diff --git a/mirai-japt/.README_images/0ff38fe6.png b/mirai-japt/.README_images/0ff38fe6.png new file mode 100644 index 000000000..d0ccbfad1 Binary files /dev/null and b/mirai-japt/.README_images/0ff38fe6.png differ diff --git a/mirai-japt/.README_images/4SY8BC@J4ZKQM]7OZ_~BC1I_1.png b/mirai-japt/.README_images/4SY8BC@J4ZKQM]7OZ_~BC1I_1.png new file mode 100644 index 000000000..1fd2565a9 Binary files /dev/null and b/mirai-japt/.README_images/4SY8BC@J4ZKQM]7OZ_~BC1I_1.png differ diff --git a/mirai-japt/.README_images/722W(E$HTTX{D6XFFH]]$43.png b/mirai-japt/.README_images/722W(E$HTTX{D6XFFH]]$43.png new file mode 100644 index 000000000..87d56bac5 Binary files /dev/null and b/mirai-japt/.README_images/722W(E$HTTX{D6XFFH]]$43.png differ diff --git a/mirai-japt/.README_images/ce3034e3.png b/mirai-japt/.README_images/ce3034e3.png new file mode 100644 index 000000000..340c8c434 Binary files /dev/null and b/mirai-japt/.README_images/ce3034e3.png differ diff --git a/mirai-japt/README.md b/mirai-japt/README.md index a9627a246..1b80cef52 100644 --- a/mirai-japt/README.md +++ b/mirai-japt/README.md @@ -3,7 +3,51 @@ Mirai Java Apt -提供一些阻塞/异步/RxJava API 来让 Java 调用 Mirai 的挂起函数 API 更容易 -提供 Utils 类来让 Java 调用 Mirai 的内联方法更容易 +提供阻塞API 来让 Java 调用 Mirai 的 API 更容易 -该模块暂未完成. \ No newline at end of file +## Requirements + +- JDK 1.8+ + +## 开始 + +```java +class Test{ + public static void main(String[] args){ + BlockingBot bot = BlockingBot.newInstance(123456, ""); + + bot.login(); + + bot.getFriendList().forEach(friend -> { + System.out.println(friend.getNick()); + }); + + Events.subscribeAlways(GroupMessage.class, (GroupMessage message) -> { + final BlockingQQ sender = BlockingContacts.createBlocking(message.getSender()); + + sender.sendMessage("Hello"); + }); + + Thread.sleep(999999999); + } +} +``` + +## 便捷开发 + +在 IntelliJ IDEA 或 Android Studio 中找到设置 `Editor -> General -> Postfix Completion`, 添加一个设置到 `Java` 分类中: +![](.README_images/ce3034e3.png) +Applicable expression types: +``` +net.mamoe.mirai.contact.Contact +``` +转换后表达式: +``` +net.mamoe.mirai.japt.BlockingContacts.createBlocking($EXPR$) +``` + +效果: + +![4SY8BC@J4ZKQM7OZ_~BC1I_1](.README_images/4SY8BC%40J4ZKQM%5D7OZ_~BC1I_1.png) + +![722WEHTTXD6XFFH43](.README_images/722W%28E%24HTTX%7BD6XFFH%5D%5D%2443.png) diff --git a/mirai-japt/build.gradle.kts b/mirai-japt/build.gradle.kts index eee895d1d..f2dcaff16 100644 --- a/mirai-japt/build.gradle.kts +++ b/mirai-japt/build.gradle.kts @@ -1,8 +1,12 @@ plugins { kotlin("jvm") java + `maven-publish` + id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME } +apply(from = rootProject.file("gradle/publish.gradle")) + val kotlinVersion: String by rootProject.ext val atomicFuVersion: String by rootProject.ext val coroutinesVersion: String by rootProject.ext @@ -13,6 +17,12 @@ val serializationVersion: String by rootProject.ext val klockVersion: String by rootProject.ext val ktorVersion: String by rootProject.ext +description = "Java helper for Mirai" + +@Suppress("PropertyName") +val mirai_japt_version: String by rootProject.ext +version = mirai_japt_version + kotlin { sourceSets { all { @@ -38,6 +48,8 @@ dependencies { api(kotlinx("io", kotlinXIoVersion)) api(kotlinx("coroutines-io", coroutinesIoVersion)) api(kotlinx("coroutines-core", coroutinesVersion)) + api(kotlin("stdlib-jdk7", kotlinVersion)) + api(kotlin("stdlib-jdk8", kotlinVersion)) } tasks.withType() { diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java index 49996572a..fb55493a6 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java @@ -1,21 +1,57 @@ package net.mamoe.mirai.japt; import kotlinx.io.core.ByteReadPacket; +import net.mamoe.mirai.Bot; import net.mamoe.mirai.BotAccount; +import net.mamoe.mirai.BotFactoryJvmKt; +import net.mamoe.mirai.contact.QQ; import net.mamoe.mirai.data.AddFriendResult; +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.BotConfiguration; +import net.mamoe.mirai.utils.MiraiExperimentalAPI; import net.mamoe.mirai.utils.MiraiInternalAPI; import net.mamoe.mirai.utils.MiraiLogger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.OutputStream; import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Stream; +/** + * 对 {@link Bot} 的阻塞式包装 + * + * @see Bot + */ @SuppressWarnings("unused") public interface BlockingBot { + /** + * 使用默认配置创建一个机器人实例 + * + * @param id qq 号 + * @param password 密码 + * @return 机器人实例 + */ + static BlockingBot newInstance(long id, String password) { + return BlockingContacts.createBlocking(BotFactoryJvmKt.Bot(id, password)); + } - // TODO: 2020/2/3 需要更新 + /** + * 使用特定配置创建一个机器人实例 + * + * @param id qq 号 + * @param password 密码 + * @return 机器人实例 + */ + static BlockingBot newInstance(long id, String password, BotConfiguration configuration) { + return BlockingContacts.createBlocking(BotFactoryJvmKt.Bot(id, password, configuration)); + } + + // 要获取 Bot 实例列表, 请前往 BotKt /** * 账号信息 @@ -29,6 +65,13 @@ public interface BlockingBot { */ long getUin(); + /** + * 获取昵称 + */ + @NotNull + @MiraiExperimentalAPI(message = "还未支持") + String getNick(); + /** * 日志记录器 */ @@ -37,27 +80,33 @@ public interface BlockingBot { // region contacts + /** + * 获取自身 QQ 实例 + */ + @NotNull + QQ getSelfQQ(); + /** * 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友 */ @NotNull - List getQQs(); + List getFriendList(); /** * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. */ @NotNull - BlockingQQ getQQ(long id); + BlockingQQ getFriend(long id); /** * 与这个机器人相关的群列表. 机器人不一定是群成员. */ @NotNull - List getGroups(); + List getGroupList(); /** * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. - * 若 {@code id} 无效, 将会抛出 {@link java.util.NoSuchElementException} + * 若 {@code id} 无效, 将会抛出 {@link NoSuchElementException} */ @NotNull BlockingGroup getGroup(long id); @@ -75,20 +124,48 @@ public interface BlockingBot { /** * 登录. - *

- * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login] */ void login(); + /** + * 查询群列表. 返回值前 32 bits 为 uin, 后 32 bits 为 groupCode + */ + @NotNull + Stream queryGroupList(); + + /** + * 查询群资料. 获得的仅为当前时刻的资料. + * 请优先使用 {@link #getGroup(long)} 然后查看群资料. + */ + @NotNull + GroupInfo queryGroupInfo(long groupCode); + + /** + * 查询群成员列表. + * 请优先使用 {@link #getGroup(long)} , {@link BlockingGroup#getMembers()} 查看群成员. + *

+ * 这个函数很慢. 请不要频繁使用. + */ + @NotNull + Stream queryGroupMemberList(long groupUin, long groupCode, long ownerId); + // endregion // region actions + @NotNull byte[] downloadAsByteArray(@NotNull Image image); @NotNull ByteReadPacket download(@NotNull Image image); + /** + * 下载图片到 {@code outputStream}. + *

+ * 不会自动关闭 {@code outputStream} + */ + void download(@NotNull Image image, @NotNull OutputStream outputStream); + /** * 添加一个好友 * diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java index b168c1948..26faed221 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java @@ -1,34 +1,87 @@ package net.mamoe.mirai.japt; +import net.mamoe.mirai.Bot; +import net.mamoe.mirai.contact.Contact; +import net.mamoe.mirai.contact.Member; +import net.mamoe.mirai.contact.QQ; +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.MessageSendEvent.FriendMessageSendEvent; +import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent; +import net.mamoe.mirai.message.data.Image; import net.mamoe.mirai.message.data.Message; import net.mamoe.mirai.message.data.MessageChain; +import net.mamoe.mirai.utils.ExternalImage; import org.jetbrains.annotations.NotNull; +/** + * 对 {@link Contact} 的阻塞式包装. + */ @SuppressWarnings("unused") public interface BlockingContact { /** - * 这个联系人所属 [Bot] + * 这个联系人所属 {@link Bot} */ @NotNull BlockingBot getBot(); /** - * 可以是 QQ 号码或者群号码 [GroupId]. + * 可以是 QQ 号码或者群号码. + *

+ * 对于 QQ, {@code uin} 与 {@code id} 是相同的意思. + * 对于 Group, {@code groupCode} 与 {@code id} 是相同的意思. */ long getId(); /** * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable */ - void sendMessage(@NotNull MessageChain messages); + // kotlin bug + void sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException; /** * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable */ - void sendMessage(@NotNull String message); + void sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException; /** * 向这个对象发送消息. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable */ - void sendMessage(@NotNull Message message); + void sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException; + + /** + * 上传一个图片以备发送. + * 群图片与好友图片在服务器上是通用的, 在 mirai 目前不通用. + * + * @throws EventCancelledException 当发送消息事件被取消 + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + */ + Image uploadImage(@NotNull ExternalImage image) throws EventCancelledException; + + /** + * 判断 {@code this} 和 {@code other} 是否是相同的类型, 并且 {@link Contact#getId()} 相同. + *

+ * 注: + * {@link Contact#getId()} 相同的 {@link Member} 和 {@link QQ}, 他们并不 equals. + * 因为, {@link Member} 含义为群员, 必属于一个群. + * 而 {@link QQ} 含义为一个独立的人, 可以是好友, 也可以是陌生人. + */ + boolean equals(Object other); } diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java index f69de07f5..2b95d9453 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java @@ -8,24 +8,31 @@ import net.mamoe.mirai.japt.internal.BlockingBotImpl; import net.mamoe.mirai.japt.internal.BlockingGroupImpl; import net.mamoe.mirai.japt.internal.BlockingMemberImpl; import net.mamoe.mirai.japt.internal.BlockingQQImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; /** * 构造阻塞式的联系人. */ public final class BlockingContacts { - public static BlockingQQ createBlocking(QQ qq) { - return new BlockingQQImpl(qq); + @NotNull + public static BlockingQQ createBlocking(@NotNull QQ qq) { + return new BlockingQQImpl(Objects.requireNonNull(qq)); } - public static BlockingGroup createBlocking(Group group) { - return new BlockingGroupImpl(group); + @NotNull + public static BlockingGroup createBlocking(@NotNull Group group) { + return new BlockingGroupImpl(Objects.requireNonNull(group)); } - public static BlockingMember createBlocking(Member member) { - return new BlockingMemberImpl(member); + @NotNull + public static BlockingMember createBlocking(@NotNull Member member) { + return new BlockingMemberImpl(Objects.requireNonNull(member)); } - public static BlockingBot createBlocking(Bot bot) { - return new BlockingBotImpl(bot); + @NotNull + public static BlockingBot createBlocking(@NotNull Bot bot) { + return new BlockingBotImpl(Objects.requireNonNull(bot)); } } diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java index 54f50e0f5..2ebd58f6b 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java @@ -1,13 +1,106 @@ package net.mamoe.mirai.japt; -import net.mamoe.mirai.contact.Group; +import net.mamoe.mirai.contact.*; +import net.mamoe.mirai.data.MemberInfo; +import net.mamoe.mirai.event.events.*; +import net.mamoe.mirai.utils.MiraiExperimentalAPI; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Map; +import java.util.List; import java.util.NoSuchElementException; @SuppressWarnings("unused") public interface BlockingGroup extends BlockingContact { + /** + * 群名称. + */ + @NotNull + String getName(); + + /** + * 修改群名称 + * 频繁修改可能会被服务器拒绝. + * + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + * @see MemberPermissionChangeEvent + */ + void setName(@NotNull String name) throws PermissionDeniedException; + + /** + * 入群公告, 没有时为空字符串. (同步事件更新) + */ + @NotNull + String getEntranceAnnouncement(); + + /** + * 修改入群公告. + * + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + * @see GroupEntranceAnnouncementChangeEvent + */ + void setEntranceAnnouncement(@NotNull String announcement) throws PermissionDeniedException; + + /** + * 获取全员禁言状态 + * + * @return 全员禁言状态. true 为开启 + */ + boolean isMuteAll(); + + /** + * 设置全体禁言 + * + * @see GroupMuteAllEvent + */ + void setMuteAll(boolean enabled) throws PermissionDeniedException; + + /** + * 获取坦白说状态 + * + * @return 坦白说状态, true 为允许 + */ + boolean isConfessTalkEnabled(); + + /** + * 设置坦白说状态 + * + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + * @see GroupAllowConfessTalkEvent + */ + void setConfessTalk(boolean enabled) throws PermissionDeniedException; + + /** + * 获取允许群员邀请好友入群的状态. + * + * @return 允许群员邀请好友入群的状态. `true` 为允许 + */ + boolean isAllowMemberInvite(); + + /** + * 设置允许群员邀请好友入群的状态. + * + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + * @see GroupAllowMemberInviteEvent + */ + void setAllowMemberInvite(boolean allow) throws PermissionDeniedException; + + /** + * 获取自动加群审批的状态 + */ + boolean isAutoApproveEnabled(); + + /** + * 匿名聊天是否开启 + */ + boolean isAnonymousChatEnabled(); + + /** + * 同为 groupCode, 用户看到的群号码. + */ + @Override + long getId(); + /** * 群主 (同步事件更新) */ @@ -15,16 +108,29 @@ public interface BlockingGroup extends BlockingContact { BlockingMember getOwner(); /** - * 群名称 (同步事件更新) + * 机器人被禁言还剩余多少秒 + * + * @see BotMuteEvent + * @see GroupKt#isBotMuted */ - @NotNull - String getName(); + int getBotMuteRemaining(); /** - * 入群公告, 没有时为空字符串. (同步事件更新) + * 检查机器人是否正处于禁言状态 + */ + default boolean isBotMuted() { + int time = getBotMuteRemaining(); + return time != 0 && time != 0xFFFFFFFF; + } + + /** + * 机器人在这个群里的权限 + * + * @see BotGroupPermissionChangeEvent */ @NotNull - String getAnnouncement(); + @MiraiExperimentalAPI + MemberPermission getBotPermission(); /** * 在 {@link Group} 实例创建的时候查询一次. 并与事件同步事件更新 @@ -32,7 +138,7 @@ public interface BlockingGroup extends BlockingContact { * **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新 */ @NotNull - Map getMembers(); + List getMembers(); /** * 获取群成员. 若此 ID 的成员不存在, 则会抛出 {@link NoSuchElementException} @@ -40,11 +146,38 @@ public interface BlockingGroup extends BlockingContact { @NotNull BlockingMember getMember(long id); + /** + * 获取群成员. 若此 ID 的成员不存在则返回 null + */ + @Nullable + BlockingMember getMemberOrNull(long id); + + /** + * 检查此 id 的群成员是否存在 + */ + boolean containsMember(long id); + /** * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 */ boolean quit(); + /** + * 构造一个 [Member]. + * 非特殊情况请不要使用这个函数. 优先使用 [get]. + */ + @MiraiExperimentalAPI(message = "dangerous") + @NotNull + Member newMember(@NotNull MemberInfo memberInfo); + @NotNull String toFullString(); + + static long calculateGroupUinByGroupCode(long groupCode) { + return Group.Companion.calculateGroupUinByGroupCode(groupCode); + } + + static long calculateGroupCodeByGroupUin(long groupUin) { + return Group.Companion.calculateGroupCodeByGroupUin(groupUin); + } } \ No newline at end of file diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java index 0c601e86d..b043fe2e0 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java @@ -1,10 +1,13 @@ package net.mamoe.mirai.japt; +import kotlin.text.StringsKt; import net.mamoe.mirai.contact.MemberPermission; +import net.mamoe.mirai.contact.PermissionDeniedException; +import net.mamoe.mirai.event.events.MemberCardChangeEvent; import org.jetbrains.annotations.NotNull; @SuppressWarnings("unused") -public interface BlockingMember { +public interface BlockingMember extends BlockingQQ { /** * 所在的群 */ @@ -17,16 +20,72 @@ public interface BlockingMember { @NotNull MemberPermission getPermission(); + /** + * 群名片. 可能为空. + */ + @NotNull + String getNameCard(); + + /** + * 修改群名片. 将会触发事件 + * + * @throws PermissionDeniedException 无权限修改时 + * @see #getGroupCardOrNick() 获取非空群名片或昵称 + * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + */ + void setNameCard(@NotNull String nameCard) throws PermissionDeniedException; + + /** + * 获取群名片或昵称 + */ + @NotNull + default String getGroupCardOrNick() { + String nameCard = this.getNameCard(); + if (!StringsKt.isBlank(nameCard)) { + return nameCard; + } + return this.getNick(); + } + /** * 禁言 * * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. - * @return 若机器人无权限禁言这个群成员, 返回 `false` + * @throws PermissionDeniedException 无权限修改时 */ - boolean mute(int durationSeconds); + void mute(int durationSeconds); + + /** + * 禁言 + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @throws PermissionDeniedException 无权限修改时 + */ + default void mute(long durationSeconds) { + mute((int) durationSeconds); + } /** * 解除禁言 + * + * @throws PermissionDeniedException 无权限修改时 */ - boolean unmute(); + void unmute(); + + /** + * 踢出该成员. + * + * @param message 消息 + * @throws PermissionDeniedException 无权限修改时 + */ + void kick(@NotNull String message); + + /** + * 踢出该成员. + * + * @throws PermissionDeniedException 无权限修改时 + */ + default void kick() { + kick(""); + } } diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java index 44145d585..7fb7afc0e 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java @@ -3,13 +3,30 @@ package net.mamoe.mirai.japt; import net.mamoe.mirai.data.FriendNameRemark; import net.mamoe.mirai.data.PreviousNameList; import net.mamoe.mirai.data.Profile; +import net.mamoe.mirai.utils.MiraiExperimentalAPI; import org.jetbrains.annotations.NotNull; @SuppressWarnings("unused") public interface BlockingQQ extends BlockingContact { + /** + * 获取 QQ 号码 + * + * @return QQ 号码 + */ + @Override + long getId(); + + /** + * 获取昵称 + * + * @return 昵称 + */ + String getNick(); + /** * 查询用户资料 */ + @MiraiExperimentalAPI(message = "还未支持") @NotNull Profile queryProfile(); @@ -20,12 +37,14 @@ public interface BlockingQQ extends BlockingContact { * - 昵称 * - 共同群内的群名片 */ + @MiraiExperimentalAPI(message = "还未支持") @NotNull PreviousNameList queryPreviousNameList(); /** * 查询机器人账号给这个人设置的备注 */ + @MiraiExperimentalAPI(message = "还未支持") @NotNull FriendNameRemark queryRemark(); } diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java index 76d7af4e7..ab46c98c6 100644 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java +++ b/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java @@ -20,18 +20,46 @@ import org.jetbrains.annotations.NotNull; import java.util.function.Consumer; import java.util.function.Function; +/** + * 事件处理 + */ public final class Events { + /** + * 监听一个事件, 当 {@code onEvent} 返回 {@link ListeningStatus#STOPPED} 时停止监听. + * 机器人离线后不会停止监听. + * + * @param eventClass 事件类 + * @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听. + * @param 事件类型 + * @return 事件监听器. 可调用 {@link Listener#complete()} 或 {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止. + */ @NotNull public static Listener subscribe(@NotNull Class eventClass, @NotNull Function onEvent) { return EventInternalJvmKt._subscribeEventForJaptOnly(eventClass, GlobalScope.INSTANCE, onEvent); } + /** + * 监听一个事件, 直到手动停止. + * 机器人离线后不会停止监听. + * + * @param eventClass 事件类 + * @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听. + * @param 事件类型 + * @return 事件监听器. 可调用 {@link Listener#complete()} 或 {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止. + */ @NotNull public static Listener subscribeAlways(@NotNull Class eventClass, @NotNull Consumer onEvent) { return EventInternalJvmKt._subscribeEventForJaptOnly(eventClass, GlobalScope.INSTANCE, onEvent); } + /** + * 阻塞地广播一个事件. + * + * @param event 事件 + * @param 事件类型 + * @return {@code event} 本身 + */ @NotNull public static E broadcast(@NotNull E event) { return EventsImplKt.broadcast(event); diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt index c07a24e67..0fbc212b4 100644 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt @@ -14,34 +14,55 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.readBytes import net.mamoe.mirai.Bot import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.data.AddFriendResult +import net.mamoe.mirai.data.GroupInfo +import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.japt.BlockingBot import net.mamoe.mirai.japt.BlockingGroup import net.mamoe.mirai.japt.BlockingQQ import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.toList +import java.io.OutputStream +import java.util.stream.Stream +import kotlin.streams.asStream internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { @MiraiInternalAPI override fun getAccount(): BotAccount = bot.account override fun getUin(): Long = bot.uin - override fun getLogger(): MiraiLogger = bot.logger - @UseExperimental(MiraiInternalAPI::class) - override fun getQQs(): List = bot.qqs.delegate.toList().map { it.blocking() } + @MiraiExperimentalAPI + override fun getNick(): String = bot.nick + + override fun getLogger(): MiraiLogger = bot.logger + override fun getSelfQQ(): QQ = bot.selfQQ + + override fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Stream = + runBlocking { bot.queryGroupMemberList(groupUin, groupCode, ownerId) }.asStream() - override fun getQQ(id: Long): BlockingQQ = bot.getFriend(id).blocking() @UseExperimental(MiraiInternalAPI::class) - override fun getGroups(): List = bot.groups.delegate.toList().map { it.blocking() } + override fun getFriendList(): List = bot.qqs.delegate.toList().map { it.blocking() } + + override fun getFriend(id: Long): BlockingQQ = bot.getFriend(id).blocking() + override fun queryGroupList(): Stream = runBlocking { bot.queryGroupList() }.asStream() + + @UseExperimental(MiraiInternalAPI::class) + override fun getGroupList(): List = bot.groups.delegate.toList().map { it.blocking() } + + override fun queryGroupInfo(code: Long): GroupInfo = runBlocking { bot.queryGroupInfo(code) } override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() } override fun getNetwork(): BotNetworkHandler = bot.network override fun login() = runBlocking { bot.login() } override fun downloadAsByteArray(image: Image): ByteArray = bot.run { runBlocking { image.download().readBytes() } } override fun download(image: Image): ByteReadPacket = bot.run { runBlocking { image.download() } } + override fun download(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.downloadTo(outputStream) } } + override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) } override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) } override fun dispose(throwable: Throwable?) = bot.close(throwable) diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt index 812299984..193b57506 100644 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt +++ b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt @@ -17,28 +17,36 @@ import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.data.FriendNameRemark +import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile import net.mamoe.mirai.japt.BlockingBot import net.mamoe.mirai.japt.BlockingGroup import net.mamoe.mirai.japt.BlockingMember import net.mamoe.mirai.japt.BlockingQQ -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.toChain -import net.mamoe.mirai.message.data.toMessage +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.toList internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ { override fun getBot(): BlockingBot = delegate.bot.blocking() override fun getId(): Long = delegate.id + override fun getNick(): String = delegate.nick + override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) } + override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) } + @MiraiExperimentalAPI override fun queryProfile(): Profile = runBlocking { delegate.queryProfile() } + + @MiraiExperimentalAPI override fun queryPreviousNameList(): PreviousNameList = runBlocking { delegate.queryPreviousNameList() } + + @MiraiExperimentalAPI override fun queryRemark(): FriendNameRemark = runBlocking { delegate.queryRemark() } } @@ -47,22 +55,76 @@ internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup { override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) } override fun getOwner(): BlockingMember = delegate.owner.blocking() + @MiraiExperimentalAPI + override fun newMember(memberInfo: MemberInfo): Member = delegate.Member(memberInfo) + + override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) } + override fun setEntranceAnnouncement(announcement: String) { + delegate.entranceAnnouncement = announcement + } + override fun getName(): String = delegate.name override fun getId(): Long = delegate.id + @MiraiExperimentalAPI + override fun getBotPermission(): MemberPermission = delegate.botPermission + + override fun setConfessTalk(enabled: Boolean) { + delegate.isConfessTalkEnabled = enabled + } + + override fun isAnonymousChatEnabled(): Boolean = delegate.isAnonymousChatEnabled + + override fun isAutoApproveEnabled(): Boolean = delegate.isAutoApproveEnabled + + override fun isConfessTalkEnabled(): Boolean = delegate.isConfessTalkEnabled + override fun toFullString(): String = delegate.toFullString() + override fun containsMember(id: Long): Boolean = delegate.contains(id) + + override fun isAllowMemberInvite(): Boolean = delegate.isAllowMemberInvite + override fun getMember(id: Long): BlockingMember = delegate[id].blocking() override fun getBot(): BlockingBot = delegate.bot.blocking() - override fun getAnnouncement(): String = delegate.entranceAnnouncement + override fun getBotMuteRemaining(): Int = delegate.botMuteRemaining + + override fun isMuteAll(): Boolean = delegate.isMuteAll + + override fun setName(name: String) { + delegate.name = name + } + + override fun setMuteAll(enabled: Boolean) { + delegate.isMuteAll = enabled + } + + override fun getEntranceAnnouncement(): String = delegate.entranceAnnouncement @UseExperimental(MiraiInternalAPI::class) - override fun getMembers(): Map = - delegate.members.delegate.toList().associateBy { it.id }.mapValues { it.value.blocking() } + override fun getMembers(): List = + delegate.members.delegate.toList().map { it.blocking() } + + override fun setAllowMemberInvite(allow: Boolean) { + delegate.isAllowMemberInvite = allow + } + + override fun getMemberOrNull(id: Long): BlockingMember? { + return delegate.getOrNull(id)?.blocking() + } override fun quit(): Boolean = runBlocking { delegate.quit() } } -internal class BlockingMemberImpl(private val delegate: Member) : BlockingMember { +internal class BlockingMemberImpl(private val delegate: Member) : BlockingMember, BlockingQQ by (delegate as QQ).blocking() { override fun getGroup(): BlockingGroup = delegate.group.blocking() + override fun getNameCard(): String = delegate.nameCard + override fun getPermission(): MemberPermission = delegate.permission - override fun mute(durationSeconds: Int): Boolean = runBlocking { delegate.mute(durationSeconds) } + override fun setNameCard(nameCard: String) { + delegate.nameCard = nameCard + } + + override fun mute(durationSeconds: Int) = runBlocking { delegate.mute(durationSeconds) } override fun unmute() = runBlocking { delegate.unmute() } + override fun kick(message: String) { + runBlocking { delegate.kick(message) } + } } \ No newline at end of file diff --git a/mirai-japt/src/test/kotlin/BlockingTest.java b/mirai-japt/src/test/kotlin/BlockingTest.java deleted file mode 100644 index 385ab7c23..000000000 --- a/mirai-japt/src/test/kotlin/BlockingTest.java +++ /dev/null @@ -1,14 +0,0 @@ -public class BlockingTest { - - - public static void main(String[] args) { - //Bot bot = new Bot(new BotAccount(123456, ""), EmptyCoroutineContext.INSTANCE); - //if (bot.getNetwork().login() != LoginResult.) { - // throw IllegalStateException("Login failed") - //} - //bot.getContacts().getGroups(); - - //var qq = BlockingContacts.createBlocking(bot.getContacts().getQQ(123L)) - //println(createBlocking.queryRemark()) - } -} diff --git a/settings.gradle b/settings.gradle index c94d586a7..f161aa955 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,6 +21,8 @@ pluginManagement { rootProject.name = 'mirai' +include(':mirai-demos') + try { def keyProps = new Properties() def keyFile = file("local.properties") @@ -46,7 +48,7 @@ include(':mirai-console') include(':mirai-api-http') include(':mirai-demos:mirai-demo-1') include(':mirai-demos:mirai-demo-gentleman') -include(':mirai-demos') +include(':mirai-demos:mirai-demo-java') include(':mirai-plugins') include(':mirai-plugins:image-sender') @@ -67,6 +69,7 @@ if (versionPos==-1){ project(':mirai-demos:mirai-demo-1').projectDir = file('mirai-demos/mirai-demo-1') project(':mirai-demos:mirai-demo-gentleman').projectDir = file('mirai-demos/mirai-demo-gentleman') +project(':mirai-demos:mirai-demo-java').projectDir = file('mirai-demos/mirai-demo-java') project(':mirai-plugins:image-sender').projectDir = file('mirai-plugins/image-sender') enableFeaturePreview('GRADLE_METADATA') \ No newline at end of file