Commit d047a363 authored by liujiahua123123's avatar liujiahua123123

Merge remote-tracking branch 'origin/master'

parents aade2e51 7cd1bc73
......@@ -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*
- 修复验证码包发出后无回复 (错误的验证码包)
......
# style guide
kotlin.code.style=official
# config
mirai_version=0.7.5
mirai_version=0.8.0
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin
......
......@@ -20,7 +20,6 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.addFriend
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.hexToUBytes
......@@ -81,6 +80,7 @@ suspend inline fun ApplicationCall.ok() = this.respond(HttpStatusCode.OK, "OK")
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
......@@ -107,14 +107,14 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.param(name
@Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class)
private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R? =
when {
R::class == Byte::class -> call.parameters[name]?.toByte()
R::class == Int::class -> call.parameters[name]?.toInt()
R::class == Short::class -> call.parameters[name]?.toShort()
R::class == Float::class -> call.parameters[name]?.toFloat()
R::class == Long::class -> call.parameters[name]?.toLong()
R::class == Double::class -> call.parameters[name]?.toDouble()
R::class == Boolean::class -> when (call.parameters[name]) {
when (R::class) {
Byte::class -> call.parameters[name]?.toByte()
Int::class -> call.parameters[name]?.toInt()
Short::class -> call.parameters[name]?.toShort()
Float::class -> call.parameters[name]?.toFloat()
Long::class -> call.parameters[name]?.toLong()
Double::class -> call.parameters[name]?.toDouble()
Boolean::class -> when (call.parameters[name]) {
"true" -> true
"false" -> false
"0" -> false
......@@ -123,13 +123,13 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNul
else -> illegalParam("boolean", name)
}
R::class == String::class -> call.parameters[name]
String::class -> call.parameters[name]
R::class == UByte::class -> call.parameters[name]?.toUByte()
R::class == UInt::class -> call.parameters[name]?.toUInt()
R::class == UShort::class -> call.parameters[name]?.toUShort()
UByte::class -> call.parameters[name]?.toUByte()
UInt::class -> call.parameters[name]?.toUInt()
UShort::class -> call.parameters[name]?.toUShort()
R::class == UByteArray::class -> call.parameters[name]?.hexToUBytes()
R::class == ByteArray::class -> call.parameters[name]?.hexToBytes()
UByteArray::class -> call.parameters[name]?.hexToUBytes()
ByteArray::class -> call.parameters[name]?.hexToBytes()
else -> error(name::class.simpleName + " is not supported")
} as R?
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE")
package net.mamoe.mirai
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot.ContactSystem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.internal.Group
import net.mamoe.mirai.contact.internal.QQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.GroupNotFoundException
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic
data class BotAccount(
val id: UInt,
val password: String
) {
constructor(id: Long, password: String) : this(id.toUInt(), password)
}
// These pseudo 'constructors' are suspend, therefore they should not be inside the object
@Suppress("FunctionName")
suspend inline fun Bot(account: BotAccount, logger: MiraiLogger): Bot = Bot(account, logger, coroutineContext)
@Suppress("FunctionName")
suspend inline fun Bot(account: BotAccount): Bot = Bot(account, coroutineContext)
@JvmSynthetic
@Suppress("FunctionName")
suspend inline fun Bot(qq: UInt, password: String): Bot = Bot(BotAccount(qq, password), coroutineContext)
@Suppress("FunctionName")
suspend inline fun Bot(qq: Long, password: String): Bot = Bot(BotAccount(qq.toUInt(), password), coroutineContext)
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人.
*
* [Bot] 由 3 个模块组成.
* [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问
* [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问
* [机器人账号信息][BotAccount]: 可通过 [Bot.qqAccount] 访问
*
* 若需要得到机器人的 QQ 账号, 请访问 [Bot.qqAccount]
* 若需要得到服务器上所有机器人列表, 请访问 [Bot.instances]
*
* 在 BotHelper.kt 中有一些访问的捷径. 如 [Bot.getGroup]
*
*
*
* Bot that is the base of the whole program.
* It consists of
* a [ContactSystem], which manage contacts such as [QQ] and [Group];
* a [TIMBotNetworkHandler], which manages the connection to the server;
* a [BotAccount], which stores the account information(e.g. qq number the bot)
*
* To of all the QQ contacts, access [Bot.qqAccount]
* To of all the Robot instance, access [Bot.instances]
*
*
* @author Him188moe
* @author NaturalHG
* @see Contact
*/
class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineContext) : CoroutineScope {
private val supervisorJob = SupervisorJob(context[Job])
override val coroutineContext: CoroutineContext =
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
interface Bot : CoroutineScope {
companion object {
suspend inline operator fun invoke(account: BotAccount, logger: MiraiLogger): Bot = BotImpl(account, logger, coroutineContext)
suspend inline operator fun invoke(account: BotAccount): Bot = BotImpl(account, context = coroutineContext)
@JvmSynthetic
suspend inline operator fun invoke(qq: UInt, password: String): Bot = BotImpl(BotAccount(qq, password), context = coroutineContext)
constructor(qq: Long, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context)
constructor(qq: UInt, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context)
constructor(account: BotAccount, context: CoroutineContext) : this(account, DefaultLogger("Bot(" + account.id + ")"), context)
suspend inline operator fun invoke(qq: Long, password: String): Bot = BotImpl(BotAccount(qq.toUInt(), password), context = coroutineContext)
operator fun invoke(qq: Long, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
@JvmSynthetic
operator fun invoke(qq: UInt, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
val contacts = ContactSystem()
operator fun invoke(account: BotAccount, context: CoroutineContext): Bot = BotImpl(account, context = context)
val network: BotNetworkHandler<*> get() = _network
private lateinit var _network: BotNetworkHandler<*>
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
init {
launch {
addInstance(this@Bot)
}
fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq)
}
override fun toString(): String = "Bot(${account.id})"
/**
* 账号信息
*/
val account: BotAccount
/**
* 日志记录器
*/
val logger: MiraiLogger
override val coroutineContext: CoroutineContext
// region contacts
/**
* 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友
*/
val qqs: ContactList<QQ>
/**
* 获取缓存的 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] 下的协程.
* 然后重新启动并尝试登录
*/
@JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
fun tryReinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable? = null
): Job = launch {
repeat(configuration.reconnectionRetryTimes) {
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
logger.info("Reconnected successfully")
return@launch
} else {
delay(configuration.reconnectPeriodMillis)
}
}
}
): Job
/**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
* 然后重新启动并尝试登录
*/
@JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
suspend fun reinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable? = null
): LoginResult {
logger.info("BotAccount: ${qqAccount.toLong()}")
logger.info("Initializing BotNetworkHandler")
try {
if (::_network.isInitialized) {
_network.close(cause)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
_network = TIMBotNetworkHandler(this@Bot.coroutineContext + configuration, this@Bot)
return _network.login()
}
): LoginResult
/**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程.
* 然后重新启动并尝试登录
*/
@JvmOverloads // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration,
cause: Throwable? = null
): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
): Deferred<LoginResult>
/**
* Bot 联系人管理.
*
* @see Bot.contacts
*/
inner class ContactSystem internal constructor() {
val bot: Bot get() = this@Bot
@UseExperimental(MiraiInternalAPI::class)
@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) }
}
}
}
// endregion
@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
}
}
fun close()
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
data class BotAccount(
val id: UInt,
val password: String
) {
constructor(id: Long, password: String) : this(id.toUInt(), password)
}
\ No newline at end of file
......@@ -68,15 +68,15 @@ Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749
* @param remark 好友备注
*/
@UseExperimental(ExperimentalContracts::class)
suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = bot.withSession {
return when (CanAddFriendPacket(bot.qqAccount, id, bot.sessionKey).sendAndExpect<CanAddFriendResponse>()) {
suspend fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = withSession {
return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect<CanAddFriendResponse>()) {
is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED
is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED
is CanAddFriendResponse.ReadyToAdd,
is CanAddFriendResponse.RequireVerification -> {
val key = RequestFriendAdditionKeyPacket(bot.qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(bot.qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
AddFriendResult.WAITING_FOR_APPROVE
} //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥
......@@ -87,7 +87,7 @@ suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remar
/*is CanAddFriendResponse.ReadyToAdd -> {
// TODO: 2019/11/11 这不需要验证信息的情况
//AddFriendPacket(bot.qqAccount, id, bot.sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
//AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
TODO()
}*/
}
......
@file:JvmMultifileClass
@file:JvmName("BotHelperKt")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai
......@@ -26,26 +26,8 @@ import kotlin.jvm.JvmOverloads
*/
//Contacts
suspend inline fun Bot.getQQ(@PositiveNumbers number: Long): QQ = this.contacts.getQQ(number)
suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id))
suspend inline fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number)
suspend inline fun Bot.getGroup(id: UInt): Group = this.contacts.getGroup(GroupId(id))
suspend inline fun Bot.getGroup(@PositiveNumbers id: Long): Group =
this.contacts.getGroup(GroupId(id.coerceAtLeastOrFail(0).toUInt()))
suspend inline fun Bot.getGroup(id: GroupId): Group = this.contacts.getGroup(id)
suspend inline fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.getGroup(internalId)
/**
* 取得群列表
*/
inline val Bot.groups: ContactList<Group> get() = this.contacts.groups
/**
* 取得好友列表
*/
inline val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
/**
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
......@@ -70,7 +52,7 @@ internal suspend inline fun Bot.sendPacket(packet: OutgoingPacket) =
* 使用在默认配置基础上修改的配置进行登录
*/
@UseExperimental(ExperimentalContracts::class)
suspend inline fun Bot.login(noinline configuration: BotConfiguration.() -> Unit): LoginResult {
suspend inline fun Bot.login(configuration: BotConfiguration.() -> Unit): LoginResult {
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
......@@ -91,7 +73,7 @@ suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() }
* 使用在默认配置基础上修改的配置进行登录, 返回 [this]
*/
@UseExperimental(ExperimentalContracts::class)
suspend inline fun Bot.alsoLogin(noinline configuration: BotConfiguration.() -> Unit): Bot {
suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bot {
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
......@@ -108,14 +90,6 @@ suspend inline fun Bot.alsoLogin(message: String): Bot {
}
}
/**
* 添加好友
*/
@UseExperimental(ExperimentalContracts::class)
@JvmOverloads
suspend inline fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult =
contacts.addFriend(id, message, remark)
/**
* 取得机器人的 QQ 号
*/
......
@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()
}
}
......@@ -8,7 +8,9 @@ import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.chain
import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
import net.mamoe.mirai.withSession
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
......@@ -54,55 +56,3 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R {
}
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)
}
\ No newline at end of file
@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)
......@@ -2,101 +2,50 @@
package net.mamoe.mirai.contact
fun GroupId.toInternalId(): GroupInternalId {//求你别出错
val left: Long = this.value.toString().let {
if (it.length < 6) {
return GroupInternalId(this.value)
}
it.substring(0, it.length - 6).toLong()
}
val right: Long = this.value.toString().let {
it.substring(it.length - 6).toLong()
import kotlin.math.pow
@Suppress("ObjectPropertyName")
private val `10EXP6` = 10.0.pow(6).toUInt()
fun GroupId.toInternalId(): GroupInternalId {
if (this.value <= `10EXP6`) {
return GroupInternalId(this.value)
}
val left: Long = this.value.toString().dropLast(6).toLong()
val right: Long = this.value.toString().takeLast(6).toLong()
return GroupInternalId(
when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toUInt()
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toUInt()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toUInt()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toUInt()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toUInt()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toUInt()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toUInt()
}
in 1..10 -> ((left + 202).toString() + right.toString()).toUInt()
in 11..19 -> ((left + 469).toString() + right.toString()).toUInt()
in 20..66 -> ((left + 208).toString() + right.toString()).toUInt()
in 67..156 -> ((left + 1943).toString() + right.toString()).toUInt()
in 157..209 -> ((left + 199).toString() + right.toString()).toUInt()
in 210..309 -> ((left + 389).toString() + right.toString()).toUInt()
in 310..499 -> ((left + 349).toString() + right.toString()).toUInt()
else -> this.value
}
)
}
fun GroupInternalId.toId(): GroupId = with(value) {
//求你别出错
var left: UInt = this.toString().let {
if (it.length < 6) {
return GroupId(value)
}
it.substring(0 until it.length - 6).toUInt()
fun GroupInternalId.toId(): GroupId = with(value.toString()) {
if (value < `10EXP6`) {
return GroupId(value)
}
val left: UInt = this.dropLast(6).toUInt()
return GroupId(when (left.toInt()) {
in 203..212 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 202u).toString() + right.toString()).toUInt()
return GroupId(
when (left.toInt()) {
in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 480..488 -> ((left - 469u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 2100..2146 -> ((left.toString().take(3).toUInt() - 208u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 2010..2099 -> ((left - 1943u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 2147..2199 -> ((left.toString().take(3).toUInt() - 199u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 4100..4199 -> ((left.toString().take(3).toUInt() - 389u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 3800..3989 -> ((left.toString().take(3).toUInt() - 349u).toString() + this.takeLast(7).toInt().toString()).toUInt()
else -> value
}
in 480..488 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 469u).toString() + right.toString()).toUInt()
}
in 2100..2146 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 208u).toString() + right.toString()).toUInt()
}
in 2010..2099 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 1943u).toString() + right.toString()).toUInt()
}
in 2147..2199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 199u).toString() + right.toString()).toUInt()
}
in 4100..4199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 389u).toString() + right.toString()).toUInt()
}
in 3800..3989 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 349u).toString() + right.toString()).toUInt()
}
else -> value
})
)
}
\ No newline at end of file
......@@ -3,6 +3,8 @@
package net.mamoe.mirai.contact.internal
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.data.Profile
......@@ -15,9 +17,7 @@ import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.logStacktrace
import net.mamoe.mirai.withSession
import kotlin.coroutines.CoroutineContext
......@@ -35,15 +35,12 @@ internal sealed class ContactImpl : Contact {
*/
@Suppress("FunctionName")
@PublishedApi
internal suspend fun Group(bot: Bot, groupId: GroupId, context: CoroutineContext): Group {
val info: RawGroupInfo = try {
bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, groupId.toInternalId(), sessionKey).sendAndExpect() }
} catch (e: Exception) {
e.logStacktrace()
error("Cannot obtain group info for id ${groupId.value}")
internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group =
GroupImpl(bot, groupId, context).apply {
this@apply.info = info.parseBy(this@apply)
launch { startUpdater() }
}
return GroupImpl(bot, groupId, context).apply { this.info = info.parseBy(this); startUpdater() }
}
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
......@@ -52,14 +49,15 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
override val internalId = GroupId(id).toInternalId()
internal lateinit var info: GroupInfo
internal lateinit var initialInfoJob: Job
override val owner: Member get() = info.owner
override val name: String get() = info.name
override val announcement: String get() = info.announcement
override val members: ContactList<Member> get() = info.members
override fun getMember(id: UInt): Member =
if (members.containsKey(id)) members[id]!!
else throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
override suspend fun sendMessage(message: MessageChain) {
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
......@@ -76,20 +74,18 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
@UseExperimental(MiraiInternalAPI::class)
override suspend fun startUpdater() {
subscribeAlways<MemberJoinEventPacket> {
// FIXME: 2019/11/29 非线程安全!!
members.mutable[it.member.id] = it.member
members.delegate.addLast(it.member)
}
subscribeAlways<MemberQuitEvent> {
// FIXME: 2019/11/29 非线程安全!!
members.mutable.remove(it.member.id)
members.delegate.remove(it.member)
}
}
override fun toString(): String = "Group(${this.id})"
}
@Suppress("FunctionName")
suspend inline fun QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { startUpdater() }
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } }
@PublishedApi
internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) :
......@@ -118,9 +114,9 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot:
override fun toString(): String = "QQ(${this.id})"
}
@Suppress("FunctionName")
suspend inline fun Member(delegate: QQ, group: Group, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, group, permission, coroutineContext).apply { startUpdater() }
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } }
/**
* 群成员
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
package net.mamoe.mirai.network
......@@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor(
*/
val gtk: Int get() = _gtk
suspend inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
suspend inline fun UInt.qq(): QQ = bot.getQQ(this)
inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
inline fun UInt.qq(): QQ = bot.getQQ(this)
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
......
......@@ -226,7 +226,9 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
return
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) {
......@@ -239,7 +241,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) }
.also { temporaryPacketHandlers.removeAll(it) }
}.forEach {
it.doReceiveWithoutExceptions(packet)
it.doReceiveCatchingExceptions(packet)
}
if (factory is SessionPacketFactory<*>) {
......@@ -286,7 +288,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
it::class.annotations.filterIsInstance<NoLog>().any()
}
}?.let {
bot.logger.verbose("Packet sent: ${it.packetId}")
bot.logger.verbose("Packet sent: ${it.name}")
}
PacketSentEvent(bot, packet).broadcast()
......
......@@ -60,7 +60,7 @@ internal class TemporaryPacketHandler<P : Packet, R>(
internal inline fun filter(session: BotSession, packet: Packet, sequenceId: UShort): Boolean =
expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) sequenceId == toSend.sequenceId else true
internal suspend inline fun doReceiveWithoutExceptions(packet: Packet) {
internal suspend inline fun doReceiveCatchingExceptions(packet: Packet) {
@Suppress("UNCHECKED_CAST")
val ret = try {
withContext(callerContext) {
......
......@@ -2,19 +2,6 @@
package net.mamoe.mirai.network.protocol.tim.packet
/**
* 包 ID. 除特殊外, [PacketFactory] 都需要这个注解来指定包 ID.
*/
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
internal annotation class AnnotatedId( // 注解无法在 JS 平台使用, 但现在暂不需要考虑 JS
val id: KnownPacketId
)
internal inline val AnnotatedId.value: UShort get() = id.value
/**
* 包的最后一次修改时间, 和分析时使用的 TIM 版本
*/
......
......@@ -24,7 +24,7 @@ internal class OutgoingPacket(
val sequenceId: UShort,
internal val delegate: ByteReadPacket
) : Packet {
private val name: String by lazy {
val name: String by lazy {
name ?: packetId.toString()
}
}
......
......@@ -10,7 +10,6 @@ import kotlinx.io.pool.useInstance
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.read
......@@ -26,20 +25,13 @@ import net.mamoe.mirai.utils.readProtoMap
*/
internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) {
/**
* 2 Ubyte.
* 读取注解 [AnnotatedId]
*/
private val annotatedId: AnnotatedId
get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
?: error("Annotation AnnotatedId not found for class ${this::class.simpleName}")
@Suppress("PropertyName")
internal var _id: PacketId = NullPacketId
// TODO: 2019/11/22 修改 包 ID 为参数
/**
* 包 ID.
*/
open val id: PacketId by lazy { annotatedId.id }
open val id: PacketId get() = _id
/**
* **解码**服务器的回复数据包
......
......@@ -55,35 +55,35 @@ internal inline class IgnoredPacketId constructor(override val value: UShort) :
* 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id
*/
@Suppress("unused")
internal enum class KnownPacketId(override inline val value: UShort, override inline val factory: PacketFactory<*, *>) :
internal enum class KnownPacketId(override val value: UShort, override val factory: PacketFactory<*, *>) :
PacketId {
inline TOUCH(0x0825u, TouchPacket),
inline SESSION_KEY(0x0828u, RequestSessionPacket),
inline LOGIN(0x0836u, SubmitPasswordPacket),
inline CAPTCHA(0x00BAu, CaptchaPacket),
inline SERVER_EVENT_1(0x00CEu, EventPacketFactory),
inline SERVER_EVENT_2(0x0017u, EventPacketFactory),
inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
inline HEARTBEAT(0x0058u, HeartbeatPacket),
inline S_KEY(0x001Du, RequestSKeyPacket),
inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
inline GROUP_PACKET(0x0002u, GroupPacket),
inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
inline ADD_FRIEND(0x00A8u, AddFriendPacket),
inline REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
inline GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
inline FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
inline QUERY_NICKNAME(0x0126u, QueryNicknamePacket),
inline QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket),
inline QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket)
TOUCH(0x0825u, TouchPacket),
SESSION_KEY(0x0828u, RequestSessionPacket),
LOGIN(0x0836u, SubmitPasswordPacket),
CAPTCHA(0x00BAu, CaptchaPacket),
SERVER_EVENT_1(0x00CEu, EventPacketFactory),
SERVER_EVENT_2(0x0017u, EventPacketFactory),
FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
HEARTBEAT(0x0058u, HeartbeatPacket),
S_KEY(0x001Du, RequestSKeyPacket),
ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
GROUP_PACKET(0x0002u, GroupPacket),
SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
ADD_FRIEND(0x00A8u, AddFriendPacket),
REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
QUERY_NICKNAME(0x0126u, QueryNicknamePacket),
QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket),
QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket)
// 031F 查询 "新朋友" 记录
......@@ -92,5 +92,9 @@ internal enum class KnownPacketId(override inline val value: UShort, override in
;
init {
factory._id = this
}
override fun toString(): String = (factory::class.simpleName ?: this.name) + "(${value.toUHexString()})"
}
......@@ -19,7 +19,6 @@ import net.mamoe.mirai.withSession
* - 昵称
* - 共同群内的群名片
*/
@AnnotatedId(KnownPacketId.QUERY_PREVIOUS_NAME)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() {
operator fun invoke(
......@@ -77,7 +76,6 @@ class PreviousNameList(
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
operator fun invoke(
......@@ -155,7 +153,6 @@ inline class FriendAdditionKey(val value: IoBuffer)
/**
* 请求一个 32 位 Key, 在添加好友时发出
*/
@AnnotatedId(KnownPacketId.REQUEST_FRIEND_ADDITION_KEY)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() {
operator fun invoke(
......@@ -182,7 +179,6 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri
/**
* 请求添加好友
*/
@AnnotatedId(KnownPacketId.ADD_FRIEND)
internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>() {
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
@Suppress("FunctionName")
......
......@@ -92,7 +92,6 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse {
* - 服务器已经存有这个图片
* - 服务器未存有, 返回一个 key 用于客户端上传
*/
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.11.16", timVersion = "2.3.2 (21173)")
internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
@Suppress("FunctionName")
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
// 0001
// 已确认 查好友列表的列表
......@@ -15,6 +23,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
// 00 00
internal inline class FriendListList(val delegate: List<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
......
......@@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.ACCOUNT_INFO)
internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
operator fun invoke(
qq: UInt,
......
......@@ -99,7 +99,6 @@ internal class ImageUploadInfo(
/**
* 获取 Image Id 和上传用的一个 uKey
*/
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.11.22", timVersion = "2.3.2 (21173)")
internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
......
......@@ -10,6 +10,7 @@ import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
......@@ -36,6 +37,10 @@ class GroupInfo(
"GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}"
}
internal object GroupNotFound : GroupPacket.InfoResponse {
override fun toString(): String = "GroupPacket.InfoResponse.GroupNotFound"
}
internal data class RawGroupInfo(
val group: UInt,
val owner: UInt,
......@@ -45,17 +50,20 @@ internal data class RawGroupInfo(
* 含群主
*/
val members: Map<UInt, MemberPermission>
) : GroupPacket.GroupPacketResponse {
) : GroupPacket.InfoResponse {
@UseExperimental(MiraiInternalAPI::class)
suspend inline fun parseBy(group: Group): GroupInfo = group.bot.withSession {
fun parseBy(group: Group): GroupInfo = group.bot.withSession {
val memberList = LockFreeLinkedList<Member>()
members.forEach { entry: Map.Entry<UInt, MemberPermission> ->
memberList.addLast(entry.key.qq().let { group.Member(it, entry.value, it.coroutineContext) })
}
return GroupInfo(
group,
this@RawGroupInfo.owner.qq().let { Member(it, group, MemberPermission.OWNER, it.coroutineContext) },
this@RawGroupInfo.owner.qq().let { group.Member(it, MemberPermission.OWNER, it.coroutineContext) },
this@RawGroupInfo.name,
this@RawGroupInfo.announcement,
ContactList(this@RawGroupInfo.members.mapValuesTo(MutableContactList()) { entry: Map.Entry<UInt, MemberPermission> ->
entry.key.qq().let { Member(it,group, entry.value, it.coroutineContext) }
})
ContactList(memberList)
)
}
}
......@@ -71,7 +79,6 @@ inline class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, G
}
@Suppress("FunctionName")
@AnnotatedId(KnownPacketId.GROUP_PACKET)
internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
fun Message(
......@@ -159,6 +166,8 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
override fun toString(): String = "GroupPacket.MuteResponse"
}
internal interface InfoResponse : Packet, GroupPacketResponse
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
@UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(
......@@ -166,7 +175,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
sequenceId: UShort,
handler: BotNetworkHandler<*>
): GroupPacketResponse {
return when (readUByte().toUInt()) {
return when (val packetType = readUByte().toUInt()) {
0x2Au -> MessageResponse
0x7Eu -> MuteResponse // 成功: 7E 00 22 96 29 7B;
......@@ -179,180 +188,185 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
}
0x72u -> {
discardExact(1) // 00
discardExact(4) // group internal id
val group = readUInt() // group id
discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
val owner = readUInt()
discardExact(22)
val groupName = readUByteLVString()
discardExact(readUByte()) // 00
discardExact(readUByte()) // 00
val announcement = readUByteLVString()
discardExact(readUByte()) // 00
discardExact(readUByte()) // 00
discardExact(readUByte()) // 38 ... 未知
discardExact(readUByte()) // 00
discardExact(readUByte()) // 0A ...
discardExact(38)
val stop = readUInt() // 标记读取群成员的结束
discardExact(1) // 00
val members = mutableMapOf<UInt, MemberPermission>()
do {
val qq = readUInt()
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
if (qq == owner) {
continue
}
when (val flag = readByte().toInt()) {
0x02 -> GroupNotFound
0x00 -> debugPrintIfFail("解析群信息") {
discardExact(4) // group internal id
val group = readUInt() // group id
discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
val owner = readUInt()
discardExact(22)
val groupName = readUByteLVString()
val permission = when (status.takeLowestOneBit()) {
1 -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
discardExact(readUByte()) // 00
discardExact(readUByte()) // 00
val announcement = readUByteLVString()
discardExact(readUByte()) // 00
discardExact(readUByte()) // 00
discardExact(readUByte()) // 38 ... 未知
discardExact(readUByte()) // 00
discardExact(readUByte()) // 0A ...
discardExact(38)
val stop = readUInt() // 标记读取群成员的结束
discardExact(1) // 00
val members = mutableMapOf<UInt, MemberPermission>()
do {
val qq = readUInt()
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
if (qq == owner) {
continue
}
val permission = when (status.takeLowestOneBit()) {
1 -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
}
members[qq] = permission
} while (qq != stop && remaining != 0L)
members[owner] = MemberPermission.OWNER
return RawGroupInfo(group, owner, groupName, announcement, members)
/*
* 群 Mirai
*
* 00 00 00 03 01 41 00 04 01 40 23 04 40
* B1 89 BE 09 群主
*
* 02 00 00 00 00 00 00 00 00 00 21 00 C8 01
* 00 00 00 01 00 00
* 00 2D
*
* 06 4D 69 72 61 69 20 群名
* 00
* 00
* 00
* 00
* 00
* 38 87 5F D8 E8 D4 E9 79 73 8A A4 21 1C 3E 2C 43 D0 23 55 53 49 D3 1C DB F6 1F 84 59 77 66 DA 9C D7 26 0F E3 BD E1 F2 B9 29 D1 F6 97 1C 42 5E B0 AF 09 51 72 DA 03 37 AB 65
* 00
* 0A 00 00 00 00 06 00 03 00 02 00
* 01 00 04 00 04 00 00 00 01 00 05 00 04 5D 90 A7 25 00 06 00 04 04 08 00 00 00 07 00 04 00 00 05 80 00 09 00 01 01
* B1 89 BE 09 00
* 3E 03 3F A2 00 01
* 48 76 54 DC 00 00
* 76 E4 B8 DD 00 00
* 89 1A 5E AC 00 00
* B1 89 BE 09 00 00
*/
/*
* 群 XkWhXi
*
* 00 00 00 03 01 41 00 04 01 40 21 04 40
* 3E 03 3F A2 群主
*
* 02 00 00 00 01 00 00 00 00 27 1A 00 C8 01
* 00 00 00 01 00 00
* F3 C8
*
* 06 58 6B 57 68 58 69
* 00
* 00
* 3B E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 0A E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6
* 00
* 00
* 38 EB 3B A5 90 AC E3 70 1F 42 51 B4 72 81 C8 F5 5A D8 80 69 B6 76 AD A4 AA CC 6A 17 4C 79 81 FF 82 04 BA 13 CE 28 DA 6C 3F 41 77 C0 77 40 B5 87 8E EE 29 20 65 FC 2D FF 63
* 00
* 0A 00 00 00 00 06 00 03 00 02 00
* 01 00 04 00 04 00 00 00 05 00 05 00 04 57 94 6F 41 00 06 00 04 04 08 00 10 00 07 00 04 00 00 04 04 00 09 00 01 00
* B1 89 BE 09 00 2D 5C 53 A6 00 01 2F 9B 1C F2 00 00 35 49 95 D1 00 01 3B FA 06 9F 00 00 3E 03 3F A2 00 00 42 C4 32 63 00 01 59 17 3E 05 00 01 6A 89 3E 3E 00 00 6D D7 4E CA 00 00 76 E4 B8 DD 00 00 7C BB 60 3C 00 01 7C BC D3 C1 00 01 87 73 86 9D 00 00 90 19 72 65 00 00 97 30 9A 6B 00 00 9C B1 E5 55 00 01 B1 89 BE 09 00 01
*/
/*
* 群 20秃顶28火葬30重生异世
*
*
*/
/*
* 群 Big convene' (与上面两个来自不同 bot)
*
* 00 00 00 03 01 01 00 04 01 00 80 01 40
* 6C 18 F5 DA 群主
*
* 02 00 00 27 1B 00 00 00 00 27 1B 01 F4 01
* 00 00 00 01 00 00
* 0F 1F
*
* 0C 42 69 67 20 63 6F 6E 76 65 6E 65 27 00 群名
* 00 96 E6 AF 95 E4 B8 9A E4 BA 86 EF BC 8C E5 B8 8C E6 9C 9B E5 A4 A7 E5 AE B6 E8 83 BD E5 A4 9F E5 83 8F E4 BB A5 E5 89 8D E9 82 A3 E6 A0 B7 E5 BC 80 E5 BF 83 EF BC 8C E5 AD A6 E4 B9 A0 E8 BF 9B E6 AD A5 EF BC 8C E5 A4 A9 E5 A4 A9 E5 BF AB E4 B9 90 E3 80 82 E6 AD A4 E7 BE A4 E7 A6 81 E6 AD A2 E9 AA 82 E4 BA BA EF BC 8C E5 88 B7 E5 B1 8F E6 9A B4 E5 8A 9B EF BC 8C E8 BF 9D E8 A7 84 E8 80 85 E7 A6 81 E8 A8 80 EF BC 8C E4 B8 A5 E9 87 8D E8 80 85 E5 B0 B1
* 76 E8 BF 9B E7 BE A4 E6 97 B6 EF BC 8C E8 AF B7 E4 BF AE E6 94 B9 E6 AD A3 E7 A1 AE E5 A7 93 E5 90 8D E3 80 82 E4 B8 8D E8 83 BD 54 E5 90 8C E5 AD A6 EF BC 8C E5 A4 AA E8 BF 87 E5 88 86 E7 9A 84 54 21 28 E4 BA 92 E8 B5 9E E7 BE A4 EF BC 8C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF E8 81 8A E5 A4 A9 E8 80 85 E5 8F AF E4 BB A5 E4 BA 92 E8 B5 9E E5 AF B9 E6 96 B9
* 00 38 D9 FD F5 21 A6 1F 8D 61 37 A1 7A 92 91 2A 2C 71 46 A9 B9 1C 45 EB 38 74 4A 74 EA 77 7D 14 DB 12 D0 B0 09 C2 AA 22 16 F1 D0 B9 97 21 F0 5A A0 06 59 A7 3B 2F 32 D2 B8 E3
* 00 0F 00 00 00 00 06 00 03 00 02 01 01 00 04 00 04 00 00 00 15 00 05 00 04 52 7C C5 7C 00 06 00 04 00 00 00 20 00 07 00 04 00 00 00 00 00 09 00 01 00
*
* C5 15 BE BE 00 ???为啥这个只有一个呢
* 1C ED 9F 9B 00 00
* 26 D0 E1 3A 00 00
* 2D 5C 53 A6 00 01 自己 管理员
* 2D BD 28 D2 00 00
* 2E 94 76 3E 00 00
* 35 F3 BC F2 00 00
* 37 D6 91 AB 00 00
* 3A 60 1C 3E 00 80 10000000 群员, 好友
* 3A 86 EA A3 00 48 01001000 群员 手机在线
* 3D 7F E7 70 00 00
* 3E 03 3F A2 00 09 00001001 好友, 特别关心, TIM PC 在线, 管理员
* 41 47 0C DD 00 40 01000000 群员, 离线
* 41 B6 32 A8 00 80
* 44 C8 DA 23 00 00
* 45 3E 1B 6A 00 80 10000000 群员 手机在线
* 45 C6 59 E9 00 C0 群员
* 4A BD C6 F9 00 00
* 4C 67 45 E8 00 00
* 4E AD C2 C2 00 80
* 4F A0 F7 EC 00 80
* 50 CB 11 E8 00 00
* 58 22 21 90 00 00
* 59 17 3E 05 00 01 管理员 好友
* 5E 74 48 D9 00 00
* 5E A2 B5 88 00 00
* 66 A1 32 9B 00 40
* 68 07 29 0A 00 00
* 68 0F EF 4F 00 00
* 69 8B 14 F3 00 80
* 6A A5 27 4E 00 00
* 6C 11 A0 89 00 81 10000001 管理员
* 6C 18 F5 DA 00 08 群主
* 6C 21 F8 E2 00 01 管理员
* 71 F8 F5 18 00 00
* 72 0B CC B6 00 00
* 75 53 38 DF 00 00
* 7A A1 8B 82 00 00
* 7C 8C 1D 1B 00 00
* 7C BC D3 C1 00 00
* 84 2D B8 5F 00 00
* 88 4C 33 76 00 00
* 8C C8 0D 43 00 00
* 90 B8 65 22 00 00
* 91 54 89 E9 00 00
* 9C E6 93 A5 00 01 管理员
* 9D 59 6A 36 00 00
* 9D 63 81 5C 00 00
* 9E 31 AF AC 00 00
* 9E 69 86 25 00 80
* A1 FD CA 2D 00 00
* A5 22 5C 48 00 00
* A5 F2 9A B7 00 00
* AF 25 74 9E 00 01
* B1 50 24 00 00 00
* B2 BD 81 A9 00 00
* B5 0E B3 DD 00 00
* B9 BF 0D BC 00 00
* C5 15 BE BE 00 00
*/
}
members[qq] = permission
} while (qq != stop && remaining != 0L)
members[owner] = MemberPermission.OWNER
return RawGroupInfo(group, owner, groupName, announcement, members)
/*
* 群 Mirai
*
* 00 00 00 03 01 41 00 04 01 40 23 04 40
* B1 89 BE 09 群主
*
* 02 00 00 00 00 00 00 00 00 00 21 00 C8 01
* 00 00 00 01 00 00
* 00 2D
*
* 06 4D 69 72 61 69 20 群名
* 00
* 00
* 00
* 00
* 00
* 38 87 5F D8 E8 D4 E9 79 73 8A A4 21 1C 3E 2C 43 D0 23 55 53 49 D3 1C DB F6 1F 84 59 77 66 DA 9C D7 26 0F E3 BD E1 F2 B9 29 D1 F6 97 1C 42 5E B0 AF 09 51 72 DA 03 37 AB 65
* 00
* 0A 00 00 00 00 06 00 03 00 02 00
* 01 00 04 00 04 00 00 00 01 00 05 00 04 5D 90 A7 25 00 06 00 04 04 08 00 00 00 07 00 04 00 00 05 80 00 09 00 01 01
* B1 89 BE 09 00
* 3E 03 3F A2 00 01
* 48 76 54 DC 00 00
* 76 E4 B8 DD 00 00
* 89 1A 5E AC 00 00
* B1 89 BE 09 00 00
*/
/*
* 群 XkWhXi
*
* 00 00 00 03 01 41 00 04 01 40 21 04 40
* 3E 03 3F A2 群主
*
* 02 00 00 00 01 00 00 00 00 27 1A 00 C8 01
* 00 00 00 01 00 00
* F3 C8
*
* 06 58 6B 57 68 58 69
* 00
* 00
* 3B E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 0A E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6
* 00
* 00
* 38 EB 3B A5 90 AC E3 70 1F 42 51 B4 72 81 C8 F5 5A D8 80 69 B6 76 AD A4 AA CC 6A 17 4C 79 81 FF 82 04 BA 13 CE 28 DA 6C 3F 41 77 C0 77 40 B5 87 8E EE 29 20 65 FC 2D FF 63
* 00
* 0A 00 00 00 00 06 00 03 00 02 00
* 01 00 04 00 04 00 00 00 05 00 05 00 04 57 94 6F 41 00 06 00 04 04 08 00 10 00 07 00 04 00 00 04 04 00 09 00 01 00
* B1 89 BE 09 00 2D 5C 53 A6 00 01 2F 9B 1C F2 00 00 35 49 95 D1 00 01 3B FA 06 9F 00 00 3E 03 3F A2 00 00 42 C4 32 63 00 01 59 17 3E 05 00 01 6A 89 3E 3E 00 00 6D D7 4E CA 00 00 76 E4 B8 DD 00 00 7C BB 60 3C 00 01 7C BC D3 C1 00 01 87 73 86 9D 00 00 90 19 72 65 00 00 97 30 9A 6B 00 00 9C B1 E5 55 00 01 B1 89 BE 09 00 01
*/
/*
* 群 20秃顶28火葬30重生异世
*
*
*/
/*
* 群 Big convene' (与上面两个来自不同 bot)
*
* 00 00 00 03 01 01 00 04 01 00 80 01 40
* 6C 18 F5 DA 群主
*
* 02 00 00 27 1B 00 00 00 00 27 1B 01 F4 01
* 00 00 00 01 00 00
* 0F 1F
*
* 0C 42 69 67 20 63 6F 6E 76 65 6E 65 27 00 群名
* 00 96 E6 AF 95 E4 B8 9A E4 BA 86 EF BC 8C E5 B8 8C E6 9C 9B E5 A4 A7 E5 AE B6 E8 83 BD E5 A4 9F E5 83 8F E4 BB A5 E5 89 8D E9 82 A3 E6 A0 B7 E5 BC 80 E5 BF 83 EF BC 8C E5 AD A6 E4 B9 A0 E8 BF 9B E6 AD A5 EF BC 8C E5 A4 A9 E5 A4 A9 E5 BF AB E4 B9 90 E3 80 82 E6 AD A4 E7 BE A4 E7 A6 81 E6 AD A2 E9 AA 82 E4 BA BA EF BC 8C E5 88 B7 E5 B1 8F E6 9A B4 E5 8A 9B EF BC 8C E8 BF 9D E8 A7 84 E8 80 85 E7 A6 81 E8 A8 80 EF BC 8C E4 B8 A5 E9 87 8D E8 80 85 E5 B0 B1
* 76 E8 BF 9B E7 BE A4 E6 97 B6 EF BC 8C E8 AF B7 E4 BF AE E6 94 B9 E6 AD A3 E7 A1 AE E5 A7 93 E5 90 8D E3 80 82 E4 B8 8D E8 83 BD 54 E5 90 8C E5 AD A6 EF BC 8C E5 A4 AA E8 BF 87 E5 88 86 E7 9A 84 54 21 28 E4 BA 92 E8 B5 9E E7 BE A4 EF BC 8C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF E8 81 8A E5 A4 A9 E8 80 85 E5 8F AF E4 BB A5 E4 BA 92 E8 B5 9E E5 AF B9 E6 96 B9
* 00 38 D9 FD F5 21 A6 1F 8D 61 37 A1 7A 92 91 2A 2C 71 46 A9 B9 1C 45 EB 38 74 4A 74 EA 77 7D 14 DB 12 D0 B0 09 C2 AA 22 16 F1 D0 B9 97 21 F0 5A A0 06 59 A7 3B 2F 32 D2 B8 E3
* 00 0F 00 00 00 00 06 00 03 00 02 01 01 00 04 00 04 00 00 00 15 00 05 00 04 52 7C C5 7C 00 06 00 04 00 00 00 20 00 07 00 04 00 00 00 00 00 09 00 01 00
*
* C5 15 BE BE 00 ???为啥这个只有一个呢
* 1C ED 9F 9B 00 00
* 26 D0 E1 3A 00 00
* 2D 5C 53 A6 00 01 自己 管理员
* 2D BD 28 D2 00 00
* 2E 94 76 3E 00 00
* 35 F3 BC F2 00 00
* 37 D6 91 AB 00 00
* 3A 60 1C 3E 00 80 10000000 群员, 好友
* 3A 86 EA A3 00 48 01001000 群员 手机在线
* 3D 7F E7 70 00 00
* 3E 03 3F A2 00 09 00001001 好友, 特别关心, TIM PC 在线, 管理员
* 41 47 0C DD 00 40 01000000 群员, 离线
* 41 B6 32 A8 00 80
* 44 C8 DA 23 00 00
* 45 3E 1B 6A 00 80 10000000 群员 手机在线
* 45 C6 59 E9 00 C0 群员
* 4A BD C6 F9 00 00
* 4C 67 45 E8 00 00
* 4E AD C2 C2 00 80
* 4F A0 F7 EC 00 80
* 50 CB 11 E8 00 00
* 58 22 21 90 00 00
* 59 17 3E 05 00 01 管理员 好友
* 5E 74 48 D9 00 00
* 5E A2 B5 88 00 00
* 66 A1 32 9B 00 40
* 68 07 29 0A 00 00
* 68 0F EF 4F 00 00
* 69 8B 14 F3 00 80
* 6A A5 27 4E 00 00
* 6C 11 A0 89 00 81 10000001 管理员
* 6C 18 F5 DA 00 08 群主
* 6C 21 F8 E2 00 01 管理员
* 71 F8 F5 18 00 00
* 72 0B CC B6 00 00
* 75 53 38 DF 00 00
* 7A A1 8B 82 00 00
* 7C 8C 1D 1B 00 00
* 7C BC D3 C1 00 00
* 84 2D B8 5F 00 00
* 88 4C 33 76 00 00
* 8C C8 0D 43 00 00
* 90 B8 65 22 00 00
* 91 54 89 E9 00 00
* 9C E6 93 A5 00 01 管理员
* 9D 59 6A 36 00 00
* 9D 63 81 5C 00 00
* 9E 31 AF AC 00 00
* 9E 69 86 25 00 80
* A1 FD CA 2D 00 00
* A5 22 5C 48 00 00
* A5 F2 9A B7 00 00
* AF 25 74 9E 00 01
* B1 50 24 00 00 00
* B2 BD 81 A9 00 00
* B5 0E B3 DD 00 00
* B9 BF 0D BC 00 00
* C5 15 BE BE 00 00
*/
else -> unsupportedFlag("GroupPacketResponse typed 0x72", flag.toUHexString())
}
}
else -> unsupported()
else -> unsupportedType("GroupPacketResponse", packetType.toUHexString())
}
}
}
\ No newline at end of file
......@@ -17,7 +17,6 @@ inline class NicknameMap(val delegate: Map<UInt, String>) : Packet
/**
* 批量查询昵称.
*/
@AnnotatedId(KnownPacketId.QUERY_NICKNAME)
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>() {
//00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
operator fun invoke(
......@@ -159,7 +157,6 @@ internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>()
*
* @see Profile
*/
@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS)
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 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(
val qq: UInt,
val profile: Profile
......
......@@ -15,7 +15,6 @@ import net.mamoe.mirai.utils.io.writeZero
*/
inline class FriendNameRemark(val value: String) : Packet
@AnnotatedId(KnownPacketId.QUERY_FRIEND_REMARK)
internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() {
/**
* 查询好友的备注
......
......@@ -11,7 +11,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.md5
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
operator fun invoke(
......
......@@ -7,9 +7,7 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.AnnotatedId
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
......@@ -23,7 +21,6 @@ data class FriendStatusChanged(
/**
* 好友在线状态改变
*/
@AnnotatedId(KnownPacketId.FRIEND_ONLINE_STATUS_CHANGE)
internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {
......
......@@ -13,7 +13,6 @@ import net.mamoe.mirai.contact.internal.MemberImpl
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact
......@@ -76,7 +75,7 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
discardExact(1) // 01
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) {
MemberJoinEventPacket(member, null)
......
@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
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.toUHexString
// region mute
/**
......@@ -59,6 +61,7 @@ class MemberUnmuteEvent(
/**
* 机器人被解除禁言事件
*/
@Suppress("SpellCheckingInspection")
class BeingUnmutedEvent(
override val operator: Member
) : UnmuteEvent() {
......@@ -73,15 +76,24 @@ sealed class UnmuteEvent : EventOfMute() {
// 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 {
abstract val operator: Member
abstract val group: Group
}
// TODO: 2019/12/14 这可能不只是禁言的包.
internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute {
//取消
//00 00 00 11 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
//00 00 00 11 00
// 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
// 01 01
// 22 96 29 7B
// 0C 01
......@@ -92,7 +104,8 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
// 00 00 00 00
// 禁言
//00 00 00 11 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
//00 00 00 11 00
// 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
// 01
// 01
// 22 96 29 7B
......@@ -104,28 +117,42 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
// 01
// 76 E4 B8 DD
// 00 27 8D 00
discardExact(19)
discardExact(2)
val group = bot.getGroup(readUInt())
discardExact(2)
val operator = group.getMember(readUInt())
discardExact(4) //time
discardExact(2)
val memberQQ = readUInt()
val durationSeconds = readUInt().toInt()
return if (durationSeconds == 0) {
if (memberQQ == bot.qqAccount) {
BeingUnmutedEvent(operator)
} else {
MemberUnmuteEvent(group.getMember(memberQQ), operator)
discardExact(3)
return when (val flag = readByte().toUInt()) {
0x0Eu -> {
//00 00 00 0E 00 08 00 02 00 01 00
// 0A 00 04 01 00 00 00 35 DB 60 A2 11 00 3E 08 07 20 A2 C1 ED AE 03 5A 34 08 A2 FF 8C F0 03 1A 19 08 F4 0E 10 FE 8C D3 EF 05 18 84 A1 F8 F9 06 20 00 28 00 30 A2 FF 8C F0 03 2A 0D 08 00 12 09 08 F4 0E 10 00 18 01 20 00 30 00 38 00
Unknown0x02DCPacketFlag0x0EMaybeMutePacket
}
} else {
if (memberQQ == bot.qqAccount) {
BeingMutedEvent(durationSeconds, operator)
} else {
MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator)
0x11u -> {
discardExact(15)
discardExact(2)
val group = bot.getGroup(readUInt())
discardExact(2)
val operator = group.getMember(readUInt())
discardExact(4) //time
discardExact(2)
val memberQQ = readUInt()
val durationSeconds = readUInt().toInt()
if (durationSeconds == 0) {
if (memberQQ == bot.qqAccount) {
BeingUnmutedEvent(operator)
} else {
MemberUnmuteEvent(group.getMember(memberQQ), operator)
}
} else {
if (memberQQ == bot.qqAccount) {
BeingMutedEvent(durationSeconds, operator)
} else {
MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator)
}
}
}
else -> error("Unsupported flag in 0x02DC packet. flag=$flag, remainning=${readBytes().toUHexString()}")
}
}
}
\ No newline at end of file
......@@ -11,7 +11,6 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
......@@ -117,6 +116,9 @@ data class GroupMessage(
override val message: MessageChain
) : MessagePacket<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
inline fun At.member(): Member = group.getMember(this.target)
......
......@@ -12,7 +12,6 @@ internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
override val value: ByteArray = TIMProtocol.key00BA
}
@AnnotatedId(KnownPacketId.CAPTCHA)
internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(CaptchaKey) {
/**
* 请求验证码传输
......
......@@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
/**
* 改变在线状态: "我在线上", "隐身" 等
*/
@AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS)
internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
operator fun invoke(
bot: UInt,
......
......@@ -13,7 +13,6 @@ import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ
@NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
operator fun invoke(
bot: UInt,
......@@ -31,5 +30,4 @@ internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>(
}
@NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
internal object HeartbeatPacketResponse : Packet, Subscribable
\ No newline at end of file
......@@ -44,7 +44,6 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr
/**
* 提交密码
*/
@AnnotatedId(KnownPacketId.LOGIN)
internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
operator fun invoke(
bot: UInt,
......
......@@ -23,7 +23,6 @@ internal inline class SKey(
* 请求 `SKey`
* SKey 用于 http api
*/
@AnnotatedId(KnownPacketId.S_KEY)
internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
operator fun invoke(
bot: UInt,
......
......@@ -9,7 +9,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.localIpAddress
@AnnotatedId(KnownPacketId.SESSION_KEY)
internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
operator fun invoke(
bot: UInt,
......
......@@ -20,7 +20,6 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.TOUCH)
internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
operator fun invoke(
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(
var loginTime: Int,
val loginIP: String,
......
......@@ -62,6 +62,12 @@ class BotConfiguration : CoroutineContext.Element {
* 验证码处理器
*/
var captchaSolver: CaptchaSolver = DefaultCaptchaSolver
/**
* 登录完成后几秒会收到好友消息的历史记录,
* 这些历史记录不会触发事件.
* 这个选项为是否把这些记录添加到日志
*/
var logPreviousMessages: Boolean = false
companion object Key : CoroutineContext.Key<BotConfiguration> {
/**
......
@file:Suppress("unused", "UNUSED_PARAMETER")
package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Group
/**
* 在获取 [Group] 对象等操作时可能出现的异常
*/
class GroupNotFoundException : Exception {
constructor()
constructor(message: String?)
constructor(message: String?, cause: Throwable?)
constructor(cause: Throwable?)
}
open class MiraiInternalException : Exception {
constructor()
constructor(message: String?)
constructor(message: String?, cause: Throwable?)
constructor(cause: Throwable?)
}
\ No newline at end of file
@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
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils
......
......@@ -54,6 +54,17 @@ internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
return bytes.toReadPacket()
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal inline fun <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"))
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket {
val bytes = this.readBytes()
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils.io
......@@ -95,7 +95,7 @@ fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMa
}
/**
* 读扁平的 tag-UVarInt map. 重复的 tag 将不会只保留最后一个
* 读扁平的 tag-UVarInt map. 重复的 tag 将只保留最后一个
*
* tag: UByte
* value: UVarint
......@@ -140,10 +140,11 @@ fun Map<UInt, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 1) =
}
})
@Suppress("NOTHING_TO_INLINE")
internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported")
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}")
internal inline fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}")
internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed")
@JvmName("printTLVStringMap")
......
package net.mamoe.mirai.contact
import net.mamoe.mirai.test.shouldBeEqualTo
import org.junit.Test
import kotlin.random.Random
internal class GroupIdConversionsKtTest {
@UseExperimental(ExperimentalUnsignedTypes::class)
@Test
fun toInternalId() {
repeat(1000000) { _ ->
val it = Random.nextInt()
try {
GroupId(it.toUInt()).toInternalId() shouldBeEqualTo GroupId(it.toUInt()).toInternalIdOld()
} catch (e: Throwable) {
println(it)
throw e
}
}
}
@UseExperimental(ExperimentalUnsignedTypes::class)
@Test
fun toId() {
repeat(1000000) { _ ->
val it = Random.nextInt()
try {
GroupInternalId(it.toUInt()).toId() shouldBeEqualTo GroupInternalId(it.toUInt()).toIdOld()
} catch (e: Throwable) {
println(it)
throw e
}
}
}
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private fun GroupId.toInternalIdOld(): GroupInternalId {//求你别出错
val left: Long = this.value.toString().let {
if (it.length <= 6) {
return GroupInternalId(this.value)
}
it.substring(0, it.length - 6).toLong()
}
val right: Long = this.value.toString().let {
it.substring(it.length - 6).toLong()
}
return GroupInternalId(
when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toUInt()
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toUInt()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toUInt()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toUInt()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toUInt()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toUInt()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toUInt()
}
else -> this.value
}
)
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private fun GroupInternalId.toIdOld(): GroupId = with(value) {
//求你别出错
var left: UInt = this.toString().let {
if (it.length <= 6) {
return GroupId(value)
}
it.substring(0 until it.length - 6).toUInt()
}
return GroupId(when (left.toInt()) {
in 203..212 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 202u).toString() + right.toString()).toUInt()
}
in 480..488 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 469u).toString() + right.toString()).toUInt()
}
in 2100..2146 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 208u).toString() + right.toString()).toUInt()
}
in 2010..2099 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 1943u).toString() + right.toString()).toUInt()
}
in 2147..2199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 199u).toString() + right.toString()).toUInt()
}
in 4100..4199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 389u).toString() + right.toString()).toUInt()
}
in 3800..3989 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 349u).toString() + right.toString()).toUInt()
}
else -> value
})
}
\ No newline at end of file
@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()
}
}
}
}
\ No newline at end of file
......@@ -41,7 +41,7 @@ fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id
dependencies {
implementation(project(":mirai-core"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
// runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
......
......@@ -3,7 +3,7 @@ apply plugin: "java"
dependencies {
api project(":mirai-core")
runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
// runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
}
......@@ -4,8 +4,8 @@ apply plugin: "application"
dependencies {
api project(":mirai-core")
runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
//runtime files("../../mirai-core/build/classes/atomicfu/jvm/main") // classpath is not set correctly by IDE
//runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment