diff --git a/UpdateLog.md b/UpdateLog.md index dfb087fa3..91224d7b0 100644 --- a/UpdateLog.md +++ b/UpdateLog.md @@ -4,6 +4,19 @@ 开发版本. 频繁更新, 不保证高稳定性 +### `0.8.0` *2019/12/14* +协议 +- 现在查询群资料时可处理群号无效的情况 +- 现在能正常分辨禁言事件包 + +功能 +- 增加无锁链表: LockFreeLinkedList, 并将 ContactList 的实现改为该无锁链表 +- **ContactSystem.getQQ 不再是 `suspend`** +- ContactSystem.getGroup 仍是 `suspend`, 原因为需要查询群资料. 在群 ID 无效时抛出 `GroupNotFoundException` + +优化 +- 日志中, 发送给服务器的包将会被以名字记录, 而不是 id + ### `0.7.5` *2019/12/09* - 修复验证码包发出后无回复 (错误的验证码包) diff --git a/gradle.properties b/gradle.properties index 107c57879..951fc616e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # style guide kotlin.code.style=official # config -mirai_version=0.7.5 +mirai_version=0.8.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/MiraiHttpApplication.kt b/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt index 7aad33d6b..4c990a704 100644 --- a/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt +++ b/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt @@ -20,7 +20,6 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.addFriend import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.getGroup -import net.mamoe.mirai.getQQ import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToUBytes @@ -81,6 +80,7 @@ suspend inline fun ApplicationCall.ok() = this.respond(HttpStatusCode.OK, "OK") /** * 错误请求. 抛出这个异常后将会返回错误给一个请求 */ +@Suppress("unused") open class IllegalAccessException : Exception { override val message: String get() = super.message!! @@ -107,14 +107,14 @@ private inline fun PipelineContext.param(name @Suppress("IMPLICIT_CAST_TO_ANY") @UseExperimental(ExperimentalUnsignedTypes::class) private inline fun PipelineContext.paramOrNull(name: String): R? = - when { - R::class == Byte::class -> call.parameters[name]?.toByte() - R::class == Int::class -> call.parameters[name]?.toInt() - R::class == Short::class -> call.parameters[name]?.toShort() - R::class == Float::class -> call.parameters[name]?.toFloat() - R::class == Long::class -> call.parameters[name]?.toLong() - R::class == Double::class -> call.parameters[name]?.toDouble() - R::class == Boolean::class -> when (call.parameters[name]) { + when (R::class) { + Byte::class -> call.parameters[name]?.toByte() + Int::class -> call.parameters[name]?.toInt() + Short::class -> call.parameters[name]?.toShort() + Float::class -> call.parameters[name]?.toFloat() + Long::class -> call.parameters[name]?.toLong() + Double::class -> call.parameters[name]?.toDouble() + Boolean::class -> when (call.parameters[name]) { "true" -> true "false" -> false "0" -> false @@ -123,13 +123,13 @@ private inline fun PipelineContext.paramOrNul else -> illegalParam("boolean", name) } - R::class == String::class -> call.parameters[name] + String::class -> call.parameters[name] - R::class == UByte::class -> call.parameters[name]?.toUByte() - R::class == UInt::class -> call.parameters[name]?.toUInt() - R::class == UShort::class -> call.parameters[name]?.toUShort() + UByte::class -> call.parameters[name]?.toUByte() + UInt::class -> call.parameters[name]?.toUInt() + UShort::class -> call.parameters[name]?.toUShort() - R::class == UByteArray::class -> call.parameters[name]?.hexToUBytes() - R::class == ByteArray::class -> call.parameters[name]?.hexToBytes() + UByteArray::class -> call.parameters[name]?.hexToUBytes() + ByteArray::class -> call.parameters[name]?.hexToBytes() else -> error(name::class.simpleName + " is not supported") } as R? 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 d2a96bacb..d3d01e7a1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -1,254 +1,136 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE") package net.mamoe.mirai -import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import net.mamoe.mirai.Bot.ContactSystem +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Job import net.mamoe.mirai.contact.* -import net.mamoe.mirai.contact.internal.Group -import net.mamoe.mirai.contact.internal.QQ import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult -import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess import net.mamoe.mirai.utils.BotConfiguration -import net.mamoe.mirai.utils.DefaultLogger -import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.GroupNotFoundException import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.internal.PositiveNumbers -import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail -import net.mamoe.mirai.utils.io.logStacktrace import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext -import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic -data class BotAccount( - val id: UInt, - val password: String -) { - constructor(id: Long, password: String) : this(id.toUInt(), password) -} - -// These pseudo 'constructors' are suspend, therefore they should not be inside the object - -@Suppress("FunctionName") -suspend inline fun Bot(account: BotAccount, logger: MiraiLogger): Bot = Bot(account, logger, coroutineContext) - -@Suppress("FunctionName") -suspend inline fun Bot(account: BotAccount): Bot = Bot(account, coroutineContext) - -@JvmSynthetic -@Suppress("FunctionName") -suspend inline fun Bot(qq: UInt, password: String): Bot = Bot(BotAccount(qq, password), coroutineContext) - -@Suppress("FunctionName") -suspend inline fun Bot(qq: Long, password: String): Bot = Bot(BotAccount(qq.toUInt(), password), coroutineContext) /** * Mirai 的机器人. 一个机器人实例登录一个 QQ 账号. * Mirai 为多账号设计, 可同时维护多个机器人. * - * [Bot] 由 3 个模块组成. - * [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问 - * [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问 - * [机器人账号信息][BotAccount]: 可通过 [Bot.qqAccount] 访问 - * - * 若需要得到机器人的 QQ 账号, 请访问 [Bot.qqAccount] - * 若需要得到服务器上所有机器人列表, 请访问 [Bot.instances] - * - * 在 BotHelper.kt 中有一些访问的捷径. 如 [Bot.getGroup] - * - * - * - * Bot that is the base of the whole program. - * It consists of - * a [ContactSystem], which manage contacts such as [QQ] and [Group]; - * a [TIMBotNetworkHandler], which manages the connection to the server; - * a [BotAccount], which stores the account information(e.g. qq number the bot) - * - * To of all the QQ contacts, access [Bot.qqAccount] - * To of all the Robot instance, access [Bot.instances] - * - * - * @author Him188moe - * @author NaturalHG * @see Contact */ -class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineContext) : CoroutineScope { - private val supervisorJob = SupervisorJob(context[Job]) - override val coroutineContext: CoroutineContext = - context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") } +interface Bot : CoroutineScope { + companion object { + suspend inline operator fun invoke(account: BotAccount, logger: MiraiLogger): Bot = BotImpl(account, logger, coroutineContext) + suspend inline operator fun invoke(account: BotAccount): Bot = BotImpl(account, context = coroutineContext) + @JvmSynthetic + suspend inline operator fun invoke(qq: UInt, password: String): Bot = BotImpl(BotAccount(qq, password), context = coroutineContext) - constructor(qq: Long, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context) - constructor(qq: UInt, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context) - constructor(account: BotAccount, context: CoroutineContext) : this(account, DefaultLogger("Bot(" + account.id + ")"), context) + suspend inline operator fun invoke(qq: Long, password: String): Bot = BotImpl(BotAccount(qq.toUInt(), password), context = coroutineContext) + operator fun invoke(qq: Long, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context) + @JvmSynthetic + operator fun invoke(qq: UInt, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context) - val contacts = ContactSystem() + operator fun invoke(account: BotAccount, context: CoroutineContext): Bot = BotImpl(account, context = context) - val network: BotNetworkHandler<*> get() = _network - private lateinit var _network: BotNetworkHandler<*> + inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) - init { - launch { - addInstance(this@Bot) - } + fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq) } - override fun toString(): String = "Bot(${account.id})" + /** + * 账号信息 + */ + val account: BotAccount + + /** + * 日志记录器 + */ + val logger: MiraiLogger + + override val coroutineContext: CoroutineContext + + // region contacts + + /** + * 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友 + */ + val qqs: ContactList + + /** + * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. + */ + fun getQQ(id: UInt): QQ + + /** + * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. + */ + fun getQQ(id: Long): QQ + + /** + * 与这个机器人相关的群列表. 机器人不一定是群成员. + */ + val groups: ContactList + + /** + * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. + * 若 [id] 无效, 将会抛出 [GroupNotFoundException] + */ + suspend fun getGroup(id: GroupId): Group + + /** + * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. + * 若 [internalId] 无效, 将会抛出 [GroupNotFoundException] + */ + suspend fun getGroup(internalId: GroupInternalId): Group + + /** + * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. + * 若 [id] 无效, 将会抛出 [GroupNotFoundException] + */ + suspend fun getGroup(id: Long): Group + + // endregion + + // region network + + /** + * 网络模块 + */ + val network: BotNetworkHandler<*> /** * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. * 然后重新启动并尝试登录 */ - @JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close fun tryReinitializeNetworkHandler( configuration: BotConfiguration, cause: Throwable? = null - ): Job = launch { - repeat(configuration.reconnectionRetryTimes) { - if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) { - logger.info("Reconnected successfully") - return@launch - } else { - delay(configuration.reconnectPeriodMillis) - } - } - } + ): Job /** * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. * 然后重新启动并尝试登录 */ - @JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close suspend fun reinitializeNetworkHandler( configuration: BotConfiguration, cause: Throwable? = null - ): LoginResult { - logger.info("BotAccount: ${qqAccount.toLong()}") - logger.info("Initializing BotNetworkHandler") - try { - if (::_network.isInitialized) { - _network.close(cause) - } - } catch (e: Exception) { - logger.error("Cannot close network handler", e) - } - _network = TIMBotNetworkHandler(this@Bot.coroutineContext + configuration, this@Bot) - - return _network.login() - } + ): LoginResult /** * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. * 然后重新启动并尝试登录 */ - @JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close fun reinitializeNetworkHandlerAsync( configuration: BotConfiguration, cause: Throwable? = null - ): Deferred = async { reinitializeNetworkHandler(configuration, cause) } + ): Deferred - /** - * Bot 联系人管理. - * - * @see Bot.contacts - */ - inner class ContactSystem internal constructor() { - val bot: Bot get() = this@Bot + // endregion - @UseExperimental(MiraiInternalAPI::class) - @Suppress("PropertyName") - internal val _groups = MutableContactList() - internal lateinit var groupsUpdater: Job - private val groupsLock = Mutex() - - val groups: ContactList = ContactList(_groups) - - @Suppress("PropertyName") - @UseExperimental(MiraiInternalAPI::class) - internal val _qqs = MutableContactList() //todo 实现群列表和好友列表获取 - internal lateinit var qqUpdaterJob: Job - private val qqsLock = Mutex() - - val qqs: ContactList = ContactList(_qqs) - - /** - * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个. - * - * 注: 这个方法是线程安全的 - */ - @UseExperimental(MiraiInternalAPI::class) - @JvmSynthetic - suspend fun getQQ(id: UInt): QQ = - if (_qqs.containsKey(id)) _qqs[id]!! - else qqsLock.withLock { - _qqs.getOrPut(id) { QQ(bot, id, coroutineContext) } - } - - // NO INLINE!! to help Java - @UseExperimental(MiraiInternalAPI::class) - suspend fun getQQ(id: Long): QQ = id.coerceAtLeastOrFail(0).toUInt().let { - if (_qqs.containsKey(it)) _qqs[it]!! - else qqsLock.withLock { - _qqs.getOrPut(it) { QQ(bot, it, coroutineContext) } - } - } - - /** - * 获取缓存的群对象. 若没有对应的缓存, 则会创建一个. - * - * 注: 这个方法是线程安全的 - */ - suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId()) - - /** - * 获取缓存的群对象. 若没有对应的缓存, 则会创建一个. - * - * 注: 这个方法是线程安全的 - */ - @UseExperimental(MiraiInternalAPI::class) - suspend fun getGroup(id: GroupId): Group = id.value.let { - if (_groups.containsKey(it)) _groups[it]!! - else groupsLock.withLock { - _groups.getOrPut(it) { Group(bot, id, coroutineContext) } - } - } - - // NO INLINE!! to help Java - @UseExperimental(MiraiInternalAPI::class) - suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let { - if (_groups.containsKey(it)) _groups[it]!! - else groupsLock.withLock { - _groups.getOrPut(it) { Group(bot, GroupId(it), coroutineContext) } - } - } - } - - @UseExperimental(MiraiInternalAPI::class) - fun close() { - _network.close() - this.coroutineContext.cancelChildren() - contacts._groups.clear() - contacts._qqs.clear() - } - - companion object { - @Suppress("ObjectPropertyName") - private val _instances: MutableList = mutableListOf() - private val instanceLock: Mutex = Mutex() - - private val instances: List get() = _instances - - suspend fun instanceWhose(qq: UInt) = instanceLock.withLock { - instances.first { it.qqAccount == qq } - } - - internal suspend fun addInstance(bot: Bot) = instanceLock.withLock { - _instances += bot - } - } + fun close() } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt new file mode 100644 index 000000000..5a9427630 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt @@ -0,0 +1,10 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai + +data class BotAccount( + val id: UInt, + val password: String +) { + constructor(id: Long, password: String) : this(id.toUInt(), password) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt index 3367ddbb1..d413b9eb0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt @@ -68,15 +68,15 @@ Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749 * @param remark 好友备注 */ @UseExperimental(ExperimentalContracts::class) -suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = bot.withSession { - return when (CanAddFriendPacket(bot.qqAccount, id, bot.sessionKey).sendAndExpect()) { +suspend fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = withSession { + return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect()) { is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED is CanAddFriendResponse.ReadyToAdd, is CanAddFriendResponse.RequireVerification -> { - val key = RequestFriendAdditionKeyPacket(bot.qqAccount, id, sessionKey).sendAndExpect().key - AddFriendPacket.RequestAdd(bot.qqAccount, id, sessionKey, message, remark, key).sendAndExpect() + val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect().key + AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect() AddFriendResult.WAITING_FOR_APPROVE } //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥 @@ -87,7 +87,7 @@ suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remar /*is CanAddFriendResponse.ReadyToAdd -> { // TODO: 2019/11/11 这不需要验证信息的情况 - //AddFriendPacket(bot.qqAccount, id, bot.sessionKey, ).sendAndExpectAsync().await() + //AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync().await() TODO() }*/ } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt index acfc697fb..075d716fc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt @@ -1,6 +1,6 @@ @file:JvmMultifileClass @file:JvmName("BotHelperKt") -@file:Suppress("unused", "EXPERIMENTAL_API_USAGE") +@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") package net.mamoe.mirai @@ -26,26 +26,8 @@ import kotlin.jvm.JvmOverloads */ //Contacts -suspend inline fun Bot.getQQ(@PositiveNumbers number: Long): QQ = this.contacts.getQQ(number) +suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id)) -suspend inline fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number) - -suspend inline fun Bot.getGroup(id: UInt): Group = this.contacts.getGroup(GroupId(id)) -suspend inline fun Bot.getGroup(@PositiveNumbers id: Long): Group = - this.contacts.getGroup(GroupId(id.coerceAtLeastOrFail(0).toUInt())) - -suspend inline fun Bot.getGroup(id: GroupId): Group = this.contacts.getGroup(id) -suspend inline fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.getGroup(internalId) - -/** - * 取得群列表 - */ -inline val Bot.groups: ContactList get() = this.contacts.groups - -/** - * 取得好友列表 - */ -inline val Bot.qqs: ContactList get() = this.contacts.qqs /** * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. @@ -70,7 +52,7 @@ internal suspend inline fun Bot.sendPacket(packet: OutgoingPacket) = * 使用在默认配置基础上修改的配置进行登录 */ @UseExperimental(ExperimentalContracts::class) -suspend inline fun Bot.login(noinline configuration: BotConfiguration.() -> Unit): LoginResult { +suspend inline fun Bot.login(configuration: BotConfiguration.() -> Unit): LoginResult { contract { callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) } @@ -91,7 +73,7 @@ suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() } * 使用在默认配置基础上修改的配置进行登录, 返回 [this] */ @UseExperimental(ExperimentalContracts::class) -suspend inline fun Bot.alsoLogin(noinline configuration: BotConfiguration.() -> Unit): Bot { +suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bot { contract { callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) } @@ -108,14 +90,6 @@ suspend inline fun Bot.alsoLogin(message: String): Bot { } } -/** - * 添加好友 - */ -@UseExperimental(ExperimentalContracts::class) -@JvmOverloads -suspend inline fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = - contacts.addFriend(id, message, remark) - /** * 取得机器人的 QQ 号 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt new file mode 100644 index 000000000..2efa4d328 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -0,0 +1,165 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai + +import kotlinx.coroutines.* +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.contact.internal.Group +import net.mamoe.mirai.contact.internal.QQ +import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler +import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId +import net.mamoe.mirai.network.protocol.tim.packet.action.GroupNotFound +import net.mamoe.mirai.network.protocol.tim.packet.action.GroupPacket +import net.mamoe.mirai.network.protocol.tim.packet.action.RawGroupInfo +import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult +import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess +import net.mamoe.mirai.network.qqAccount +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.internal.PositiveNumbers +import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail +import net.mamoe.mirai.utils.io.inline +import net.mamoe.mirai.utils.io.logStacktrace +import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSynthetic + +@PublishedApi +internal class BotImpl @PublishedApi internal constructor( + override val account: BotAccount, + override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"), + context: CoroutineContext +) : Bot, CoroutineScope { + private val supervisorJob = SupervisorJob(context[Job]) + override val coroutineContext: CoroutineContext = + context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") } + + init { + launch { + instances.addLast(this@BotImpl) + } + } + + companion object { + init { + KnownPacketId.values() /* load id classes */ + } + + @PublishedApi + internal val instances: LockFreeLinkedList = LockFreeLinkedList() + + inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block) + + fun instanceWhose(qq: UInt): Bot { + instances.forEach { + if (it.qqAccount == qq) { + return it + } + } + throw NoSuchElementException() + } + } + + override fun toString(): String = "Bot(${account.id})" + + // region network + + override val network: BotNetworkHandler<*> get() = _network + private lateinit var _network: BotNetworkHandler<*> + + override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close + configuration: BotConfiguration, + cause: Throwable? + ): Job = launch { + repeat(configuration.reconnectionRetryTimes) { + if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) { + logger.info("Reconnected successfully") + return@launch + } else { + delay(configuration.reconnectPeriodMillis) + } + } + } + + override suspend fun reinitializeNetworkHandler( + configuration: BotConfiguration, + cause: Throwable? + ): LoginResult { + logger.info("BotAccount: ${qqAccount.toLong()}") + logger.info("Initializing BotNetworkHandler") + try { + if (::_network.isInitialized) { + _network.close(cause) + } + } catch (e: Exception) { + logger.error("Cannot close network handler", e) + } + _network = TIMBotNetworkHandler(this.coroutineContext + configuration, this) + + return _network.login() + } + + override fun reinitializeNetworkHandlerAsync( + configuration: BotConfiguration, + cause: Throwable? + ): Deferred = async { reinitializeNetworkHandler(configuration, cause) } + + // endregion + + // region contacts + override val groups: ContactList = ContactList(LockFreeLinkedList()) + + override val qqs: ContactList = ContactList(LockFreeLinkedList()) + + /** + * 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个. + */ + @UseExperimental(MiraiInternalAPI::class) + @JvmSynthetic + override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) } + + // NO INLINE!! to help Java + @UseExperimental(MiraiInternalAPI::class) + override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt()) + + override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId()) + + @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class) + override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline { + val info: RawGroupInfo = try { + when (val response = + withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect() }) { + is RawGroupInfo -> response + is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}") + else -> assertUnreachable() + } + } catch (e: Exception) { + throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e) + } + + return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) } + } + + // NO INLINE!! to help Java + @UseExperimental(MiraiInternalAPI::class) + override suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let { + groups.delegate.getOrNull(it) ?: inline { + val info: RawGroupInfo = try { + withSession { GroupPacket.QueryGroupInfo(qqAccount, GroupId(it).toInternalId(), sessionKey).sendAndExpect() } + } catch (e: Exception) { + e.logStacktrace() + error("Cannot obtain group info for id ${it.toLong()}") + } + + return groups.delegate.getOrAdd(it) { Group(this, GroupId(it), info, coroutineContext) } + } + } + // endregion + + @UseExperimental(MiraiInternalAPI::class) + override fun close() { + _network.close() + this.supervisorJob.complete() + groups.delegate.clear() + qqs.delegate.clear() + } +} 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 ffe71ee8f..b3e3b420e 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 @@ -8,7 +8,9 @@ import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.chain import net.mamoe.mirai.message.singleChain import net.mamoe.mirai.network.BotSession +import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.joinToString import net.mamoe.mirai.withSession import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind @@ -54,55 +56,3 @@ inline fun Contact.withSession(block: BotSession.() -> R): R { } return bot.withSession(block) } - -/** - * 只读联系人列表 - */ -@UseExperimental(MiraiInternalAPI::class) -inline class ContactList(internal val mutable: MutableContactList) : Map { - /** - * ID 列表的字符串表示. - * 如: - * ``` - * [123456, 321654, 123654] - * ``` - */ - val idContentString: String get() = this.keys.joinToString(prefix = "[", postfix = "]") { it.toLong().toString() } - - override fun toString(): String = mutable.toString() - - - // TODO: 2019/12/2 应该使用属性代理, 但属性代理会导致 UInt 内联错误. 等待 kotlin 修复后替换 - - override val size: Int get() = mutable.size - override fun containsKey(key: UInt): Boolean = mutable.containsKey(key) - override fun containsValue(value: C): Boolean = mutable.containsValue(value) - override fun get(key: UInt): C? = mutable[key] - override fun isEmpty(): Boolean = mutable.isEmpty() - override val entries: MutableSet> get() = mutable.entries - override val keys: MutableSet get() = mutable.keys - override val values: MutableCollection get() = mutable.values -} - -/** - * 可修改联系人列表. 只会在内部使用. - */ -@MiraiInternalAPI -inline class MutableContactList(private val delegate: MutableMap = linkedMapOf()) : MutableMap { - override fun toString(): String = asIterable().joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") { it.value.toString() } - - // TODO: 2019/12/2 应该使用属性代理, 但属性代理会导致 UInt 内联错误. 等待 kotlin 修复后替换 - - override val size: Int get() = delegate.size - override fun containsKey(key: UInt): Boolean = delegate.containsKey(key) - override fun containsValue(value: C): Boolean = delegate.containsValue(value) - override fun get(key: UInt): C? = delegate[key] - override fun isEmpty(): Boolean = delegate.isEmpty() - override val entries: MutableSet> get() = delegate.entries - override val keys: MutableSet get() = delegate.keys - override val values: MutableCollection get() = delegate.values - override fun clear() = delegate.clear() - override fun put(key: UInt, value: C): C? = delegate.put(key, value) - override fun putAll(from: Map) = delegate.putAll(from) - override fun remove(key: UInt): C? = delegate.remove(key) -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt new file mode 100644 index 000000000..46ac648d5 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt @@ -0,0 +1,48 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.contact + +import net.mamoe.mirai.utils.LockFreeLinkedList +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.joinToString + + +/** + * 只读联系人列表, lock-free 实现 + */ +@UseExperimental(MiraiInternalAPI::class) +@Suppress("unused") +class ContactList(@PublishedApi internal val delegate: LockFreeLinkedList) { + /** + * ID 列表的字符串表示. + * 如: + * ``` + * [123456, 321654, 123654] + * ``` + */ + val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]" + + operator fun get(id: UInt): C = delegate[id] + fun getOrNull(id: UInt): C? = delegate.getOrNull(id) + fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null + + val size: Int get() = delegate.size + operator fun contains(element: C): Boolean = delegate.contains(element) + fun containsAll(elements: Collection): Boolean = elements.all { contains(it) } + fun isEmpty(): Boolean = delegate.isEmpty() + inline fun forEach(block: (C) -> Unit) = delegate.forEach(block) + + override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") +} + +operator fun LockFreeLinkedList.get(id: UInt): C { + forEach { if (it.id == id) return it } + throw NoSuchElementException() +} + +fun LockFreeLinkedList.getOrNull(id: UInt): C? { + forEach { if (it.id == id) return it } + return null +} + +fun LockFreeLinkedList.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt index de6cc8a7c..e383d3bb8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt @@ -2,101 +2,50 @@ package net.mamoe.mirai.contact -fun GroupId.toInternalId(): GroupInternalId {//求你别出错 - val left: Long = this.value.toString().let { - if (it.length < 6) { - return GroupInternalId(this.value) - } - it.substring(0, it.length - 6).toLong() - } - val right: Long = this.value.toString().let { - it.substring(it.length - 6).toLong() +import kotlin.math.pow + + +@Suppress("ObjectPropertyName") +private val `10EXP6` = 10.0.pow(6).toUInt() + + +fun GroupId.toInternalId(): GroupInternalId { + if (this.value <= `10EXP6`) { + return GroupInternalId(this.value) } + val left: Long = this.value.toString().dropLast(6).toLong() + val right: Long = this.value.toString().takeLast(6).toLong() return GroupInternalId( when (left) { - in 1..10 -> { - ((left + 202).toString() + right.toString()).toUInt() - } - in 11..19 -> { - ((left + 469).toString() + right.toString()).toUInt() - } - in 20..66 -> { - ((left + 208).toString() + right.toString()).toUInt() - } - in 67..156 -> { - ((left + 1943).toString() + right.toString()).toUInt() - } - in 157..209 -> { - ((left + 199).toString() + right.toString()).toUInt() - } - in 210..309 -> { - ((left + 389).toString() + right.toString()).toUInt() - } - in 310..499 -> { - ((left + 349).toString() + right.toString()).toUInt() - } + in 1..10 -> ((left + 202).toString() + right.toString()).toUInt() + in 11..19 -> ((left + 469).toString() + right.toString()).toUInt() + in 20..66 -> ((left + 208).toString() + right.toString()).toUInt() + in 67..156 -> ((left + 1943).toString() + right.toString()).toUInt() + in 157..209 -> ((left + 199).toString() + right.toString()).toUInt() + in 210..309 -> ((left + 389).toString() + right.toString()).toUInt() + in 310..499 -> ((left + 349).toString() + right.toString()).toUInt() else -> this.value } ) } -fun GroupInternalId.toId(): GroupId = with(value) { - //求你别出错 - var left: UInt = this.toString().let { - if (it.length < 6) { - return GroupId(value) - } - it.substring(0 until it.length - 6).toUInt() +fun GroupInternalId.toId(): GroupId = with(value.toString()) { + if (value < `10EXP6`) { + return GroupId(value) } + val left: UInt = this.dropLast(6).toUInt() - return GroupId(when (left.toInt()) { - in 203..212 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 6).toUInt() - } - ((left - 202u).toString() + right.toString()).toUInt() + return GroupId( + when (left.toInt()) { + in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt() + in 480..488 -> ((left - 469u).toString() + this.takeLast(6).toInt().toString()).toUInt() + in 2100..2146 -> ((left.toString().take(3).toUInt() - 208u).toString() + this.takeLast(7).toInt().toString()).toUInt() + in 2010..2099 -> ((left - 1943u).toString() + this.takeLast(6).toInt().toString()).toUInt() + in 2147..2199 -> ((left.toString().take(3).toUInt() - 199u).toString() + this.takeLast(7).toInt().toString()).toUInt() + in 4100..4199 -> ((left.toString().take(3).toUInt() - 389u).toString() + this.takeLast(7).toInt().toString()).toUInt() + in 3800..3989 -> ((left.toString().take(3).toUInt() - 349u).toString() + this.takeLast(7).toInt().toString()).toUInt() + else -> value } - in 480..488 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 6).toUInt() - } - ((left - 469u).toString() + right.toString()).toUInt() - } - in 2100..2146 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 7).toUInt() - } - left = left.toString().substring(0 until 3).toUInt() - ((left - 208u).toString() + right.toString()).toUInt() - } - in 2010..2099 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 6).toUInt() - } - ((left - 1943u).toString() + right.toString()).toUInt() - } - in 2147..2199 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 7).toUInt() - } - left = left.toString().substring(0 until 3).toUInt() - ((left - 199u).toString() + right.toString()).toUInt() - } - in 4100..4199 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 7).toUInt() - } - left = left.toString().substring(0 until 3).toUInt() - ((left - 389u).toString() + right.toString()).toUInt() - } - in 3800..3989 -> { - val right: UInt = this.toString().let { - it.substring(it.length - 7).toUInt() - } - left = left.toString().substring(0 until 3).toUInt() - ((left - 349u).toString() + right.toString()).toUInt() - } - else -> value - }) + ) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt index e9350c0c2..4055c5e83 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt @@ -3,6 +3,8 @@ package net.mamoe.mirai.contact.internal import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.data.Profile @@ -15,9 +17,7 @@ import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.network.sessionKey import net.mamoe.mirai.qqAccount import net.mamoe.mirai.sendPacket -import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.logStacktrace import net.mamoe.mirai.withSession import kotlin.coroutines.CoroutineContext @@ -35,15 +35,12 @@ internal sealed class ContactImpl : Contact { */ @Suppress("FunctionName") @PublishedApi -internal suspend fun Group(bot: Bot, groupId: GroupId, context: CoroutineContext): Group { - val info: RawGroupInfo = try { - bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, groupId.toInternalId(), sessionKey).sendAndExpect() } - } catch (e: Exception) { - e.logStacktrace() - error("Cannot obtain group info for id ${groupId.value}") +internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group = + GroupImpl(bot, groupId, context).apply { + this@apply.info = info.parseBy(this@apply) + launch { startUpdater() } } - return GroupImpl(bot, groupId, context).apply { this.info = info.parseBy(this); startUpdater() } -} + @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) : @@ -52,14 +49,15 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr override val internalId = GroupId(id).toInternalId() internal lateinit var info: GroupInfo + internal lateinit var initialInfoJob: Job + override val owner: Member get() = info.owner override val name: String get() = info.name override val announcement: String get() = info.announcement override val members: ContactList get() = info.members override fun getMember(id: UInt): Member = - if (members.containsKey(id)) members[id]!! - else throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}") + members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}") override suspend fun sendMessage(message: MessageChain) { bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message)) @@ -76,20 +74,18 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr @UseExperimental(MiraiInternalAPI::class) override suspend fun startUpdater() { subscribeAlways { - // FIXME: 2019/11/29 非线程安全!! - members.mutable[it.member.id] = it.member + members.delegate.addLast(it.member) } subscribeAlways { - // FIXME: 2019/11/29 非线程安全!! - members.mutable.remove(it.member.id) + members.delegate.remove(it.member) } } override fun toString(): String = "Group(${this.id})" } -@Suppress("FunctionName") -suspend inline fun QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { startUpdater() } +@Suppress("FunctionName", "NOTHING_TO_INLINE") +internal inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } } @PublishedApi internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) : @@ -118,9 +114,9 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot: override fun toString(): String = "QQ(${this.id})" } -@Suppress("FunctionName") -suspend inline fun Member(delegate: QQ, group: Group, permission: MemberPermission, coroutineContext: CoroutineContext): Member = - MemberImpl(delegate, group, permission, coroutineContext).apply { startUpdater() } +@Suppress("FunctionName", "NOTHING_TO_INLINE") +internal inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member = + MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } } /** * 群成员 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt index 8a1ac5850..b06024183 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt @@ -1,4 +1,4 @@ -@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") +@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE") package net.mamoe.mirai.network @@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor( */ val gtk: Int get() = _gtk - suspend inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt()) - suspend inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) - suspend inline fun UInt.qq(): QQ = bot.getQQ(this) + inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt()) + inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) + inline fun UInt.qq(): QQ = bot.getQQ(this) suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt()) suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0)) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt index dc925f735..682b8397d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/TIMBotNetworkHandler.kt @@ -226,7 +226,9 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou return if (!packet::class.annotations.filterIsInstance().any()) { - bot.logger.verbose("Packet received: $packet") + if ((packet as? BroadcastControllable)?.shouldBroadcast != false) { + bot.logger.verbose("Packet received: $packet") + } } when (packet) { @@ -239,7 +241,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) } .also { temporaryPacketHandlers.removeAll(it) } }.forEach { - it.doReceiveWithoutExceptions(packet) + it.doReceiveCatchingExceptions(packet) } if (factory is SessionPacketFactory<*>) { @@ -286,7 +288,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou it::class.annotations.filterIsInstance().any() } }?.let { - bot.logger.verbose("Packet sent: ${it.packetId}") + bot.logger.verbose("Packet sent: ${it.name}") } PacketSentEvent(bot, packet).broadcast() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/TemporaryPacketHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/TemporaryPacketHandler.kt index b8512e040..5a8aab6c5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/TemporaryPacketHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/handler/TemporaryPacketHandler.kt @@ -60,7 +60,7 @@ internal class TemporaryPacketHandler

( internal inline fun filter(session: BotSession, packet: Packet, sequenceId: UShort): Boolean = expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) sequenceId == toSend.sequenceId else true - internal suspend inline fun doReceiveWithoutExceptions(packet: Packet) { + internal suspend inline fun doReceiveCatchingExceptions(packet: Packet) { @Suppress("UNCHECKED_CAST") val ret = try { withContext(callerContext) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Annotations.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Annotations.kt index 8760ae58f..10348acd5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Annotations.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Annotations.kt @@ -2,19 +2,6 @@ package net.mamoe.mirai.network.protocol.tim.packet - -/** - * 包 ID. 除特殊外, [PacketFactory] 都需要这个注解来指定包 ID. - */ -@MustBeDocumented -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.CLASS) -internal annotation class AnnotatedId( // 注解无法在 JS 平台使用, 但现在暂不需要考虑 JS - val id: KnownPacketId -) - -internal inline val AnnotatedId.value: UShort get() = id.value - /** * 包的最后一次修改时间, 和分析时使用的 TIM 版本 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt index 0fed254bb..0b55041ad 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt @@ -24,7 +24,7 @@ internal class OutgoingPacket( val sequenceId: UShort, internal val delegate: ByteReadPacket ) : Packet { - private val name: String by lazy { + val name: String by lazy { name ?: packetId.toString() } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketFactory.kt index 398213572..ae7b6df3d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketFactory.kt @@ -10,7 +10,6 @@ import kotlinx.io.pool.useInstance import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.read @@ -26,20 +25,13 @@ import net.mamoe.mirai.utils.readProtoMap */ internal abstract class PacketFactory(val decrypterType: DecrypterType) { - /** - * 2 Ubyte. - * 读取注解 [AnnotatedId] - */ - private val annotatedId: AnnotatedId - get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId) - ?: error("Annotation AnnotatedId not found for class ${this::class.simpleName}") + @Suppress("PropertyName") + internal var _id: PacketId = NullPacketId - - // TODO: 2019/11/22 修改 包 ID 为参数 /** * 包 ID. */ - open val id: PacketId by lazy { annotatedId.id } + open val id: PacketId get() = _id /** * **解码**服务器的回复数据包 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketId.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketId.kt index 028cc77ad..95ede668c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketId.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/PacketId.kt @@ -55,35 +55,35 @@ internal inline class IgnoredPacketId constructor(override val value: UShort) : * 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id */ @Suppress("unused") -internal enum class KnownPacketId(override inline val value: UShort, override inline val factory: PacketFactory<*, *>) : +internal enum class KnownPacketId(override val value: UShort, override val factory: PacketFactory<*, *>) : PacketId { - inline TOUCH(0x0825u, TouchPacket), - inline SESSION_KEY(0x0828u, RequestSessionPacket), - inline LOGIN(0x0836u, SubmitPasswordPacket), - inline CAPTCHA(0x00BAu, CaptchaPacket), - inline SERVER_EVENT_1(0x00CEu, EventPacketFactory), - inline SERVER_EVENT_2(0x0017u, EventPacketFactory), - inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket), - inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket), + TOUCH(0x0825u, TouchPacket), + SESSION_KEY(0x0828u, RequestSessionPacket), + LOGIN(0x0836u, SubmitPasswordPacket), + CAPTCHA(0x00BAu, CaptchaPacket), + SERVER_EVENT_1(0x00CEu, EventPacketFactory), + SERVER_EVENT_2(0x0017u, EventPacketFactory), + FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket), + CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket), - inline HEARTBEAT(0x0058u, HeartbeatPacket), - inline S_KEY(0x001Du, RequestSKeyPacket), - inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket), - inline GROUP_PACKET(0x0002u, GroupPacket), - inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket), - inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket), - inline ADD_FRIEND(0x00A8u, AddFriendPacket), - inline REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket), - inline GROUP_IMAGE_ID(0x0388u, GroupImagePacket), - inline FRIEND_IMAGE_ID(0x0352u, FriendImagePacket), + HEARTBEAT(0x0058u, HeartbeatPacket), + S_KEY(0x001Du, RequestSKeyPacket), + ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket), + GROUP_PACKET(0x0002u, GroupPacket), + SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket), + CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket), + ADD_FRIEND(0x00A8u, AddFriendPacket), + REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket), + GROUP_IMAGE_ID(0x0388u, GroupImagePacket), + FRIEND_IMAGE_ID(0x0352u, FriendImagePacket), - inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket), - inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket), - inline QUERY_NICKNAME(0x0126u, QueryNicknamePacket), + REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket), + REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket), + QUERY_NICKNAME(0x0126u, QueryNicknamePacket), - inline QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket), + QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket), - inline QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket) + QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket) // 031F 查询 "新朋友" 记录 @@ -92,5 +92,9 @@ internal enum class KnownPacketId(override inline val value: UShort, override in ; + init { + factory._id = this + } + override fun toString(): String = (factory::class.simpleName ?: this.name) + "(${value.toUHexString()})" } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/AddContact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/AddContact.kt index 02114cf40..7bdb91d62 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/AddContact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/AddContact.kt @@ -19,7 +19,6 @@ import net.mamoe.mirai.withSession * - 昵称 * - 共同群内的群名片 */ -@AnnotatedId(KnownPacketId.QUERY_PREVIOUS_NAME) @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") internal object QueryPreviousNamePacket : SessionPacketFactory() { operator fun invoke( @@ -77,7 +76,6 @@ class PreviousNameList( * * @author Him188moe */ -@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND) @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") internal object CanAddFriendPacket : SessionPacketFactory() { operator fun invoke( @@ -155,7 +153,6 @@ inline class FriendAdditionKey(val value: IoBuffer) /** * 请求一个 32 位 Key, 在添加好友时发出 */ -@AnnotatedId(KnownPacketId.REQUEST_FRIEND_ADDITION_KEY) @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") internal object RequestFriendAdditionKeyPacket : SessionPacketFactory() { operator fun invoke( @@ -182,7 +179,6 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory() { @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @Suppress("FunctionName") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendImage.kt index 59dadf377..24b14083d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendImage.kt @@ -92,7 +92,6 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse { * - 服务器已经存有这个图片 * - 服务器未存有, 返回一个 key 用于客户端上传 */ -@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID) @PacketVersion(date = "2019.11.16", timVersion = "2.3.2 (21173)") internal object FriendImagePacket : SessionPacketFactory() { @Suppress("FunctionName") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendList.kt index 4ad460f88..32c762e30 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/FriendList.kt @@ -1,5 +1,13 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.network.protocol.tim.packet.action +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.protocol.tim.packet.Packet +import net.mamoe.mirai.network.protocol.tim.packet.PacketId +import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory + // 0001 // 已确认 查好友列表的列表 @@ -15,6 +23,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.action // 00 00 +internal inline class FriendListList(val delegate: List): Packet + +internal object QueryFriendListListPacket : SessionPacketFactory() { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendList { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} // 0134 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GradeInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GradeInfo.kt index 61ec3289b..b9654120f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GradeInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GradeInfo.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ * * @author Him188moe */ -@AnnotatedId(KnownPacketId.ACCOUNT_INFO) internal object RequestAccountInfoPacket : SessionPacketFactory() { operator fun invoke( qq: UInt, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupImage.kt index f4170e16d..987655e07 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupImage.kt @@ -99,7 +99,6 @@ internal class ImageUploadInfo( /** * 获取 Image Id 和上传用的一个 uKey */ -@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID) @PacketVersion(date = "2019.11.22", timVersion = "2.3.2 (21173)") internal object GroupImagePacket : SessionPacketFactory() { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupPacket.kt index e52d07c82..bbaab73ff 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/GroupPacket.kt @@ -10,6 +10,7 @@ import net.mamoe.mirai.message.internal.toPacket import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.packet.* +import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.withSession @@ -36,6 +37,10 @@ class GroupInfo( "GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}" } +internal object GroupNotFound : GroupPacket.InfoResponse { + override fun toString(): String = "GroupPacket.InfoResponse.GroupNotFound" +} + internal data class RawGroupInfo( val group: UInt, val owner: UInt, @@ -45,17 +50,20 @@ internal data class RawGroupInfo( * 含群主 */ val members: Map -) : GroupPacket.GroupPacketResponse { +) : GroupPacket.InfoResponse { + @UseExperimental(MiraiInternalAPI::class) - suspend inline fun parseBy(group: Group): GroupInfo = group.bot.withSession { + fun parseBy(group: Group): GroupInfo = group.bot.withSession { + val memberList = LockFreeLinkedList() + members.forEach { entry: Map.Entry -> + memberList.addLast(entry.key.qq().let { group.Member(it, entry.value, it.coroutineContext) }) + } return GroupInfo( group, - this@RawGroupInfo.owner.qq().let { Member(it, group, MemberPermission.OWNER, it.coroutineContext) }, + this@RawGroupInfo.owner.qq().let { group.Member(it, MemberPermission.OWNER, it.coroutineContext) }, this@RawGroupInfo.name, this@RawGroupInfo.announcement, - ContactList(this@RawGroupInfo.members.mapValuesTo(MutableContactList()) { entry: Map.Entry -> - entry.key.qq().let { Member(it,group, entry.value, it.coroutineContext) } - }) + ContactList(memberList) ) } } @@ -71,7 +79,6 @@ inline class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, G } @Suppress("FunctionName") -@AnnotatedId(KnownPacketId.GROUP_PACKET) internal object GroupPacket : SessionPacketFactory() { @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") fun Message( @@ -159,6 +166,8 @@ internal object GroupPacket : SessionPacketFactory ): GroupPacketResponse { - return when (readUByte().toUInt()) { + return when (val packetType = readUByte().toUInt()) { 0x2Au -> MessageResponse 0x7Eu -> MuteResponse // 成功: 7E 00 22 96 29 7B; @@ -179,180 +188,185 @@ internal object GroupPacket : SessionPacketFactory { - discardExact(1) // 00 - discardExact(4) // group internal id - val group = readUInt() // group id + when (val flag = readByte().toInt()) { + 0x02 -> GroupNotFound + 0x00 -> debugPrintIfFail("解析群信息") { + discardExact(4) // group internal id + val group = readUInt() // group id - discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40 - val owner = readUInt() - discardExact(22) - val groupName = readUByteLVString() + discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40 + val owner = readUInt() + discardExact(22) + val groupName = readUByteLVString() - discardExact(readUByte()) // 00 - discardExact(readUByte()) // 00 - val announcement = readUByteLVString() - discardExact(readUByte()) // 00 - discardExact(readUByte()) // 00 - discardExact(readUByte()) // 38 ... 未知 - discardExact(readUByte()) // 00 - discardExact(readUByte()) // 0A ... + discardExact(readUByte()) // 00 + discardExact(readUByte()) // 00 + val announcement = readUByteLVString() + discardExact(readUByte()) // 00 + discardExact(readUByte()) // 00 + discardExact(readUByte()) // 38 ... 未知 + discardExact(readUByte()) // 00 + discardExact(readUByte()) // 0A ... - discardExact(38) + discardExact(38) - val stop = readUInt() // 标记读取群成员的结束 - discardExact(1) // 00 - val members = mutableMapOf() - do { - val qq = readUInt() - val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态 - if (qq == owner) { - continue + val stop = readUInt() // 标记读取群成员的结束 + discardExact(1) // 00 + val members = mutableMapOf() + do { + val qq = readUInt() + val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态 + if (qq == owner) { + continue + } + + val permission = when (status.takeLowestOneBit()) { + 1 -> MemberPermission.ADMINISTRATOR + else -> MemberPermission.MEMBER + } + members[qq] = permission + } while (qq != stop && remaining != 0L) + members[owner] = MemberPermission.OWNER + return RawGroupInfo(group, owner, groupName, announcement, members) + /* + * 群 Mirai + * + * 00 00 00 03 01 41 00 04 01 40 23 04 40 + * B1 89 BE 09 群主 + * + * 02 00 00 00 00 00 00 00 00 00 21 00 C8 01 + * 00 00 00 01 00 00 + * 00 2D + * + * 06 4D 69 72 61 69 20 群名 + * 00 + * 00 + * 00 + * 00 + * 00 + * 38 87 5F D8 E8 D4 E9 79 73 8A A4 21 1C 3E 2C 43 D0 23 55 53 49 D3 1C DB F6 1F 84 59 77 66 DA 9C D7 26 0F E3 BD E1 F2 B9 29 D1 F6 97 1C 42 5E B0 AF 09 51 72 DA 03 37 AB 65 + * 00 + * 0A 00 00 00 00 06 00 03 00 02 00 + * 01 00 04 00 04 00 00 00 01 00 05 00 04 5D 90 A7 25 00 06 00 04 04 08 00 00 00 07 00 04 00 00 05 80 00 09 00 01 01 + * B1 89 BE 09 00 + * 3E 03 3F A2 00 01 + * 48 76 54 DC 00 00 + * 76 E4 B8 DD 00 00 + * 89 1A 5E AC 00 00 + * B1 89 BE 09 00 00 + */ + + /* + * 群 XkWhXi + * + * 00 00 00 03 01 41 00 04 01 40 21 04 40 + * 3E 03 3F A2 群主 + * + * 02 00 00 00 01 00 00 00 00 27 1A 00 C8 01 + * 00 00 00 01 00 00 + * F3 C8 + * + * 06 58 6B 57 68 58 69 + * 00 + * 00 + * 3B E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 0A E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 + * 00 + * 00 + * 38 EB 3B A5 90 AC E3 70 1F 42 51 B4 72 81 C8 F5 5A D8 80 69 B6 76 AD A4 AA CC 6A 17 4C 79 81 FF 82 04 BA 13 CE 28 DA 6C 3F 41 77 C0 77 40 B5 87 8E EE 29 20 65 FC 2D FF 63 + * 00 + * 0A 00 00 00 00 06 00 03 00 02 00 + * 01 00 04 00 04 00 00 00 05 00 05 00 04 57 94 6F 41 00 06 00 04 04 08 00 10 00 07 00 04 00 00 04 04 00 09 00 01 00 + * B1 89 BE 09 00 2D 5C 53 A6 00 01 2F 9B 1C F2 00 00 35 49 95 D1 00 01 3B FA 06 9F 00 00 3E 03 3F A2 00 00 42 C4 32 63 00 01 59 17 3E 05 00 01 6A 89 3E 3E 00 00 6D D7 4E CA 00 00 76 E4 B8 DD 00 00 7C BB 60 3C 00 01 7C BC D3 C1 00 01 87 73 86 9D 00 00 90 19 72 65 00 00 97 30 9A 6B 00 00 9C B1 E5 55 00 01 B1 89 BE 09 00 01 + */ + + /* + * 群 20秃顶28火葬30重生异世 + * + * + */ + + /* + * 群 Big convene' (与上面两个来自不同 bot) + * + * 00 00 00 03 01 01 00 04 01 00 80 01 40 + * 6C 18 F5 DA 群主 + * + * 02 00 00 27 1B 00 00 00 00 27 1B 01 F4 01 + * 00 00 00 01 00 00 + * 0F 1F + * + * 0C 42 69 67 20 63 6F 6E 76 65 6E 65 27 00 群名 + * 00 96 E6 AF 95 E4 B8 9A E4 BA 86 EF BC 8C E5 B8 8C E6 9C 9B E5 A4 A7 E5 AE B6 E8 83 BD E5 A4 9F E5 83 8F E4 BB A5 E5 89 8D E9 82 A3 E6 A0 B7 E5 BC 80 E5 BF 83 EF BC 8C E5 AD A6 E4 B9 A0 E8 BF 9B E6 AD A5 EF BC 8C E5 A4 A9 E5 A4 A9 E5 BF AB E4 B9 90 E3 80 82 E6 AD A4 E7 BE A4 E7 A6 81 E6 AD A2 E9 AA 82 E4 BA BA EF BC 8C E5 88 B7 E5 B1 8F E6 9A B4 E5 8A 9B EF BC 8C E8 BF 9D E8 A7 84 E8 80 85 E7 A6 81 E8 A8 80 EF BC 8C E4 B8 A5 E9 87 8D E8 80 85 E5 B0 B1 + * 76 E8 BF 9B E7 BE A4 E6 97 B6 EF BC 8C E8 AF B7 E4 BF AE E6 94 B9 E6 AD A3 E7 A1 AE E5 A7 93 E5 90 8D E3 80 82 E4 B8 8D E8 83 BD 54 E5 90 8C E5 AD A6 EF BC 8C E5 A4 AA E8 BF 87 E5 88 86 E7 9A 84 54 21 28 E4 BA 92 E8 B5 9E E7 BE A4 EF BC 8C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF E8 81 8A E5 A4 A9 E8 80 85 E5 8F AF E4 BB A5 E4 BA 92 E8 B5 9E E5 AF B9 E6 96 B9 + * 00 38 D9 FD F5 21 A6 1F 8D 61 37 A1 7A 92 91 2A 2C 71 46 A9 B9 1C 45 EB 38 74 4A 74 EA 77 7D 14 DB 12 D0 B0 09 C2 AA 22 16 F1 D0 B9 97 21 F0 5A A0 06 59 A7 3B 2F 32 D2 B8 E3 + * 00 0F 00 00 00 00 06 00 03 00 02 01 01 00 04 00 04 00 00 00 15 00 05 00 04 52 7C C5 7C 00 06 00 04 00 00 00 20 00 07 00 04 00 00 00 00 00 09 00 01 00 + * + * C5 15 BE BE 00 ???为啥这个只有一个呢 + * 1C ED 9F 9B 00 00 + * 26 D0 E1 3A 00 00 + * 2D 5C 53 A6 00 01 自己 管理员 + * 2D BD 28 D2 00 00 + * 2E 94 76 3E 00 00 + * 35 F3 BC F2 00 00 + * 37 D6 91 AB 00 00 + * 3A 60 1C 3E 00 80 10000000 群员, 好友 + * 3A 86 EA A3 00 48 01001000 群员 手机在线 + * 3D 7F E7 70 00 00 + * 3E 03 3F A2 00 09 00001001 好友, 特别关心, TIM PC 在线, 管理员 + * 41 47 0C DD 00 40 01000000 群员, 离线 + * 41 B6 32 A8 00 80 + * 44 C8 DA 23 00 00 + * 45 3E 1B 6A 00 80 10000000 群员 手机在线 + * 45 C6 59 E9 00 C0 群员 + * 4A BD C6 F9 00 00 + * 4C 67 45 E8 00 00 + * 4E AD C2 C2 00 80 + * 4F A0 F7 EC 00 80 + * 50 CB 11 E8 00 00 + * 58 22 21 90 00 00 + * 59 17 3E 05 00 01 管理员 好友 + * 5E 74 48 D9 00 00 + * 5E A2 B5 88 00 00 + * 66 A1 32 9B 00 40 + * 68 07 29 0A 00 00 + * 68 0F EF 4F 00 00 + * 69 8B 14 F3 00 80 + * 6A A5 27 4E 00 00 + * 6C 11 A0 89 00 81 10000001 管理员 + * 6C 18 F5 DA 00 08 群主 + * 6C 21 F8 E2 00 01 管理员 + * 71 F8 F5 18 00 00 + * 72 0B CC B6 00 00 + * 75 53 38 DF 00 00 + * 7A A1 8B 82 00 00 + * 7C 8C 1D 1B 00 00 + * 7C BC D3 C1 00 00 + * 84 2D B8 5F 00 00 + * 88 4C 33 76 00 00 + * 8C C8 0D 43 00 00 + * 90 B8 65 22 00 00 + * 91 54 89 E9 00 00 + * 9C E6 93 A5 00 01 管理员 + * 9D 59 6A 36 00 00 + * 9D 63 81 5C 00 00 + * 9E 31 AF AC 00 00 + * 9E 69 86 25 00 80 + * A1 FD CA 2D 00 00 + * A5 22 5C 48 00 00 + * A5 F2 9A B7 00 00 + * AF 25 74 9E 00 01 + * B1 50 24 00 00 00 + * B2 BD 81 A9 00 00 + * B5 0E B3 DD 00 00 + * B9 BF 0D BC 00 00 + * C5 15 BE BE 00 00 + */ } - - val permission = when (status.takeLowestOneBit()) { - 1 -> MemberPermission.ADMINISTRATOR - else -> MemberPermission.MEMBER - } - members[qq] = permission - } while (qq != stop && remaining != 0L) - members[owner] = MemberPermission.OWNER - return RawGroupInfo(group, owner, groupName, announcement, members) - /* - * 群 Mirai - * - * 00 00 00 03 01 41 00 04 01 40 23 04 40 - * B1 89 BE 09 群主 - * - * 02 00 00 00 00 00 00 00 00 00 21 00 C8 01 - * 00 00 00 01 00 00 - * 00 2D - * - * 06 4D 69 72 61 69 20 群名 - * 00 - * 00 - * 00 - * 00 - * 00 - * 38 87 5F D8 E8 D4 E9 79 73 8A A4 21 1C 3E 2C 43 D0 23 55 53 49 D3 1C DB F6 1F 84 59 77 66 DA 9C D7 26 0F E3 BD E1 F2 B9 29 D1 F6 97 1C 42 5E B0 AF 09 51 72 DA 03 37 AB 65 - * 00 - * 0A 00 00 00 00 06 00 03 00 02 00 - * 01 00 04 00 04 00 00 00 01 00 05 00 04 5D 90 A7 25 00 06 00 04 04 08 00 00 00 07 00 04 00 00 05 80 00 09 00 01 01 - * B1 89 BE 09 00 - * 3E 03 3F A2 00 01 - * 48 76 54 DC 00 00 - * 76 E4 B8 DD 00 00 - * 89 1A 5E AC 00 00 - * B1 89 BE 09 00 00 - */ - - /* - * 群 XkWhXi - * - * 00 00 00 03 01 41 00 04 01 40 21 04 40 - * 3E 03 3F A2 群主 - * - * 02 00 00 00 01 00 00 00 00 27 1A 00 C8 01 - * 00 00 00 01 00 00 - * F3 C8 - * - * 06 58 6B 57 68 58 69 - * 00 - * 00 - * 3B E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 0A E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 - * 00 - * 00 - * 38 EB 3B A5 90 AC E3 70 1F 42 51 B4 72 81 C8 F5 5A D8 80 69 B6 76 AD A4 AA CC 6A 17 4C 79 81 FF 82 04 BA 13 CE 28 DA 6C 3F 41 77 C0 77 40 B5 87 8E EE 29 20 65 FC 2D FF 63 - * 00 - * 0A 00 00 00 00 06 00 03 00 02 00 - * 01 00 04 00 04 00 00 00 05 00 05 00 04 57 94 6F 41 00 06 00 04 04 08 00 10 00 07 00 04 00 00 04 04 00 09 00 01 00 - * B1 89 BE 09 00 2D 5C 53 A6 00 01 2F 9B 1C F2 00 00 35 49 95 D1 00 01 3B FA 06 9F 00 00 3E 03 3F A2 00 00 42 C4 32 63 00 01 59 17 3E 05 00 01 6A 89 3E 3E 00 00 6D D7 4E CA 00 00 76 E4 B8 DD 00 00 7C BB 60 3C 00 01 7C BC D3 C1 00 01 87 73 86 9D 00 00 90 19 72 65 00 00 97 30 9A 6B 00 00 9C B1 E5 55 00 01 B1 89 BE 09 00 01 - */ - - /* - * 群 20秃顶28火葬30重生异世 - * - * - */ - - /* - * 群 Big convene' (与上面两个来自不同 bot) - * - * 00 00 00 03 01 01 00 04 01 00 80 01 40 - * 6C 18 F5 DA 群主 - * - * 02 00 00 27 1B 00 00 00 00 27 1B 01 F4 01 - * 00 00 00 01 00 00 - * 0F 1F - * - * 0C 42 69 67 20 63 6F 6E 76 65 6E 65 27 00 群名 - * 00 96 E6 AF 95 E4 B8 9A E4 BA 86 EF BC 8C E5 B8 8C E6 9C 9B E5 A4 A7 E5 AE B6 E8 83 BD E5 A4 9F E5 83 8F E4 BB A5 E5 89 8D E9 82 A3 E6 A0 B7 E5 BC 80 E5 BF 83 EF BC 8C E5 AD A6 E4 B9 A0 E8 BF 9B E6 AD A5 EF BC 8C E5 A4 A9 E5 A4 A9 E5 BF AB E4 B9 90 E3 80 82 E6 AD A4 E7 BE A4 E7 A6 81 E6 AD A2 E9 AA 82 E4 BA BA EF BC 8C E5 88 B7 E5 B1 8F E6 9A B4 E5 8A 9B EF BC 8C E8 BF 9D E8 A7 84 E8 80 85 E7 A6 81 E8 A8 80 EF BC 8C E4 B8 A5 E9 87 8D E8 80 85 E5 B0 B1 - * 76 E8 BF 9B E7 BE A4 E6 97 B6 EF BC 8C E8 AF B7 E4 BF AE E6 94 B9 E6 AD A3 E7 A1 AE E5 A7 93 E5 90 8D E3 80 82 E4 B8 8D E8 83 BD 54 E5 90 8C E5 AD A6 EF BC 8C E5 A4 AA E8 BF 87 E5 88 86 E7 9A 84 54 21 28 E4 BA 92 E8 B5 9E E7 BE A4 EF BC 8C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF E8 81 8A E5 A4 A9 E8 80 85 E5 8F AF E4 BB A5 E4 BA 92 E8 B5 9E E5 AF B9 E6 96 B9 - * 00 38 D9 FD F5 21 A6 1F 8D 61 37 A1 7A 92 91 2A 2C 71 46 A9 B9 1C 45 EB 38 74 4A 74 EA 77 7D 14 DB 12 D0 B0 09 C2 AA 22 16 F1 D0 B9 97 21 F0 5A A0 06 59 A7 3B 2F 32 D2 B8 E3 - * 00 0F 00 00 00 00 06 00 03 00 02 01 01 00 04 00 04 00 00 00 15 00 05 00 04 52 7C C5 7C 00 06 00 04 00 00 00 20 00 07 00 04 00 00 00 00 00 09 00 01 00 - * - * C5 15 BE BE 00 ???为啥这个只有一个呢 - * 1C ED 9F 9B 00 00 - * 26 D0 E1 3A 00 00 - * 2D 5C 53 A6 00 01 自己 管理员 - * 2D BD 28 D2 00 00 - * 2E 94 76 3E 00 00 - * 35 F3 BC F2 00 00 - * 37 D6 91 AB 00 00 - * 3A 60 1C 3E 00 80 10000000 群员, 好友 - * 3A 86 EA A3 00 48 01001000 群员 手机在线 - * 3D 7F E7 70 00 00 - * 3E 03 3F A2 00 09 00001001 好友, 特别关心, TIM PC 在线, 管理员 - * 41 47 0C DD 00 40 01000000 群员, 离线 - * 41 B6 32 A8 00 80 - * 44 C8 DA 23 00 00 - * 45 3E 1B 6A 00 80 10000000 群员 手机在线 - * 45 C6 59 E9 00 C0 群员 - * 4A BD C6 F9 00 00 - * 4C 67 45 E8 00 00 - * 4E AD C2 C2 00 80 - * 4F A0 F7 EC 00 80 - * 50 CB 11 E8 00 00 - * 58 22 21 90 00 00 - * 59 17 3E 05 00 01 管理员 好友 - * 5E 74 48 D9 00 00 - * 5E A2 B5 88 00 00 - * 66 A1 32 9B 00 40 - * 68 07 29 0A 00 00 - * 68 0F EF 4F 00 00 - * 69 8B 14 F3 00 80 - * 6A A5 27 4E 00 00 - * 6C 11 A0 89 00 81 10000001 管理员 - * 6C 18 F5 DA 00 08 群主 - * 6C 21 F8 E2 00 01 管理员 - * 71 F8 F5 18 00 00 - * 72 0B CC B6 00 00 - * 75 53 38 DF 00 00 - * 7A A1 8B 82 00 00 - * 7C 8C 1D 1B 00 00 - * 7C BC D3 C1 00 00 - * 84 2D B8 5F 00 00 - * 88 4C 33 76 00 00 - * 8C C8 0D 43 00 00 - * 90 B8 65 22 00 00 - * 91 54 89 E9 00 00 - * 9C E6 93 A5 00 01 管理员 - * 9D 59 6A 36 00 00 - * 9D 63 81 5C 00 00 - * 9E 31 AF AC 00 00 - * 9E 69 86 25 00 80 - * A1 FD CA 2D 00 00 - * A5 22 5C 48 00 00 - * A5 F2 9A B7 00 00 - * AF 25 74 9E 00 01 - * B1 50 24 00 00 00 - * B2 BD 81 A9 00 00 - * B5 0E B3 DD 00 00 - * B9 BF 0D BC 00 00 - * C5 15 BE BE 00 00 - */ + else -> unsupportedFlag("GroupPacketResponse typed 0x72", flag.toUHexString()) + } } - else -> unsupported() + else -> unsupportedType("GroupPacketResponse", packetType.toUHexString()) } } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt index b2a424d5a..55c586437 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt @@ -17,7 +17,6 @@ inline class NicknameMap(val delegate: Map) : Packet /** * 批量查询昵称. */ -@AnnotatedId(KnownPacketId.QUERY_NICKNAME) internal object QueryNicknamePacket : SessionPacketFactory() { /** * 单个查询. @@ -135,7 +134,6 @@ body=03 00 00 00 00 00 00 00 00 00 00 00 03 8E 3C A6 A0 EE EF 02 07 6C 01 14 E8 /** * 请求获取头像 */ // ? 这个包的数据跟下面那个包一样 -@AnnotatedId(KnownPacketId.REQUEST_PROFILE_AVATAR) internal object RequestProfileAvatarPacket : SessionPacketFactory() { //00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 operator fun invoke( @@ -159,7 +157,6 @@ internal object RequestProfileAvatarPacket : SessionPacketFactory() * * @see Profile */ -@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS) internal object RequestProfileDetailsPacket : SessionPacketFactory() { //00 01 3E F8 FB E3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 //00 01 B1 89 BE 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 @@ -211,7 +208,6 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory() { /** * 查询好友的备注 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/SendFriendMessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/SendFriendMessagePacket.kt index b009c84ce..4784de0b1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/SendFriendMessagePacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/SendFriendMessagePacket.kt @@ -11,7 +11,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.md5 -@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE) @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") internal object SendFriendMessagePacket : SessionPacketFactory() { operator fun invoke( diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendOnlineStatusChanged.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendOnlineStatusChanged.kt index b60fac6dc..5e7924fdc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendOnlineStatusChanged.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/FriendOnlineStatusChanged.kt @@ -7,9 +7,7 @@ import kotlinx.io.core.discardExact import kotlinx.io.core.readUByte import kotlinx.io.core.readUInt import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.getQQ import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.tim.packet.AnnotatedId import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId import net.mamoe.mirai.network.protocol.tim.packet.PacketId import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory @@ -23,7 +21,6 @@ data class FriendStatusChanged( /** * 好友在线状态改变 */ -@AnnotatedId(KnownPacketId.FRIEND_ONLINE_STATUS_CHANGE) internal object FriendOnlineStatusChangedPacket : SessionPacketFactory() { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberJoin.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberJoin.kt index 76476a406..5d04c8af9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberJoin.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberJoin.kt @@ -13,7 +13,6 @@ import net.mamoe.mirai.contact.internal.MemberImpl import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.getGroup -import net.mamoe.mirai.getQQ import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.discardExact @@ -76,7 +75,7 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler(0x02DCu) { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute { + //取消 - //00 00 00 11 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 + //00 00 00 11 00 + // 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 // 01 01 // 22 96 29 7B // 0C 01 @@ -92,7 +104,8 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl // 00 00 00 00 // 禁言 - //00 00 00 11 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 + //00 00 00 11 00 + // 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 // 01 // 01 // 22 96 29 7B @@ -104,28 +117,42 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl // 01 // 76 E4 B8 DD // 00 27 8D 00 - discardExact(19) - discardExact(2) - val group = bot.getGroup(readUInt()) - discardExact(2) - val operator = group.getMember(readUInt()) - discardExact(4) //time - discardExact(2) - val memberQQ = readUInt() - val durationSeconds = readUInt().toInt() - return if (durationSeconds == 0) { - if (memberQQ == bot.qqAccount) { - BeingUnmutedEvent(operator) - } else { - MemberUnmuteEvent(group.getMember(memberQQ), operator) + discardExact(3) + return when (val flag = readByte().toUInt()) { + 0x0Eu -> { + //00 00 00 0E 00 08 00 02 00 01 00 + // 0A 00 04 01 00 00 00 35 DB 60 A2 11 00 3E 08 07 20 A2 C1 ED AE 03 5A 34 08 A2 FF 8C F0 03 1A 19 08 F4 0E 10 FE 8C D3 EF 05 18 84 A1 F8 F9 06 20 00 28 00 30 A2 FF 8C F0 03 2A 0D 08 00 12 09 08 F4 0E 10 00 18 01 20 00 30 00 38 00 + Unknown0x02DCPacketFlag0x0EMaybeMutePacket } - } else { - if (memberQQ == bot.qqAccount) { - BeingMutedEvent(durationSeconds, operator) - } else { - MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator) + + 0x11u -> { + discardExact(15) + discardExact(2) + val group = bot.getGroup(readUInt()) + discardExact(2) + val operator = group.getMember(readUInt()) + discardExact(4) //time + discardExact(2) + val memberQQ = readUInt() + + val durationSeconds = readUInt().toInt() + if (durationSeconds == 0) { + if (memberQQ == bot.qqAccount) { + BeingUnmutedEvent(operator) + } else { + MemberUnmuteEvent(group.getMember(memberQQ), operator) + } + } else { + if (memberQQ == bot.qqAccount) { + BeingMutedEvent(durationSeconds, operator) + } else { + MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator) + } + } } + + else -> error("Unsupported flag in 0x02DC packet. flag=$flag, remainning=${readBytes().toUHexString()}") } } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEvent.kt index 9792c309b..3c5a06195 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEvent.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEvent.kt @@ -11,7 +11,6 @@ import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.getGroup -import net.mamoe.mirai.getQQ import net.mamoe.mirai.message.* import net.mamoe.mirai.message.internal.readMessageChain import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion @@ -117,6 +116,9 @@ data class GroupMessage( override val message: MessageChain ) : MessagePacket() { + /* + 01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04 + */ override val subject: Group get() = group inline fun At.member(): Member = group.getMember(this.target) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt index a1a092877..707cfa8a9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Captcha.kt @@ -12,7 +12,6 @@ internal object CaptchaKey : DecrypterByteArray, DecrypterType { override val value: ByteArray = TIMProtocol.key00BA } -@AnnotatedId(KnownPacketId.CAPTCHA) internal object CaptchaPacket : PacketFactory(CaptchaKey) { /** * 请求验证码传输 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ChangeOnlineStatusPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ChangeOnlineStatusPacket.kt index abbc4f8e4..694064ccc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ChangeOnlineStatusPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ChangeOnlineStatusPacket.kt @@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ /** * 改变在线状态: "我在线上", "隐身" 等 */ -@AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS) internal object ChangeOnlineStatusPacket : PacketFactory(NoDecrypter) { operator fun invoke( bot: UInt, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Heartbeat.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Heartbeat.kt index d9fbdaf68..46609339e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Heartbeat.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Heartbeat.kt @@ -13,7 +13,6 @@ import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeQQ @NoLog -@AnnotatedId(KnownPacketId.HEARTBEAT) internal object HeartbeatPacket : SessionPacketFactory() { operator fun invoke( bot: UInt, @@ -31,5 +30,4 @@ internal object HeartbeatPacket : SessionPacketFactory( } @NoLog -@AnnotatedId(KnownPacketId.HEARTBEAT) internal object HeartbeatPacketResponse : Packet, Subscribable \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt index 41131469d..862c8155f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt @@ -44,7 +44,6 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr /** * 提交密码 */ -@AnnotatedId(KnownPacketId.LOGIN) internal object SubmitPasswordPacket : PacketFactory(SubmitPasswordResponseDecrypter) { operator fun invoke( bot: UInt, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/SKey.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/SKey.kt index 5cffe9d21..f503e07dc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/SKey.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/SKey.kt @@ -23,7 +23,6 @@ internal inline class SKey( * 请求 `SKey` * SKey 用于 http api */ -@AnnotatedId(KnownPacketId.S_KEY) internal object RequestSKeyPacket : SessionPacketFactory() { operator fun invoke( bot: UInt, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Session.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Session.kt index fbda3a223..cb1ce8281 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Session.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Session.kt @@ -9,7 +9,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.localIpAddress -@AnnotatedId(KnownPacketId.SESSION_KEY) internal object RequestSessionPacket : PacketFactory(SessionResponseDecryptionKey) { operator fun invoke( bot: UInt, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt index 94e364841..abfa323f9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/Touch.kt @@ -20,7 +20,6 @@ internal object TouchKey : DecrypterByteArray, DecrypterType { * * @author Him188moe */ -@AnnotatedId(KnownPacketId.TOUCH) internal object TouchPacket : PacketFactory(TouchKey) { operator fun invoke( bot: UInt, @@ -45,7 +44,7 @@ internal object TouchPacket : PacketFactory } } - internal sealed class TouchResponse : Packet { + internal sealed class TouchResponse : Packet { class OK( var loginTime: Int, val loginIP: String, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index af3cbfc64..cbddc629a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -62,6 +62,12 @@ class BotConfiguration : CoroutineContext.Element { * 验证码处理器 */ var captchaSolver: CaptchaSolver = DefaultCaptchaSolver + /** + * 登录完成后几秒会收到好友消息的历史记录, + * 这些历史记录不会触发事件. + * 这个选项为是否把这些记录添加到日志 + */ + var logPreviousMessages: Boolean = false companion object Key : CoroutineContext.Key { /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Exceptions.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Exceptions.kt new file mode 100644 index 000000000..b42004faa --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Exceptions.kt @@ -0,0 +1,22 @@ +@file:Suppress("unused", "UNUSED_PARAMETER") + +package net.mamoe.mirai.utils + +import net.mamoe.mirai.contact.Group + +/** + * 在获取 [Group] 对象等操作时可能出现的异常 + */ +class GroupNotFoundException : Exception { + constructor() + constructor(message: String?) + constructor(message: String?, cause: Throwable?) + constructor(cause: Throwable?) +} + +open class MiraiInternalException : Exception { + constructor() + constructor(message: String?) + constructor(message: String?, cause: Throwable?) + constructor(cause: Throwable?) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt new file mode 100644 index 000000000..fb41bda83 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt @@ -0,0 +1,686 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package net.mamoe.mirai.utils + +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.loop + +fun LockFreeLinkedList.joinToString( + separator: CharSequence = ", ", + prefix: CharSequence = "[", + postfix: CharSequence = "]", + transform: ((E) -> CharSequence)? = null +): String = prefix.toString() + buildString { + this@joinToString.forEach { + if (transform != null) { + append(transform(it)) + } else append(it) + append(separator) + } +}.dropLast(2) + postfix + +/** + * Returns a [List] containing all the elements in [this] in the same order + */ +fun LockFreeLinkedList.toList(): List = toMutableList() + +/** + * Returns a [MutableList] containing all the elements in [this] in the same order + */ +fun LockFreeLinkedList.toMutableList(): MutableList { + val list = mutableListOf() + this.forEach { list.add(it) } + return list +} + +/** + * Implementation of lock-free LinkedList. + * + * Modifying can be performed concurrently. + * Iterating concurrency is guaranteed. + */ +open class LockFreeLinkedList { + @PublishedApi + internal val tail: Tail = Tail() + + @PublishedApi + internal val head: Head = Head(tail) + + fun removeFirst(): E { + while (true) { + val currentFirst = head.nextNode + if (!currentFirst.isValidElementNode()) { + throw NoSuchElementException() + } + if (head.compareAndSetNextNodeRef(currentFirst, currentFirst.nextNode)) { + return currentFirst.nodeValue + } + } + } + + fun peekFirst(): E = head.nextNode.letValueIfValid { return it } ?: throw NoSuchElementException() + + fun peekLast(): E = head.iterateBeforeFirst { it === tail }.letValueIfValid { return it } ?: throw NoSuchElementException() + + fun removeLast(): E { + while (true) { + val beforeLast = head.iterateBeforeFirst { it.nextNode === tail } + if (!beforeLast.isValidElementNode()) { + throw NoSuchElementException() + } + val last = beforeLast.nextNode + if (beforeLast.nextNodeRef.compareAndSet(last, last.nextNode)) { + return last.nodeValue + } + } + } + + fun addLast(element: E) { + val node = element.asNode(tail) + + while (true) { + val tail = head.iterateBeforeFirst { it === tail } // find the last node. + if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node + return + } + } + } + + operator fun plusAssign(element: E) = this.addLast(element) + + inline fun filteringGetOrAdd(filter: (E) -> Boolean, noinline supplier: () -> E): E { + val node = LazyNode(tail, supplier) + + while (true) { + var current: Node = head + + findLastNode@ while (true) { + if (current.isValidElementNode() && filter(current.nodeValue)) + return current.nodeValue + + if (current.nextNode === tail) { + if (current.compareAndSetNextNodeRef(tail, node)) { // ensure only one attempt can put the lazyNode in + return node.nodeValue + } + } + + current = current.nextNode + } + } + } + + @PublishedApi // limitation by atomicfu + internal fun Node.compareAndSetNextNodeRef(expect: Node, update: Node) = this.nextNodeRef.compareAndSet(expect, update) + + override fun toString(): String = joinToString() + + @Suppress("unused") + internal fun getLinkStructure(): String = buildString { + head.childIterateReturnsLastSatisfying>({ + append(it.toString()) + append(" <- ") + it.nextNode + }, { it !is Tail }) + }.dropLast(4) + + fun remove(element: E): Boolean { + while (true) { + val before = head.iterateBeforeNodeValue(element) + val toRemove = before.nextNode + if (toRemove === tail) { + return false + } + if (toRemove.isRemoved()) { + continue + } + toRemove.removed.value = true // logically remove: all the operations will recognize this node invalid + + + // physically remove: try to fix the link + var next: Node = toRemove.nextNode + while (next !== tail && next.isRemoved()) { + next = next.nextNode + } + if (before.nextNodeRef.compareAndSet(toRemove, next)) { + return true + } + } + } + + val size: Int get() = head.countChildIterate>({ it.nextNode }, { it !is Tail }) - 1 // empty head is always included + + operator fun contains(element: E): Boolean { + forEach { if (it == element) return true } + return false + } + + @Suppress("unused") + fun containsAll(elements: Collection): Boolean = elements.all { contains(it) } + + fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() } + + inline fun forEach(block: (E) -> Unit) { + var node: Node = head + while (true) { + node.letValueIfValid(block) + node = node.nextNode + if (node === tail) return + } + } + + fun addAll(elements: Collection) = elements.forEach { addLast(it) } + + @Suppress("unused") + fun clear() { + val first = head.nextNode + head.nextNode = tail + first.childIterateReturnFirstUnsatisfying({ + val n = it.nextNode + it.nextNode = tail + it.removed.value = true + n + }, { it !== tail }) // clear the link structure, help GC. + } + + @Suppress("unused") + fun removeAll(elements: Collection): Boolean = elements.all { remove(it) } + + /* + + + private fun removeNode(node: Node): Boolean { + if (node == tail) { + return false + } + while (true) { + val before = head.iterateBeforeFirst { it === node } + val toRemove = before.nextNode + val next = toRemove.nextNode + if (toRemove == tail) { // This + return true + } + toRemove.nodeValue = null // logically remove first, then all the operations will recognize this node invalid + + if (before.nextNodeRef.compareAndSet(toRemove, next)) { // physically remove: try to fix the link + return true + } + } + } + + fun removeAt(index: Int): E { + require(index >= 0) { "index must be >= 0" } + val nodeBeforeIndex = head.iterateValidNodeNTimes(index) + val value = nodeBeforeIndex.nodeValue + if (value === null) noSuchElement() + removeNode(nodeBeforeIndex) + return value + } + + operator fun set(index: Int, element: E): E { + while (true) { + val nodeAtIndex = head.iterateValidNodeNTimes(index + 1) + val originalValue = nodeAtIndex.nodeValue + if (originalValue === null) noSuchElement() // this node has been removed. + if (!nodeAtIndex.nodeValueRef.compareAndSet(null, element)) { // with concurrent compatibility + continue + } + return originalValue + } + } + + /** + * Find the last index of the element in the list that is [equals] to [element], with concurrent compatibility. + * + * For a typical list, say `head <- Node#1(1) <- Node#2(2) <- Node#3(3) <- Node#4(4) <- Node#5(2) <- tail`, + * the procedures of `lastIndexOf(2)` is: + * + * 1. Iterate each element, until 2 is found, accumulate the index found, which is 1 + * 2. Search again from the first matching element, which is Node#2 + * 3. Accumulate the index found. + * 4. Repeat 2,3 until the `tail` is reached. + * + * Concurrent changes may influence the result. + */ + fun lastIndexOf(element: E): Int { + var lastMatching: Node = head + var searchStartingFrom: Node = lastMatching + var index = 0 // accumulated index from each search + + findTheLastMatchingElement@ while (true) { // iterate to find the last matching element. + var timesOnThisTurn = if (searchStartingFrom === head) -1 else 0 // ignore the head + val got = searchStartingFrom.nextNode.iterateBeforeFirst { timesOnThisTurn++; it.nodeValue == element } + // find the first match starting from `searchStartingFrom` + + if (got.isTail()) break@findTheLastMatchingElement // no further elements + check(timesOnThisTurn >= 0) { "Internal check failed: too many times ran: $timesOnThisTurn" } + + searchStartingFrom = got.nextNode + index += timesOnThisTurn + + if (!got.isRemoved()) lastMatching = got + } + + if (!lastMatching.isValidElementNode()) { + // found is invalid means not found + return -1 + } + + return index + } + */ + + /* + override fun listIterator(): MutableListIterator = listIterator0(0) + override fun listIterator(index: Int): MutableListIterator = listIterator0(index) + + @Suppress("NOTHING_TO_INLINE") + private inline fun listIterator0(index: Int): MutableListIterator { + var first: Node = head + repeat(index) { + first = first.nextNode + if (first === tail) noSuchElement() + } + return object : MutableListIterator { + var currentNode: Node + get() = currentNodeRef.value + set(value) { + currentNodeRef.value = value + } + + private val currentNodeRef: AtomicRef> = atomic(first) // concurrent compatibility + + override fun nextIndex(): Int = indexOfNode(currentNode) + + + // region previous + + var previousNode: Node + get() = previousNodeRef.value + set(value) { + previousNodeRef.value = value + } + private val previousNodeRef: AtomicRef> = atomic(head) // concurrent compatibility + private val previousNodeIndexRef: AtomicInt = atomic(-1) // concurrent compatibility + private val currentNodeAtTheMomentWhenPreviousNodeIsUpdated: AtomicRef> = atomic(currentNode) + + override fun hasPrevious(): Boolean = previousIndex() == -1 + + private fun updatePrevious(): Boolean { + while (true) { + val localNodeAtTheMomentWhenPreviousNodeIsUpdated = currentNode + var i = -1 // head + var lastSatisfying: Node? = null + val foundNode = currentNode.childIterateReturnsLastSatisfying({ it.nextNode }, { + i++ + if (it.isValidElementNode()) { + lastSatisfying = it + } + it != currentNode + }) + + if (localNodeAtTheMomentWhenPreviousNodeIsUpdated !== currentNode) { + continue // current is concurrently changed, must retry + } + + if (!foundNode.isValidElementNode()) { + // Current node is not found in the list, meaning it had been removed concurrently + previousNode = head + previousNodeIndexRef.value = -1 + return false + } + + if (lastSatisfying === null) { + // All the previous nodes are logically removed. + previousNode = head + previousNodeIndexRef.value = -1 + return false + } + + currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = localNodeAtTheMomentWhenPreviousNodeIsUpdated + previousNode = lastSatisfying!! // false positive nullable warning + previousNodeIndexRef.value = i + return true + } + } + + override fun previous(): E { + if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) { + // node list have been changed. + if (!updatePrevious()) noSuchElement() + } + while (true) { + val value = previousNode.nodeValue + if (value != null) { + currentNode = previousNode + currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head + return value + } else if (!updatePrevious()) noSuchElement() + } + } + + override fun previousIndex(): Int { + if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) { + // node list have been changed. + if (!updatePrevious()) noSuchElement() + } + while (true) { + val value = previousNodeIndexRef.value + if (value != -1) return value + else if (!updatePrevious()) noSuchElement() + } + } + // endregion + + override fun add(element: E) { + val toAdd = element.asNode(tail) + while (true) { + val next = currentNode.nextNode + toAdd.nextNode = next + if (currentNode.nextNodeRef.compareAndSet(next, toAdd)) { // ensure the link is not changed + currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head + return + } + } + } + + override fun hasNext(): Boolean { + if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) { + // node list have been changed. + if (!updatePrevious()) noSuchElement() + } + return currentNode.nextNode !== tail + } + + override fun next(): E { + while (true) { + if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) { + // node list have been changed. + if (!updatePrevious()) noSuchElement() + } + val nextNodeValue = currentNode.nextNode.nodeValue + if (nextNodeValue !== null) { + currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head + return nextNodeValue + } + } + } + + override fun remove() { + if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility + noSuchElement() + } + currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head + } + + override fun set(element: E) { + if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) { + // node list have been changed. + if (!updatePrevious()) noSuchElement() + } + + } + + } + } + + override fun subList(fromIndex: Int, toIndex: Int): MutableList { + + } + */ + + /* + operator fun get(index: Int): E { + require(index >= 0) { "Index must be >= 0" } + var i = index + 1 // 1 for head + return head.iterateStopOnFirst { i-- == 0 }.nodeValue ?: noSuchElement() + } + + fun indexOf(element: E): Int { + var i = -1 // head + if (!head.iterateStopOnFirst { + i++ + it.nodeValue == element + }.isValidElementNode()) { + return -1 + } + return i - 1 // iteration is stopped at the next node + } + + private fun indexOfNode(node: Node): Int { + var i = -1 // head + if (!head.iterateStopOnFirst { + i++ + it == node + }.isValidElementNode()) { + return -1 + } + return i - 1 // iteration is stopped at the next node + } + + operator fun iterator(): MutableIterator = object : MutableIterator { + var currentNode: Node + get() = currentNodeRef.value + set(value) { + currentNodeRef.value = value + } + + private val currentNodeRef: AtomicRef> = atomic(head) // concurrent compatibility + + /** + * Check if + * + * **Notice That:** + * if `hasNext` returned `true`, then the last remaining element is removed concurrently, + * [next] will produce a [NoSuchElementException] + */ + override fun hasNext(): Boolean = !currentNode.iterateStopOnFirst { it.isValidElementNode() }.isTail() + + /** + * Iterate until the next node is not + */ + override fun next(): E { + while (true) { + val next = currentNode.nextNode + if (next.isTail()) noSuchElement() + + currentNode = next + + val nodeValue = next.nodeValue + if (nodeValue != null) { // next node is not removed, that's what we want + return nodeValue + } // or try again + } + } + + override fun remove() { + if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility + noSuchElement() + } + } + } + */ +} + + +// region internal + +@Suppress("NOTHING_TO_INLINE") +private inline fun E.asNode(nextNode: Node): Node = Node(nextNode, this) + +/** + * Self-iterate using the [iterator], until [mustBeTrue] returns `false`. + * Returns the element at the last time when the [mustBeTrue] returns `true` + */ +@PublishedApi +internal inline fun > N.childIterateReturnsLastSatisfying(iterator: (N) -> N, mustBeTrue: (N) -> Boolean): N { + if (!mustBeTrue(this)) return this + var value: N = this + + while (true) { + val newValue = iterator(value) + if (mustBeTrue(newValue)) { + value = newValue + } else { + return value + } + + if (newValue is Tail<*>) return newValue + } +} + +/** + * Self-iterate using the [iterator], until [mustBeTrue] returns `false`. + * Returns the element at the first time when the [mustBeTrue] returns `false` + */ +private inline fun E.childIterateReturnFirstUnsatisfying(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): E { + if (!mustBeTrue(this)) return this + var value: E = this + + while (true) { + val newValue = iterator(value) + if (mustBeTrue(newValue)) { + value = newValue + } else { + return newValue + } + + if (newValue is Tail<*>) return newValue + } +} + +/** + * Self-iterate using the [iterator], until [mustBeTrue] returns `false`. + * Returns the count of elements being iterated. + */ +private inline fun E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): Int { + var count = 0 + var value: E = this + if (!mustBeTrue(value)) return count + + while (true) { + count++ + val newValue = iterator(value) + if (mustBeTrue(newValue)) { + value = newValue + } else { + return count + } + } +} + +@PublishedApi +internal class LazyNode @PublishedApi internal constructor( + nextNode: Node, + private val valueComputer: () -> E +) : Node(nextNode, null) { + private val initialized = atomic(false) + + private val value: AtomicRef = atomic(null) + + override val nodeValue: E + get() { + @Suppress("BooleanLiteralArgument") // false positive warning + if (initialized.compareAndSet(false, true)) { // ensure only one lucky attempt can go into the if + val value = valueComputer() + this.value.value = value + return value // fast path + } + value.loop { + if (it != null) { + return it + } + } + } +} + +@PublishedApi +internal class Head(nextNode: Node) : Node(nextNode, null) { + override fun toString(): String = "Head" + override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Head") +} + +@PublishedApi +internal open class Tail : Node(null, null) { + override fun toString(): String = "Tail" + override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Tail") +} + +@PublishedApi +internal open class Node( + nextNode: Node?, + private var initialNodeValue: E? +) { + /* + internal val id: Int = nextId() + companion object { + private val idCount = atomic(0) + internal fun nextId() = idCount.getAndIncrement() + }*/ + + override fun toString(): String = "$nodeValue" + + open val nodeValue: E get() = initialNodeValue ?: error("Internal error: nodeValue is not initialized") + + val removed = atomic(false) + + @Suppress("LeakingThis") + val nextNodeRef: AtomicRef> = atomic(nextNode ?: this) + + inline fun letValueIfValid(block: (E) -> R): R? { + if (!this.isValidElementNode()) { + return null + } + val value = this.nodeValue + return if (value !== null) block(value) else null + } + + /** + * Short cut for accessing [nextNodeRef] + */ + var nextNode: Node + get() = nextNodeRef.value + set(value) { + nextNodeRef.value = value + } + + /** + * Returns the former node of the last node whence [filter] returns true + */ + inline fun iterateBeforeFirst(filter: (Node) -> Boolean): Node = + this.childIterateReturnsLastSatisfying({ it.nextNode }, { !filter(it) }) + + /** + * Check if all the node which is not [Tail] matches the [condition] + * + * Head, which is this, is also being tested. + * [Tail], is not being tested. + */ + inline fun allMatching(condition: (Node) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail + + /** + * Stop on and returns the former element of the element that is [equals] to the [element] + * + * E.g.: for `head <- 1 <- 2 <- 3 <- tail`, `iterateStopOnNodeValue(2)` returns the node whose value is 1 + */ + @Suppress("NOTHING_TO_INLINE") + internal inline fun iterateBeforeNodeValue(element: E): Node = this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element } + +} + +@PublishedApi +internal fun Node.isRemoved() = this.removed.value + +@PublishedApi +@Suppress("NOTHING_TO_INLINE") +internal inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() + +@PublishedApi +@Suppress("NOTHING_TO_INLINE") +internal inline fun Node<*>.isHead(): Boolean = this is Head + +@PublishedApi +@Suppress("NOTHING_TO_INLINE") +internal inline fun Node<*>.isTail(): Boolean = this is Tail + +// en dregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt index a622aecbe..6d77df7bd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt @@ -1,4 +1,4 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.utils diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt index 253c2c8dc..1f1e3770f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt @@ -54,6 +54,17 @@ internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket { return bytes.toReadPacket() } +@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) +internal inline fun ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R { + val bytes = this.readBytes() + try { + return block(bytes.toReadPacket()) + } catch (e: Throwable) { + DebugLogger.debug("Error in ByteReadPacket $name=" + bytes.toUHexString()) + throw e + } +} + @Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket { val bytes = this.readBytes() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt index e080637b6..9a0917bd9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt @@ -1,4 +1,4 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE") package net.mamoe.mirai.utils.io @@ -95,7 +95,7 @@ fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMa } /** - * 读扁平的 tag-UVarInt map. 重复的 tag 将不会只保留最后一个 + * 读扁平的 tag-UVarInt map. 重复的 tag 将只保留最后一个 * * tag: UByte * value: UVarint @@ -140,10 +140,11 @@ fun Map.printTLVMap(name: String = "", keyLength: Int = 1) = } }) -@Suppress("NOTHING_TO_INLINE") internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported") -@Suppress("NOTHING_TO_INLINE") +internal inline fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}") +internal inline fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}") + internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed") @JvmName("printTLVStringMap") diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt new file mode 100644 index 000000000..2c03fae54 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt @@ -0,0 +1,139 @@ +package net.mamoe.mirai.contact + +import net.mamoe.mirai.test.shouldBeEqualTo +import org.junit.Test +import kotlin.random.Random + +internal class GroupIdConversionsKtTest { + + @UseExperimental(ExperimentalUnsignedTypes::class) + @Test + fun toInternalId() { + repeat(1000000) { _ -> + val it = Random.nextInt() + try { + GroupId(it.toUInt()).toInternalId() shouldBeEqualTo GroupId(it.toUInt()).toInternalIdOld() + } catch (e: Throwable) { + println(it) + throw e + } + } + } + + @UseExperimental(ExperimentalUnsignedTypes::class) + @Test + fun toId() { + repeat(1000000) { _ -> + val it = Random.nextInt() + try { + GroupInternalId(it.toUInt()).toId() shouldBeEqualTo GroupInternalId(it.toUInt()).toIdOld() + } catch (e: Throwable) { + println(it) + throw e + } + } + } + + +} + +@UseExperimental(ExperimentalUnsignedTypes::class) +private fun GroupId.toInternalIdOld(): GroupInternalId {//求你别出错 + val left: Long = this.value.toString().let { + if (it.length <= 6) { + return GroupInternalId(this.value) + } + it.substring(0, it.length - 6).toLong() + } + val right: Long = this.value.toString().let { + it.substring(it.length - 6).toLong() + } + + return GroupInternalId( + when (left) { + in 1..10 -> { + ((left + 202).toString() + right.toString()).toUInt() + } + in 11..19 -> { + ((left + 469).toString() + right.toString()).toUInt() + } + in 20..66 -> { + ((left + 208).toString() + right.toString()).toUInt() + } + in 67..156 -> { + ((left + 1943).toString() + right.toString()).toUInt() + } + in 157..209 -> { + ((left + 199).toString() + right.toString()).toUInt() + } + in 210..309 -> { + ((left + 389).toString() + right.toString()).toUInt() + } + in 310..499 -> { + ((left + 349).toString() + right.toString()).toUInt() + } + else -> this.value + } + ) +} + +@UseExperimental(ExperimentalUnsignedTypes::class) +private fun GroupInternalId.toIdOld(): GroupId = with(value) { + //求你别出错 + var left: UInt = this.toString().let { + if (it.length <= 6) { + return GroupId(value) + } + it.substring(0 until it.length - 6).toUInt() + } + + return GroupId(when (left.toInt()) { + in 203..212 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 6).toUInt() + } + ((left - 202u).toString() + right.toString()).toUInt() + } + in 480..488 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 6).toUInt() + } + ((left - 469u).toString() + right.toString()).toUInt() + } + in 2100..2146 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 7).toUInt() + } + left = left.toString().substring(0 until 3).toUInt() + ((left - 208u).toString() + right.toString()).toUInt() + } + in 2010..2099 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 6).toUInt() + } + ((left - 1943u).toString() + right.toString()).toUInt() + } + in 2147..2199 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 7).toUInt() + } + left = left.toString().substring(0 until 3).toUInt() + ((left - 199u).toString() + right.toString()).toUInt() + } + in 4100..4199 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 7).toUInt() + } + left = left.toString().substring(0 until 3).toUInt() + ((left - 389u).toString() + right.toString()).toUInt() + } + in 3800..3989 -> { + val right: UInt = this.toString().let { + it.substring(it.length - 7).toUInt() + } + left = left.toString().substring(0 until 3).toUInt() + ((left - 349u).toString() + right.toString()).toUInt() + } + else -> value + }) +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt new file mode 100644 index 000000000..eaa02fbea --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt @@ -0,0 +1,264 @@ +@file:Suppress("RemoveRedundantBackticks", "NonAsciiCharacters") + +package net.mamoe.mirai.utils + +import kotlinx.coroutines.* +import net.mamoe.mirai.test.shouldBeEqualTo +import net.mamoe.mirai.test.shouldBeTrue +import org.junit.Test +import kotlin.system.exitProcess +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@MiraiExperimentalAPI +internal class LockFreeLinkedListTest { + init { + GlobalScope.launch { + delay(30 * 1000) + exitProcess(-100) + } + } + + @Test + fun addAndGetSingleThreaded() { + val list = LockFreeLinkedList() + list.addLast(1) + list.addLast(2) + list.addLast(3) + list.addLast(4) + + list.size shouldBeEqualTo 4 + } + + @Test + fun addAndGetConcurrent() = runBlocking { + //withContext(Dispatchers.Default){ + val list = LockFreeLinkedList() + + list.concurrentDo(1000, 10) { addLast(1) } + list.size shouldBeEqualTo 1000 * 10 + + list.concurrentDo(100, 10) { + remove(1).shouldBeTrue() + } + + list.size shouldBeEqualTo 1000 * 10 - 100 * 10 + //} + } + + @Test + fun addAndGetMassConcurrentAccess() = runBlocking { + val list = LockFreeLinkedList() + + val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } } + + //delay(1) // let addJob fly + if (addJob.isCompleted) { + error("Number of elements are not enough") + } + val foreachJob = async { + list.concurrentDo(1, 10000) { + forEach { it + it } + } + } + val removeLastJob = async { + list.concurrentDo(1, 15000) { + removeLast() shouldBeEqualTo 1 + } + } + val removeFirstJob = async { + list.concurrentDo(1, 10000) { + removeFirst() shouldBeEqualTo 1 + } + } + val addJob2 = async { + list.concurrentDo(1, 5000) { + addLast(1) + } + } + val removeExactJob = launch { + list.concurrentDo(3, 1000) { + remove(1).shouldBeTrue() + } + } + val filteringGetOrAddJob = launch { + list.concurrentDo(1, 10000) { + filteringGetOrAdd({ it == 2 }, { 1 }) + } + } + joinAll(addJob, addJob2, foreachJob, removeLastJob, removeFirstJob, removeExactJob, filteringGetOrAddJob) + + list.size shouldBeEqualTo 2 * 30000 - 1 * 15000 - 1 * 10000 + 1 * 5000 - 3 * 1000 + 1 * 10000 + } + + @Test + fun removeWhileForeach() { + val list = LockFreeLinkedList() + repeat(10) { list.addLast(it) } + list.forEach { + list.remove(it + 1) + } + list.peekFirst() shouldBeEqualTo 0 + } + + @Test + fun remove() { + val list = LockFreeLinkedList() + + assertFalse { list.remove(1) } + assertEquals(0, list.size) + + list.addLast(1) + assertTrue { list.remove(1) } + assertEquals(0, list.size) + + list.addLast(2) + assertFalse { list.remove(1) } + assertEquals(1, list.size) + } + + @Test + fun addAll() { + val list = LockFreeLinkedList() + list.addAll(listOf(1, 2, 3, 4, 5)) + list.size shouldBeEqualTo 5 + } + + @Test + fun clear() { + val list = LockFreeLinkedList() + list.addAll(listOf(1, 2, 3, 4, 5)) + list.size shouldBeEqualTo 5 + list.clear() + list.size shouldBeEqualTo 0 + } + + @UseExperimental(ExperimentalUnsignedTypes::class) + @Test + fun withInlineClassElements() { + val list = LockFreeLinkedList() + list.addAll(listOf(1u, 2u, 3u, 4u, 5u)) + list.size shouldBeEqualTo 5 + + list.toString() shouldBeEqualTo "[1, 2, 3, 4, 5]" + } + + @Test + fun `filteringGetOrAdd when add`() { + val list = LockFreeLinkedList() + list.addAll(listOf(1, 2, 3, 4, 5)) + val value = list.filteringGetOrAdd({ it == 6 }, { 6 }) + + println("Check value") + value shouldBeEqualTo 6 + println("Check size") + println(list.getLinkStructure()) + list.size shouldBeEqualTo 6 + } + + @Test + fun `filteringGetOrAdd when get`() { + val list = LockFreeLinkedList() + list.addAll(listOf(1, 2, 3, 4, 5)) + val value = list.filteringGetOrAdd({ it == 2 }, { 2 }) + + println("Check value") + value shouldBeEqualTo 2 + println("Check size") + println(list.getLinkStructure()) + list.size shouldBeEqualTo 5 + } + + @Test + fun `filteringGetOrAdd when empty`() { + val list = LockFreeLinkedList() + val value = list.filteringGetOrAdd({ it == 2 }, { 2 }) + + println("Check value") + value shouldBeEqualTo 2 + println("Check size") + println(list.getLinkStructure()) + list.size shouldBeEqualTo 1 + } + /* + @Test + fun indexOf() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2, 3, 3) + assertEquals(0, list.indexOf(1)) + assertEquals(2, list.indexOf(3)) + + assertEquals(-1, list.indexOf(4)) + } + + @Test + fun iterator() { + var list: LockFreeLinkedList = lockFreeLinkedListOf(2) + list.forEach { + it shouldBeEqualTo 2 + } + + list = lockFreeLinkedListOf(1, 2) + list.joinToString { it.toString() } shouldBeEqualTo "1, 2" + + + list = lockFreeLinkedListOf(1, 2) + val iterator = list.iterator() + iterator.remove() + var reached = false + for (i in iterator) { + i shouldBeEqualTo 2 + reached = true + } + reached shouldBeEqualTo true + + list.joinToString { it.toString() } shouldBeEqualTo "2" + iterator.remove() + assertFailsWith { iterator.remove() } + } + + @Test + fun `lastIndexOf of exact 1 match at first`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(2, 1) + list.lastIndexOf(2) shouldBeEqualTo 0 + } + + @Test + fun `lastIndexOf of exact 1 match`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2) + list.lastIndexOf(2) shouldBeEqualTo 1 + } + + @Test + fun `lastIndexOf of multiply matches`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2, 2) + list.lastIndexOf(2) shouldBeEqualTo 2 + } + + @Test + fun `lastIndexOf of no match`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(2) + list.lastIndexOf(3) shouldBeEqualTo -1 + } + + @Test + fun `lastIndexOf of many elements`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 4, 2, 3, 4, 5) + list.lastIndexOf(4) shouldBeEqualTo 4 + } + + */ +} + +@UseExperimental(ExperimentalCoroutinesApi::class) +@MiraiExperimentalAPI +internal suspend inline fun > E.concurrentDo(numberOfCoroutines: Int, times: Int, crossinline todo: E.() -> Unit) = + coroutineScope { + repeat(numberOfCoroutines) { + launch(start = CoroutineStart.UNDISPATCHED) { + repeat(times) { + todo() + } + } + } + } \ No newline at end of file diff --git a/mirai-debug/build.gradle.kts b/mirai-debug/build.gradle.kts index 6402594f9..1d30f6e05 100644 --- a/mirai-debug/build.gradle.kts +++ b/mirai-debug/build.gradle.kts @@ -41,7 +41,7 @@ fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id dependencies { implementation(project(":mirai-core")) - runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE + // runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") diff --git a/mirai-demos/mirai-demo-1/build.gradle b/mirai-demos/mirai-demo-1/build.gradle index 95bd7c2cf..f6ea91580 100644 --- a/mirai-demos/mirai-demo-1/build.gradle +++ b/mirai-demos/mirai-demo-1/build.gradle @@ -3,7 +3,7 @@ apply plugin: "java" dependencies { api project(":mirai-core") - runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE + // runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion } diff --git a/mirai-demos/mirai-demo-gentleman/build.gradle b/mirai-demos/mirai-demo-gentleman/build.gradle index 592ca50d0..0f18b4236 100644 --- a/mirai-demos/mirai-demo-gentleman/build.gradle +++ b/mirai-demos/mirai-demo-gentleman/build.gradle @@ -4,8 +4,8 @@ apply plugin: "application" dependencies { api project(":mirai-core") - runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE - //runtime files("../../mirai-core/build/classes/atomicfu/jvm/main") // classpath is not set correctly by IDE + //runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE + implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion