Merge remote-tracking branch 'origin/master'

This commit is contained in:
liujiahua123123 2019-12-15 14:42:26 +08:00
commit d047a3635c
50 changed files with 1835 additions and 701 deletions

View File

@ -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* ### `0.7.5` *2019/12/09*
- 修复验证码包发出后无回复 (错误的验证码包) - 修复验证码包发出后无回复 (错误的验证码包)

View File

@ -1,7 +1,7 @@
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.7.5 mirai_version=0.8.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin

View File

@ -20,7 +20,6 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.addFriend import net.mamoe.mirai.addFriend
import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.getGroup import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.hexToUBytes 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 { open class IllegalAccessException : Exception {
override val message: String get() = super.message!! override val message: String get() = super.message!!
@ -107,14 +107,14 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.param(name
@Suppress("IMPLICIT_CAST_TO_ANY") @Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R? = private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R? =
when { when (R::class) {
R::class == Byte::class -> call.parameters[name]?.toByte() Byte::class -> call.parameters[name]?.toByte()
R::class == Int::class -> call.parameters[name]?.toInt() Int::class -> call.parameters[name]?.toInt()
R::class == Short::class -> call.parameters[name]?.toShort() Short::class -> call.parameters[name]?.toShort()
R::class == Float::class -> call.parameters[name]?.toFloat() Float::class -> call.parameters[name]?.toFloat()
R::class == Long::class -> call.parameters[name]?.toLong() Long::class -> call.parameters[name]?.toLong()
R::class == Double::class -> call.parameters[name]?.toDouble() Double::class -> call.parameters[name]?.toDouble()
R::class == Boolean::class -> when (call.parameters[name]) { Boolean::class -> when (call.parameters[name]) {
"true" -> true "true" -> true
"false" -> false "false" -> false
"0" -> false "0" -> false
@ -123,13 +123,13 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNul
else -> illegalParam("boolean", name) else -> illegalParam("boolean", name)
} }
R::class == String::class -> call.parameters[name] String::class -> call.parameters[name]
R::class == UByte::class -> call.parameters[name]?.toUByte() UByte::class -> call.parameters[name]?.toUByte()
R::class == UInt::class -> call.parameters[name]?.toUInt() UInt::class -> call.parameters[name]?.toUInt()
R::class == UShort::class -> call.parameters[name]?.toUShort() UShort::class -> call.parameters[name]?.toUShort()
R::class == UByteArray::class -> call.parameters[name]?.hexToUBytes() UByteArray::class -> call.parameters[name]?.hexToUBytes()
R::class == ByteArray::class -> call.parameters[name]?.hexToBytes() ByteArray::class -> call.parameters[name]?.hexToBytes()
else -> error(name::class.simpleName + " is not supported") else -> error(name::class.simpleName + " is not supported")
} as R? } as R?

View File

@ -1,254 +1,136 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE")
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.Deferred
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot.ContactSystem
import net.mamoe.mirai.contact.* 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.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.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.GroupNotFoundException
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger 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.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic 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 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人. * 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 * @see Contact
*/ */
class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineContext) : CoroutineScope { interface Bot : CoroutineScope {
private val supervisorJob = SupervisorJob(context[Job]) companion object {
override val coroutineContext: CoroutineContext = suspend inline operator fun invoke(account: BotAccount, logger: MiraiLogger): Bot = BotImpl(account, logger, coroutineContext)
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") } 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) suspend inline operator fun invoke(qq: Long, password: String): Bot = BotImpl(BotAccount(qq.toUInt(), password), context = coroutineContext)
constructor(qq: UInt, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context) operator fun invoke(qq: Long, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
constructor(account: BotAccount, context: CoroutineContext) : this(account, DefaultLogger("Bot(" + account.id + ")"), 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 { fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq)
launch {
addInstance(this@Bot)
}
} }
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>
/**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
*/
fun getQQ(id: UInt): QQ
/**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
*/
fun getQQ(id: Long): QQ
/**
* 与这个机器人相关的群列表. 机器人不一定是群成员.
*/
val groups: ContactList<Group>
/**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* [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] 下的协程. * [关闭][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( fun tryReinitializeNetworkHandler(
configuration: BotConfiguration, configuration: BotConfiguration,
cause: Throwable? = null cause: Throwable? = null
): Job = launch { ): Job
repeat(configuration.reconnectionRetryTimes) {
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
logger.info("Reconnected successfully")
return@launch
} else {
delay(configuration.reconnectPeriodMillis)
}
}
}
/** /**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [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
suspend fun reinitializeNetworkHandler( suspend fun reinitializeNetworkHandler(
configuration: BotConfiguration, configuration: BotConfiguration,
cause: Throwable? = null cause: Throwable? = null
): LoginResult { ): 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()
}
/** /**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [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 reinitializeNetworkHandlerAsync( fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration, configuration: BotConfiguration,
cause: Throwable? = null cause: Throwable? = null
): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) } ): Deferred<LoginResult>
/** // endregion
* Bot 联系人管理.
*
* @see Bot.contacts
*/
inner class ContactSystem internal constructor() {
val bot: Bot get() = this@Bot
@UseExperimental(MiraiInternalAPI::class) fun close()
@Suppress("PropertyName")
internal val _groups = MutableContactList<Group>()
internal lateinit var groupsUpdater: Job
private val groupsLock = Mutex()
val groups: ContactList<Group> = ContactList(_groups)
@Suppress("PropertyName")
@UseExperimental(MiraiInternalAPI::class)
internal val _qqs = MutableContactList<QQ>() //todo 实现群列表和好友列表获取
internal lateinit var qqUpdaterJob: Job
private val qqsLock = Mutex()
val qqs: ContactList<QQ> = 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<Bot> = mutableListOf()
private val instanceLock: Mutex = Mutex()
private val instances: List<Bot> get() = _instances
suspend fun instanceWhose(qq: UInt) = instanceLock.withLock {
instances.first { it.qqAccount == qq }
}
internal suspend fun addInstance(bot: Bot) = instanceLock.withLock {
_instances += bot
}
}
} }

View File

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

View File

@ -68,15 +68,15 @@ Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749
* @param remark 好友备注 * @param remark 好友备注
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = bot.withSession { suspend fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = withSession {
return when (CanAddFriendPacket(bot.qqAccount, id, bot.sessionKey).sendAndExpect<CanAddFriendResponse>()) { return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect<CanAddFriendResponse>()) {
is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED
is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED
is CanAddFriendResponse.ReadyToAdd, is CanAddFriendResponse.ReadyToAdd,
is CanAddFriendResponse.RequireVerification -> { is CanAddFriendResponse.RequireVerification -> {
val key = RequestFriendAdditionKeyPacket(bot.qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(bot.qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>() AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
AddFriendResult.WAITING_FOR_APPROVE AddFriendResult.WAITING_FOR_APPROVE
} //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥 } //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥
@ -87,7 +87,7 @@ suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remar
/*is CanAddFriendResponse.ReadyToAdd -> { /*is CanAddFriendResponse.ReadyToAdd -> {
// TODO: 2019/11/11 这不需要验证信息的情况 // TODO: 2019/11/11 这不需要验证信息的情况
//AddFriendPacket(bot.qqAccount, id, bot.sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await() //AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
TODO() TODO()
}*/ }*/
} }

View File

@ -1,6 +1,6 @@
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("BotHelperKt") @file:JvmName("BotHelperKt")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE") @file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai package net.mamoe.mirai
@ -26,26 +26,8 @@ import kotlin.jvm.JvmOverloads
*/ */
//Contacts //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<Group> get() = this.contacts.groups
/**
* 取得好友列表
*/
inline val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
/** /**
* [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. * [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
@ -70,7 +52,7 @@ internal suspend inline fun Bot.sendPacket(packet: OutgoingPacket) =
* 使用在默认配置基础上修改的配置进行登录 * 使用在默认配置基础上修改的配置进行登录
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
suspend inline fun Bot.login(noinline configuration: BotConfiguration.() -> Unit): LoginResult { suspend inline fun Bot.login(configuration: BotConfiguration.() -> Unit): LoginResult {
contract { contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
} }
@ -91,7 +73,7 @@ suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() }
* 使用在默认配置基础上修改的配置进行登录, 返回 [this] * 使用在默认配置基础上修改的配置进行登录, 返回 [this]
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
suspend inline fun Bot.alsoLogin(noinline configuration: BotConfiguration.() -> Unit): Bot { suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bot {
contract { contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) 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 * 取得机器人的 QQ
*/ */

View File

@ -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<Bot> = 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<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
// endregion
// region contacts
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
override val qqs: ContactList<QQ> = 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<GroupPacket.InfoResponse>() }) {
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()
}
}

View File

@ -8,7 +8,9 @@ import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.chain import net.mamoe.mirai.message.chain
import net.mamoe.mirai.message.singleChain import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
import net.mamoe.mirai.withSession import net.mamoe.mirai.withSession
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
@ -54,55 +56,3 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R {
} }
return bot.withSession(block) return bot.withSession(block)
} }
/**
* 只读联系人列表
*/
@UseExperimental(MiraiInternalAPI::class)
inline class ContactList<C : Contact>(internal val mutable: MutableContactList<C>) : Map<UInt, C> {
/**
* 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<MutableMap.MutableEntry<UInt, C>> get() = mutable.entries
override val keys: MutableSet<UInt> get() = mutable.keys
override val values: MutableCollection<C> get() = mutable.values
}
/**
* 可修改联系人列表. 只会在内部使用.
*/
@MiraiInternalAPI
inline class MutableContactList<C : Contact>(private val delegate: MutableMap<UInt, C> = linkedMapOf()) : MutableMap<UInt, C> {
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<MutableMap.MutableEntry<UInt, C>> get() = delegate.entries
override val keys: MutableSet<UInt> get() = delegate.keys
override val values: MutableCollection<C> 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<out UInt, C>) = delegate.putAll(from)
override fun remove(key: UInt): C? = delegate.remove(key)
}

View File

@ -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<C : Contact>(@PublishedApi internal val delegate: LockFreeLinkedList<C>) {
/**
* 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<C>): 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 <C : Contact> LockFreeLinkedList<C>.get(id: UInt): C {
forEach { if (it.id == id) return it }
throw NoSuchElementException()
}
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: UInt): C? {
forEach { if (it.id == id) return it }
return null
}
fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier)

View File

@ -2,101 +2,50 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
fun GroupId.toInternalId(): GroupInternalId {//求你别出错 import kotlin.math.pow
val left: Long = this.value.toString().let {
if (it.length < 6) {
return GroupInternalId(this.value) @Suppress("ObjectPropertyName")
} private val `10EXP6` = 10.0.pow(6).toUInt()
it.substring(0, it.length - 6).toLong()
}
val right: Long = this.value.toString().let { fun GroupId.toInternalId(): GroupInternalId {
it.substring(it.length - 6).toLong() 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( return GroupInternalId(
when (left) { when (left) {
in 1..10 -> { in 1..10 -> ((left + 202).toString() + right.toString()).toUInt()
((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 11..19 -> { in 67..156 -> ((left + 1943).toString() + right.toString()).toUInt()
((left + 469).toString() + right.toString()).toUInt() in 157..209 -> ((left + 199).toString() + right.toString()).toUInt()
} in 210..309 -> ((left + 389).toString() + right.toString()).toUInt()
in 20..66 -> { in 310..499 -> ((left + 349).toString() + right.toString()).toUInt()
((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 else -> this.value
} }
) )
} }
fun GroupInternalId.toId(): GroupId = with(value) { fun GroupInternalId.toId(): GroupId = with(value.toString()) {
//求你别出错 if (value < `10EXP6`) {
var left: UInt = this.toString().let { return GroupId(value)
if (it.length < 6) {
return GroupId(value)
}
it.substring(0 until it.length - 6).toUInt()
} }
val left: UInt = this.dropLast(6).toUInt()
return GroupId(when (left.toInt()) { return GroupId(
in 203..212 -> { when (left.toInt()) {
val right: UInt = this.toString().let { in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt()
it.substring(it.length - 6).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()
((left - 202u).toString() + right.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
})
} }

View File

@ -3,6 +3,8 @@
package net.mamoe.mirai.contact.internal package net.mamoe.mirai.contact.internal
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.data.Profile 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.network.sessionKey
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket import net.mamoe.mirai.sendPacket
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.logStacktrace
import net.mamoe.mirai.withSession import net.mamoe.mirai.withSession
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -35,15 +35,12 @@ internal sealed class ContactImpl : Contact {
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
@PublishedApi @PublishedApi
internal suspend fun Group(bot: Bot, groupId: GroupId, context: CoroutineContext): Group { internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group =
val info: RawGroupInfo = try { GroupImpl(bot, groupId, context).apply {
bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, groupId.toInternalId(), sessionKey).sendAndExpect() } this@apply.info = info.parseBy(this@apply)
} catch (e: Exception) { launch { startUpdater() }
e.logStacktrace()
error("Cannot obtain group info for id ${groupId.value}")
} }
return GroupImpl(bot, groupId, context).apply { this.info = info.parseBy(this); startUpdater() }
}
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) : 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() override val internalId = GroupId(id).toInternalId()
internal lateinit var info: GroupInfo internal lateinit var info: GroupInfo
internal lateinit var initialInfoJob: Job
override val owner: Member get() = info.owner override val owner: Member get() = info.owner
override val name: String get() = info.name override val name: String get() = info.name
override val announcement: String get() = info.announcement override val announcement: String get() = info.announcement
override val members: ContactList<Member> get() = info.members override val members: ContactList<Member> get() = info.members
override fun getMember(id: UInt): Member = override fun getMember(id: UInt): Member =
if (members.containsKey(id)) members[id]!! members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
else throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message)) 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) @UseExperimental(MiraiInternalAPI::class)
override suspend fun startUpdater() { override suspend fun startUpdater() {
subscribeAlways<MemberJoinEventPacket> { subscribeAlways<MemberJoinEventPacket> {
// FIXME: 2019/11/29 非线程安全!! members.delegate.addLast(it.member)
members.mutable[it.member.id] = it.member
} }
subscribeAlways<MemberQuitEvent> { subscribeAlways<MemberQuitEvent> {
// FIXME: 2019/11/29 非线程安全!! members.delegate.remove(it.member)
members.mutable.remove(it.member.id)
} }
} }
override fun toString(): String = "Group(${this.id})" override fun toString(): String = "Group(${this.id})"
} }
@Suppress("FunctionName") @Suppress("FunctionName", "NOTHING_TO_INLINE")
suspend inline fun QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { startUpdater() } internal inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } }
@PublishedApi @PublishedApi
internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) : 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})" override fun toString(): String = "QQ(${this.id})"
} }
@Suppress("FunctionName") @Suppress("FunctionName", "NOTHING_TO_INLINE")
suspend inline fun Member(delegate: QQ, group: Group, permission: MemberPermission, coroutineContext: CoroutineContext): Member = internal inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, group, permission, coroutineContext).apply { startUpdater() } MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } }
/** /**
* 群成员 * 群成员

View File

@ -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 package net.mamoe.mirai.network
@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor(
*/ */
val gtk: Int get() = _gtk val gtk: Int get() = _gtk
suspend inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt()) inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
suspend inline fun UInt.qq(): QQ = bot.getQQ(this) inline fun UInt.qq(): QQ = bot.getQQ(this)
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt()) suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0)) suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))

View File

@ -226,7 +226,9 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
return return
if (!packet::class.annotations.filterIsInstance<NoLog>().any()) { if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
bot.logger.verbose("Packet received: $packet") if ((packet as? BroadcastControllable)?.shouldBroadcast != false) {
bot.logger.verbose("Packet received: $packet")
}
} }
when (packet) { when (packet) {
@ -239,7 +241,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) } temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) }
.also { temporaryPacketHandlers.removeAll(it) } .also { temporaryPacketHandlers.removeAll(it) }
}.forEach { }.forEach {
it.doReceiveWithoutExceptions(packet) it.doReceiveCatchingExceptions(packet)
} }
if (factory is SessionPacketFactory<*>) { if (factory is SessionPacketFactory<*>) {
@ -286,7 +288,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
it::class.annotations.filterIsInstance<NoLog>().any() it::class.annotations.filterIsInstance<NoLog>().any()
} }
}?.let { }?.let {
bot.logger.verbose("Packet sent: ${it.packetId}") bot.logger.verbose("Packet sent: ${it.name}")
} }
PacketSentEvent(bot, packet).broadcast() PacketSentEvent(bot, packet).broadcast()

View File

@ -60,7 +60,7 @@ internal class TemporaryPacketHandler<P : Packet, R>(
internal inline fun filter(session: BotSession, packet: Packet, sequenceId: UShort): Boolean = internal inline fun filter(session: BotSession, packet: Packet, sequenceId: UShort): Boolean =
expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) sequenceId == toSend.sequenceId else true 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") @Suppress("UNCHECKED_CAST")
val ret = try { val ret = try {
withContext(callerContext) { withContext(callerContext) {

View File

@ -2,19 +2,6 @@
package net.mamoe.mirai.network.protocol.tim.packet 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 版本 * 包的最后一次修改时间, 和分析时使用的 TIM 版本
*/ */

View File

@ -24,7 +24,7 @@ internal class OutgoingPacket(
val sequenceId: UShort, val sequenceId: UShort,
internal val delegate: ByteReadPacket internal val delegate: ByteReadPacket
) : Packet { ) : Packet {
private val name: String by lazy { val name: String by lazy {
name ?: packetId.toString() name ?: packetId.toString()
} }
} }

View File

@ -10,7 +10,6 @@ import kotlinx.io.pool.useInstance
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler 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.ByteArrayPool
import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
@ -26,20 +25,13 @@ import net.mamoe.mirai.utils.readProtoMap
*/ */
internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) { internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) {
/** @Suppress("PropertyName")
* 2 Ubyte. internal var _id: PacketId = NullPacketId
* 读取注解 [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}")
// TODO: 2019/11/22 修改 包 ID 为参数
/** /**
* ID. * ID.
*/ */
open val id: PacketId by lazy { annotatedId.id } open val id: PacketId get() = _id
/** /**
* **解码**服务器的回复数据包 * **解码**服务器的回复数据包

View File

@ -55,35 +55,35 @@ internal inline class IgnoredPacketId constructor(override val value: UShort) :
* 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id * 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id
*/ */
@Suppress("unused") @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 { PacketId {
inline TOUCH(0x0825u, TouchPacket), TOUCH(0x0825u, TouchPacket),
inline SESSION_KEY(0x0828u, RequestSessionPacket), SESSION_KEY(0x0828u, RequestSessionPacket),
inline LOGIN(0x0836u, SubmitPasswordPacket), LOGIN(0x0836u, SubmitPasswordPacket),
inline CAPTCHA(0x00BAu, CaptchaPacket), CAPTCHA(0x00BAu, CaptchaPacket),
inline SERVER_EVENT_1(0x00CEu, EventPacketFactory), SERVER_EVENT_1(0x00CEu, EventPacketFactory),
inline SERVER_EVENT_2(0x0017u, EventPacketFactory), SERVER_EVENT_2(0x0017u, EventPacketFactory),
inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket), FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket), CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
inline HEARTBEAT(0x0058u, HeartbeatPacket), HEARTBEAT(0x0058u, HeartbeatPacket),
inline S_KEY(0x001Du, RequestSKeyPacket), S_KEY(0x001Du, RequestSKeyPacket),
inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket), ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
inline GROUP_PACKET(0x0002u, GroupPacket), GROUP_PACKET(0x0002u, GroupPacket),
inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket), SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket), CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
inline ADD_FRIEND(0x00A8u, AddFriendPacket), ADD_FRIEND(0x00A8u, AddFriendPacket),
inline REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket), REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
inline GROUP_IMAGE_ID(0x0388u, GroupImagePacket), GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
inline FRIEND_IMAGE_ID(0x0352u, FriendImagePacket), FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket), REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket), REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
inline QUERY_NICKNAME(0x0126u, QueryNicknamePacket), 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 查询 "新朋友" 记录 // 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()})" override fun toString(): String = (factory::class.simpleName ?: this.name) + "(${value.toUHexString()})"
} }

View File

@ -19,7 +19,6 @@ import net.mamoe.mirai.withSession
* - 昵称 * - 昵称
* - 共同群内的群名片 * - 共同群内的群名片
*/ */
@AnnotatedId(KnownPacketId.QUERY_PREVIOUS_NAME)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() { internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() {
operator fun invoke( operator fun invoke(
@ -77,7 +76,6 @@ class PreviousNameList(
* *
* @author Him188moe * @author Him188moe
*/ */
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() { internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
operator fun invoke( operator fun invoke(
@ -155,7 +153,6 @@ inline class FriendAdditionKey(val value: IoBuffer)
/** /**
* 请求一个 32 Key, 在添加好友时发出 * 请求一个 32 Key, 在添加好友时发出
*/ */
@AnnotatedId(KnownPacketId.REQUEST_FRIEND_ADDITION_KEY)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() { internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() {
operator fun invoke( operator fun invoke(
@ -182,7 +179,6 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri
/** /**
* 请求添加好友 * 请求添加好友
*/ */
@AnnotatedId(KnownPacketId.ADD_FRIEND)
internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>() { internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>() {
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
@Suppress("FunctionName") @Suppress("FunctionName")

View File

@ -92,7 +92,6 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse {
* - 服务器已经存有这个图片 * - 服务器已经存有这个图片
* - 服务器未存有, 返回一个 key 用于客户端上传 * - 服务器未存有, 返回一个 key 用于客户端上传
*/ */
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.11.16", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.16", timVersion = "2.3.2 (21173)")
internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() { internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
@Suppress("FunctionName") @Suppress("FunctionName")

View File

@ -1,5 +1,13 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.action 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 // 0001
// 已确认 查好友列表的列表 // 已确认 查好友列表的列表
@ -15,6 +23,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
// 00 00 // 00 00
internal inline class FriendListList(val delegate: List<FriendList>): Packet
internal object QueryFriendListListPacket : SessionPacketFactory<FriendList>() {
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 // 0134

View File

@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
* *
* @author Him188moe * @author Him188moe
*/ */
@AnnotatedId(KnownPacketId.ACCOUNT_INFO)
internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() { internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
operator fun invoke( operator fun invoke(
qq: UInt, qq: UInt,

View File

@ -99,7 +99,6 @@ internal class ImageUploadInfo(
/** /**
* 获取 Image Id 和上传用的一个 uKey * 获取 Image Id 和上传用的一个 uKey
*/ */
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.11.22", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.22", timVersion = "2.3.2 (21173)")
internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() { internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {

View File

@ -10,6 +10,7 @@ import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* 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.MiraiInternalAPI
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession import net.mamoe.mirai.withSession
@ -36,6 +37,10 @@ class GroupInfo(
"GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}" "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( internal data class RawGroupInfo(
val group: UInt, val group: UInt,
val owner: UInt, val owner: UInt,
@ -45,17 +50,20 @@ internal data class RawGroupInfo(
* 含群主 * 含群主
*/ */
val members: Map<UInt, MemberPermission> val members: Map<UInt, MemberPermission>
) : GroupPacket.GroupPacketResponse { ) : GroupPacket.InfoResponse {
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
suspend inline fun parseBy(group: Group): GroupInfo = group.bot.withSession { fun parseBy(group: Group): GroupInfo = group.bot.withSession {
val memberList = LockFreeLinkedList<Member>()
members.forEach { entry: Map.Entry<UInt, MemberPermission> ->
memberList.addLast(entry.key.qq().let { group.Member(it, entry.value, it.coroutineContext) })
}
return GroupInfo( return GroupInfo(
group, 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.name,
this@RawGroupInfo.announcement, this@RawGroupInfo.announcement,
ContactList(this@RawGroupInfo.members.mapValuesTo(MutableContactList()) { entry: Map.Entry<UInt, MemberPermission> -> ContactList(memberList)
entry.key.qq().let { Member(it,group, entry.value, it.coroutineContext) }
})
) )
} }
} }
@ -71,7 +79,6 @@ inline class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, G
} }
@Suppress("FunctionName") @Suppress("FunctionName")
@AnnotatedId(KnownPacketId.GROUP_PACKET)
internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() { internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
fun Message( fun Message(
@ -159,6 +166,8 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
override fun toString(): String = "GroupPacket.MuteResponse" override fun toString(): String = "GroupPacket.MuteResponse"
} }
internal interface InfoResponse : Packet, GroupPacketResponse
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
@UseExperimental(ExperimentalStdlibApi::class) @UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode( override suspend fun ByteReadPacket.decode(
@ -166,7 +175,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
sequenceId: UShort, sequenceId: UShort,
handler: BotNetworkHandler<*> handler: BotNetworkHandler<*>
): GroupPacketResponse { ): GroupPacketResponse {
return when (readUByte().toUInt()) { return when (val packetType = readUByte().toUInt()) {
0x2Au -> MessageResponse 0x2Au -> MessageResponse
0x7Eu -> MuteResponse // 成功: 7E 00 22 96 29 7B; 0x7Eu -> MuteResponse // 成功: 7E 00 22 96 29 7B;
@ -179,180 +188,185 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
} }
0x72u -> { 0x72u -> {
discardExact(1) // 00 when (val flag = readByte().toInt()) {
discardExact(4) // group internal id 0x02 -> GroupNotFound
val group = readUInt() // group id 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 discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
val owner = readUInt() val owner = readUInt()
discardExact(22) discardExact(22)
val groupName = readUByteLVString() val groupName = readUByteLVString()
discardExact(readUByte()) // 00 discardExact(readUByte()) // 00
discardExact(readUByte()) // 00 discardExact(readUByte()) // 00
val announcement = readUByteLVString() val announcement = readUByteLVString()
discardExact(readUByte()) // 00 discardExact(readUByte()) // 00
discardExact(readUByte()) // 00 discardExact(readUByte()) // 00
discardExact(readUByte()) // 38 ... 未知 discardExact(readUByte()) // 38 ... 未知
discardExact(readUByte()) // 00 discardExact(readUByte()) // 00
discardExact(readUByte()) // 0A ... discardExact(readUByte()) // 0A ...
discardExact(38) discardExact(38)
val stop = readUInt() // 标记读取群成员的结束 val stop = readUInt() // 标记读取群成员的结束
discardExact(1) // 00 discardExact(1) // 00
val members = mutableMapOf<UInt, MemberPermission>() val members = mutableMapOf<UInt, MemberPermission>()
do { do {
val qq = readUInt() val qq = readUInt()
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态 val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
if (qq == owner) { if (qq == owner) {
continue 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
*/
} }
else -> unsupportedFlag("GroupPacketResponse typed 0x72", flag.toUHexString())
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 -> unsupported() else -> unsupportedType("GroupPacketResponse", packetType.toUHexString())
} }
} }
} }

View File

@ -17,7 +17,6 @@ inline class NicknameMap(val delegate: Map<UInt, String>) : Packet
/** /**
* 批量查询昵称. * 批量查询昵称.
*/ */
@AnnotatedId(KnownPacketId.QUERY_NICKNAME)
internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() { internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
/** /**
* 单个查询. * 单个查询.
@ -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<AvatarLink>() { internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>() {
//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 //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( operator fun invoke(
@ -159,7 +157,6 @@ internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>()
* *
* @see Profile * @see Profile
*/ */
@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS)
internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfileDetailsResponse>() { internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfileDetailsResponse>() {
//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 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 //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<RequestProfil
} }
} }
@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS)
internal data class RequestProfileDetailsResponse( internal data class RequestProfileDetailsResponse(
val qq: UInt, val qq: UInt,
val profile: Profile val profile: Profile

View File

@ -15,7 +15,6 @@ import net.mamoe.mirai.utils.io.writeZero
*/ */
inline class FriendNameRemark(val value: String) : Packet inline class FriendNameRemark(val value: String) : Packet
@AnnotatedId(KnownPacketId.QUERY_FRIEND_REMARK)
internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() { internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() {
/** /**
* 查询好友的备注 * 查询好友的备注

View File

@ -11,7 +11,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() { internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
operator fun invoke( operator fun invoke(

View File

@ -7,9 +7,7 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler 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.KnownPacketId
import net.mamoe.mirai.network.protocol.tim.packet.PacketId import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory 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<FriendStatusChanged>() { internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {

View File

@ -13,7 +13,6 @@ import net.mamoe.mirai.contact.internal.MemberImpl
import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.getGroup import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.discardExact
@ -76,7 +75,7 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
discardExact(1) // 01 discardExact(1) // 01
val qq = bot.getQQ(readUInt()) val qq = bot.getQQ(readUInt())
val member = Member(qq, group, MemberPermission.MEMBER, qq.coroutineContext) val member = group.Member(qq, MemberPermission.MEMBER, qq.coroutineContext)
return if (readByte().toInt() == 0x03) { return if (readByte().toInt() == 0x03) {
MemberJoinEventPacket(member, null) MemberJoinEventPacket(member, null)

View File

@ -1,15 +1,17 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.network.protocol.tim.packet.event package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup import net.mamoe.mirai.getGroup
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.toUHexString
// region mute // region mute
/** /**
@ -59,6 +61,7 @@ class MemberUnmuteEvent(
/** /**
* 机器人被解除禁言事件 * 机器人被解除禁言事件
*/ */
@Suppress("SpellCheckingInspection")
class BeingUnmutedEvent( class BeingUnmutedEvent(
override val operator: Member override val operator: Member
) : UnmuteEvent() { ) : UnmuteEvent() {
@ -73,15 +76,24 @@ sealed class UnmuteEvent : EventOfMute() {
// endregion // endregion
internal object Unknown0x02DCPacketFlag0x0EMaybeMutePacket : EventOfMute() {
override val operator: Member get() = error("Getting a field from Unknown0x02DCPacketFlag0x0EMaybeMutePacket")
override val group: Group get() = error("Getting a field from Unknown0x02DCPacketFlag0x0EMaybeMutePacket")
override fun toString(): String = "Unknown0x02DCPacketFlag0x0EMaybeMutePacket"
}
sealed class EventOfMute : EventPacket { sealed class EventOfMute : EventPacket {
abstract val operator: Member abstract val operator: Member
abstract val group: Group abstract val group: Group
} }
// TODO: 2019/12/14 这可能不只是禁言的包.
internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) { internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute { 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 // 01 01
// 22 96 29 7B // 22 96 29 7B
// 0C 01 // 0C 01
@ -92,7 +104,8 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
// 00 00 00 00 // 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
// 01 // 01
// 22 96 29 7B // 22 96 29 7B
@ -104,28 +117,42 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
// 01 // 01
// 76 E4 B8 DD // 76 E4 B8 DD
// 00 27 8D 00 // 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() discardExact(3)
return if (durationSeconds == 0) { return when (val flag = readByte().toUInt()) {
if (memberQQ == bot.qqAccount) { 0x0Eu -> {
BeingUnmutedEvent(operator) //00 00 00 0E 00 08 00 02 00 01 00
} else { // 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
MemberUnmuteEvent(group.getMember(memberQQ), operator) Unknown0x02DCPacketFlag0x0EMaybeMutePacket
} }
} else {
if (memberQQ == bot.qqAccount) { 0x11u -> {
BeingMutedEvent(durationSeconds, operator) discardExact(15)
} else { discardExact(2)
MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator) 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()}")
} }
} }
} }

View File

@ -11,7 +11,6 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.internal.readMessageChain import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
@ -117,6 +116,9 @@ data class GroupMessage(
override val message: MessageChain override val message: MessageChain
) : MessagePacket<Member, Group>() { ) : MessagePacket<Member, Group>() {
/*
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 override val subject: Group get() = group
inline fun At.member(): Member = group.getMember(this.target) inline fun At.member(): Member = group.getMember(this.target)

View File

@ -12,7 +12,6 @@ internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
override val value: ByteArray = TIMProtocol.key00BA override val value: ByteArray = TIMProtocol.key00BA
} }
@AnnotatedId(KnownPacketId.CAPTCHA)
internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(CaptchaKey) { internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(CaptchaKey) {
/** /**
* 请求验证码传输 * 请求验证码传输

View File

@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
/** /**
* 改变在线状态: "我在线上", "隐身" * 改变在线状态: "我在线上", "隐身"
*/ */
@AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS)
internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) { internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,

View File

@ -13,7 +13,6 @@ import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ import net.mamoe.mirai.utils.io.writeQQ
@NoLog @NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() { internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
@ -31,5 +30,4 @@ internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>(
} }
@NoLog @NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
internal object HeartbeatPacketResponse : Packet, Subscribable internal object HeartbeatPacketResponse : Packet, Subscribable

View File

@ -44,7 +44,6 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr
/** /**
* 提交密码 * 提交密码
*/ */
@AnnotatedId(KnownPacketId.LOGIN)
internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) { internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,

View File

@ -23,7 +23,6 @@ internal inline class SKey(
* 请求 `SKey` * 请求 `SKey`
* SKey 用于 http api * SKey 用于 http api
*/ */
@AnnotatedId(KnownPacketId.S_KEY)
internal object RequestSKeyPacket : SessionPacketFactory<SKey>() { internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,

View File

@ -9,7 +9,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.localIpAddress import net.mamoe.mirai.utils.localIpAddress
@AnnotatedId(KnownPacketId.SESSION_KEY)
internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) { internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,

View File

@ -20,7 +20,6 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
* *
* @author Him188moe * @author Him188moe
*/ */
@AnnotatedId(KnownPacketId.TOUCH)
internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) { internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
@ -45,7 +44,7 @@ internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>
} }
} }
internal sealed class TouchResponse : Packet { internal sealed class TouchResponse : Packet {
class OK( class OK(
var loginTime: Int, var loginTime: Int,
val loginIP: String, val loginIP: String,

View File

@ -62,6 +62,12 @@ class BotConfiguration : CoroutineContext.Element {
* 验证码处理器 * 验证码处理器
*/ */
var captchaSolver: CaptchaSolver = DefaultCaptchaSolver var captchaSolver: CaptchaSolver = DefaultCaptchaSolver
/**
* 登录完成后几秒会收到好友消息的历史记录,
* 这些历史记录不会触发事件.
* 这个选项为是否把这些记录添加到日志
*/
var logPreviousMessages: Boolean = false
companion object Key : CoroutineContext.Key<BotConfiguration> { companion object Key : CoroutineContext.Key<BotConfiguration> {
/** /**

View File

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

View File

@ -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 <E> LockFreeLinkedList<E>.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 <E> LockFreeLinkedList<E>.toList(): List<E> = toMutableList()
/**
* Returns a [MutableList] containing all the elements in [this] in the same order
*/
fun <E> LockFreeLinkedList<E>.toMutableList(): MutableList<E> {
val list = mutableListOf<E>()
this.forEach { list.add(it) }
return list
}
/**
* Implementation of lock-free LinkedList.
*
* Modifying can be performed concurrently.
* Iterating concurrency is guaranteed.
*/
open class LockFreeLinkedList<E> {
@PublishedApi
internal val tail: Tail<E> = Tail()
@PublishedApi
internal val head: Head<E> = 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<E> = 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 <E> Node<E>.compareAndSetNextNodeRef(expect: Node<E>, update: Node<E>) = this.nextNodeRef.compareAndSet(expect, update)
override fun toString(): String = joinToString()
@Suppress("unused")
internal fun getLinkStructure(): String = buildString {
head.childIterateReturnsLastSatisfying<Node<*>>({
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<E> = toRemove.nextNode
while (next !== tail && next.isRemoved()) {
next = next.nextNode
}
if (before.nextNodeRef.compareAndSet(toRemove, next)) {
return true
}
}
}
val size: Int get() = head.countChildIterate<Node<E>>({ 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<E>): Boolean = elements.all { contains(it) }
fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() }
inline fun forEach(block: (E) -> Unit) {
var node: Node<E> = head
while (true) {
node.letValueIfValid(block)
node = node.nextNode
if (node === tail) return
}
}
fun addAll(elements: Collection<E>) = 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<E>): Boolean = elements.all { remove(it) }
/*
private fun removeNode(node: Node<E>): 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<E> = head
var searchStartingFrom: Node<E> = 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<E> = listIterator0(0)
override fun listIterator(index: Int): MutableListIterator<E> = listIterator0(index)
@Suppress("NOTHING_TO_INLINE")
private inline fun listIterator0(index: Int): MutableListIterator<E> {
var first: Node<E> = head
repeat(index) {
first = first.nextNode
if (first === tail) noSuchElement()
}
return object : MutableListIterator<E> {
var currentNode: Node<E>
get() = currentNodeRef.value
set(value) {
currentNodeRef.value = value
}
private val currentNodeRef: AtomicRef<Node<E>> = atomic(first) // concurrent compatibility
override fun nextIndex(): Int = indexOfNode(currentNode)
// region previous
var previousNode: Node<E>
get() = previousNodeRef.value
set(value) {
previousNodeRef.value = value
}
private val previousNodeRef: AtomicRef<Node<E>> = atomic(head) // concurrent compatibility
private val previousNodeIndexRef: AtomicInt = atomic(-1) // concurrent compatibility
private val currentNodeAtTheMomentWhenPreviousNodeIsUpdated: AtomicRef<Node<E>> = atomic(currentNode)
override fun hasPrevious(): Boolean = previousIndex() == -1
private fun updatePrevious(): Boolean {
while (true) {
val localNodeAtTheMomentWhenPreviousNodeIsUpdated = currentNode
var i = -1 // head
var lastSatisfying: Node<E>? = 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<E> {
}
*/
/*
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<E>): 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<E> = object : MutableIterator<E> {
var currentNode: Node<E>
get() = currentNodeRef.value
set(value) {
currentNodeRef.value = value
}
private val currentNodeRef: AtomicRef<Node<E>> = 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> E.asNode(nextNode: Node<E>): Node<E> = 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 : Node<*>> 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> 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> 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<E> @PublishedApi internal constructor(
nextNode: Node<E>,
private val valueComputer: () -> E
) : Node<E>(nextNode, null) {
private val initialized = atomic(false)
private val value: AtomicRef<E?> = 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<E>(nextNode: Node<E>) : Node<E>(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<E> : Node<E>(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<E>(
nextNode: Node<E>?,
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<Node<E>> = atomic(nextNode ?: this)
inline fun <R> 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<E>
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<E>) -> Boolean): Node<E> =
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<E>) -> 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<E> = this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element }
}
@PublishedApi
internal fun <E> Node<E>.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

View File

@ -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 package net.mamoe.mirai.utils

View File

@ -54,6 +54,17 @@ internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
return bytes.toReadPacket() return bytes.toReadPacket()
} }
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal inline fun <R> 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")) @Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket { internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket {
val bytes = this.readBytes() val bytes = this.readBytes()

View File

@ -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 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 * tag: UByte
* value: UVarint * value: UVarint
@ -140,10 +140,11 @@ fun Map<UInt, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 1) =
} }
}) })
@Suppress("NOTHING_TO_INLINE")
internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported") 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") internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed")
@JvmName("printTLVStringMap") @JvmName("printTLVStringMap")

View File

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

View File

@ -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<Int>()
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<Int>()
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<Int>()
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<Int>()
repeat(10) { list.addLast(it) }
list.forEach {
list.remove(it + 1)
}
list.peekFirst() shouldBeEqualTo 0
}
@Test
fun remove() {
val list = LockFreeLinkedList<Int>()
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<Int>()
list.addAll(listOf(1, 2, 3, 4, 5))
list.size shouldBeEqualTo 5
}
@Test
fun clear() {
val list = LockFreeLinkedList<Int>()
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<UInt>()
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<Int>()
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<Int>()
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<Int>()
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<Int> = 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<Int> = 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<NoSuchElementException> { iterator.remove() }
}
@Test
fun `lastIndexOf of exact 1 match at first`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2, 1)
list.lastIndexOf(2) shouldBeEqualTo 0
}
@Test
fun `lastIndexOf of exact 1 match`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2)
list.lastIndexOf(2) shouldBeEqualTo 1
}
@Test
fun `lastIndexOf of multiply matches`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 2)
list.lastIndexOf(2) shouldBeEqualTo 2
}
@Test
fun `lastIndexOf of no match`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2)
list.lastIndexOf(3) shouldBeEqualTo -1
}
@Test
fun `lastIndexOf of many elements`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 4, 2, 3, 4, 5)
list.lastIndexOf(4) shouldBeEqualTo 4
}
*/
}
@UseExperimental(ExperimentalCoroutinesApi::class)
@MiraiExperimentalAPI
internal suspend inline fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutines: Int, times: Int, crossinline todo: E.() -> Unit) =
coroutineScope {
repeat(numberOfCoroutines) {
launch(start = CoroutineStart.UNDISPATCHED) {
repeat(times) {
todo()
}
}
}
}

View File

@ -41,7 +41,7 @@ fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id
dependencies { dependencies {
implementation(project(":mirai-core")) 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") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")

View File

@ -3,7 +3,7 @@ apply plugin: "java"
dependencies { dependencies {
api project(":mirai-core") 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.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
} }

View File

@ -4,8 +4,8 @@ apply plugin: "application"
dependencies { dependencies {
api project(":mirai-core") 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
//runtime files("../../mirai-core/build/classes/atomicfu/jvm/main") // classpath is not set correctly by IDE
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion