mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-08 17:20:11 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
d047a3635c
13
UpdateLog.md
13
UpdateLog.md
@ -4,6 +4,19 @@
|
||||
|
||||
开发版本. 频繁更新, 不保证高稳定性
|
||||
|
||||
### `0.8.0` *2019/12/14*
|
||||
协议
|
||||
- 现在查询群资料时可处理群号无效的情况
|
||||
- 现在能正常分辨禁言事件包
|
||||
|
||||
功能
|
||||
- 增加无锁链表: LockFreeLinkedList, 并将 ContactList 的实现改为该无锁链表
|
||||
- **ContactSystem.getQQ 不再是 `suspend`**
|
||||
- ContactSystem.getGroup 仍是 `suspend`, 原因为需要查询群资料. 在群 ID 无效时抛出 `GroupNotFoundException`
|
||||
|
||||
优化
|
||||
- 日志中, 发送给服务器的包将会被以名字记录, 而不是 id
|
||||
|
||||
### `0.7.5` *2019/12/09*
|
||||
- 修复验证码包发出后无回复 (错误的验证码包)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# style guide
|
||||
kotlin.code.style=official
|
||||
# config
|
||||
mirai_version=0.7.5
|
||||
mirai_version=0.8.0
|
||||
kotlin.incremental.multiplatform=true
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
# kotlin
|
||||
|
@ -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?
|
||||
|
@ -1,254 +1,136 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import net.mamoe.mirai.Bot.ContactSystem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.internal.Group
|
||||
import net.mamoe.mirai.contact.internal.QQ
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.GroupNotFoundException
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.internal.PositiveNumbers
|
||||
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
|
||||
import net.mamoe.mirai.utils.io.logStacktrace
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
import kotlin.jvm.JvmOverloads
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
data class BotAccount(
|
||||
val id: UInt,
|
||||
val password: String
|
||||
) {
|
||||
constructor(id: Long, password: String) : this(id.toUInt(), password)
|
||||
}
|
||||
|
||||
// These pseudo 'constructors' are suspend, therefore they should not be inside the object
|
||||
|
||||
@Suppress("FunctionName")
|
||||
suspend inline fun Bot(account: BotAccount, logger: MiraiLogger): Bot = Bot(account, logger, coroutineContext)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
suspend inline fun Bot(account: BotAccount): Bot = Bot(account, coroutineContext)
|
||||
|
||||
@JvmSynthetic
|
||||
@Suppress("FunctionName")
|
||||
suspend inline fun Bot(qq: UInt, password: String): Bot = Bot(BotAccount(qq, password), coroutineContext)
|
||||
|
||||
@Suppress("FunctionName")
|
||||
suspend inline fun Bot(qq: Long, password: String): Bot = Bot(BotAccount(qq.toUInt(), password), coroutineContext)
|
||||
|
||||
/**
|
||||
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||
*
|
||||
* [Bot] 由 3 个模块组成.
|
||||
* [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问
|
||||
* [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问
|
||||
* [机器人账号信息][BotAccount]: 可通过 [Bot.qqAccount] 访问
|
||||
*
|
||||
* 若需要得到机器人的 QQ 账号, 请访问 [Bot.qqAccount]
|
||||
* 若需要得到服务器上所有机器人列表, 请访问 [Bot.instances]
|
||||
*
|
||||
* 在 BotHelper.kt 中有一些访问的捷径. 如 [Bot.getGroup]
|
||||
*
|
||||
*
|
||||
*
|
||||
* Bot that is the base of the whole program.
|
||||
* It consists of
|
||||
* a [ContactSystem], which manage contacts such as [QQ] and [Group];
|
||||
* a [TIMBotNetworkHandler], which manages the connection to the server;
|
||||
* a [BotAccount], which stores the account information(e.g. qq number the bot)
|
||||
*
|
||||
* To of all the QQ contacts, access [Bot.qqAccount]
|
||||
* To of all the Robot instance, access [Bot.instances]
|
||||
*
|
||||
*
|
||||
* @author Him188moe
|
||||
* @author NaturalHG
|
||||
* @see Contact
|
||||
*/
|
||||
class Bot(val account: BotAccount, val logger: MiraiLogger, context: CoroutineContext) : CoroutineScope {
|
||||
private val supervisorJob = SupervisorJob(context[Job])
|
||||
override val coroutineContext: CoroutineContext =
|
||||
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
|
||||
interface Bot : CoroutineScope {
|
||||
companion object {
|
||||
suspend inline operator fun invoke(account: BotAccount, logger: MiraiLogger): Bot = BotImpl(account, logger, coroutineContext)
|
||||
suspend inline operator fun invoke(account: BotAccount): Bot = BotImpl(account, context = coroutineContext)
|
||||
@JvmSynthetic
|
||||
suspend inline operator fun invoke(qq: UInt, password: String): Bot = BotImpl(BotAccount(qq, password), context = coroutineContext)
|
||||
|
||||
constructor(qq: Long, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context)
|
||||
constructor(qq: UInt, password: String, context: CoroutineContext) : this(BotAccount(qq, password), context)
|
||||
constructor(account: BotAccount, context: CoroutineContext) : this(account, DefaultLogger("Bot(" + account.id + ")"), context)
|
||||
suspend inline operator fun invoke(qq: Long, password: String): Bot = BotImpl(BotAccount(qq.toUInt(), password), context = coroutineContext)
|
||||
operator fun invoke(qq: Long, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
|
||||
@JvmSynthetic
|
||||
operator fun invoke(qq: UInt, password: String, context: CoroutineContext): Bot = BotImpl(BotAccount(qq, password), context = context)
|
||||
|
||||
val contacts = ContactSystem()
|
||||
operator fun invoke(account: BotAccount, context: CoroutineContext): Bot = BotImpl(account, context = context)
|
||||
|
||||
val network: BotNetworkHandler<*> get() = _network
|
||||
|
||||
private lateinit var _network: BotNetworkHandler<*>
|
||||
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
|
||||
|
||||
init {
|
||||
launch {
|
||||
addInstance(this@Bot)
|
||||
}
|
||||
fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq)
|
||||
}
|
||||
|
||||
override fun toString(): String = "Bot(${account.id})"
|
||||
/**
|
||||
* 账号信息
|
||||
*/
|
||||
val account: BotAccount
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
*/
|
||||
val logger: MiraiLogger
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
|
||||
// region contacts
|
||||
|
||||
/**
|
||||
* 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友
|
||||
*/
|
||||
val qqs: ContactList<QQ>
|
||||
|
||||
/**
|
||||
* 获取缓存的 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
|
||||
// endregion
|
||||
|
||||
@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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
data class BotAccount(
|
||||
val id: UInt,
|
||||
val password: String
|
||||
) {
|
||||
constructor(id: Long, password: String) : this(id.toUInt(), password)
|
||||
}
|
@ -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()
|
||||
}*/
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("BotHelperKt")
|
||||
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
@ -26,26 +26,8 @@ import kotlin.jvm.JvmOverloads
|
||||
*/
|
||||
|
||||
//Contacts
|
||||
suspend inline fun Bot.getQQ(@PositiveNumbers number: Long): QQ = this.contacts.getQQ(number)
|
||||
suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id))
|
||||
|
||||
suspend inline fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number)
|
||||
|
||||
suspend inline fun Bot.getGroup(id: UInt): Group = this.contacts.getGroup(GroupId(id))
|
||||
suspend inline fun Bot.getGroup(@PositiveNumbers id: Long): Group =
|
||||
this.contacts.getGroup(GroupId(id.coerceAtLeastOrFail(0).toUInt()))
|
||||
|
||||
suspend inline fun Bot.getGroup(id: GroupId): Group = this.contacts.getGroup(id)
|
||||
suspend inline fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.getGroup(internalId)
|
||||
|
||||
/**
|
||||
* 取得群列表
|
||||
*/
|
||||
inline val Bot.groups: ContactList<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 号
|
||||
*/
|
||||
|
165
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
Normal file
165
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt
Normal file
@ -0,0 +1,165 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.internal.Group
|
||||
import net.mamoe.mirai.contact.internal.QQ
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupNotFound
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupPacket
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.RawGroupInfo
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
|
||||
import net.mamoe.mirai.network.qqAccount
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.internal.PositiveNumbers
|
||||
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
|
||||
import net.mamoe.mirai.utils.io.inline
|
||||
import net.mamoe.mirai.utils.io.logStacktrace
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
@PublishedApi
|
||||
internal class BotImpl @PublishedApi internal constructor(
|
||||
override val account: BotAccount,
|
||||
override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"),
|
||||
context: CoroutineContext
|
||||
) : Bot, CoroutineScope {
|
||||
private val supervisorJob = SupervisorJob(context[Job])
|
||||
override val coroutineContext: CoroutineContext =
|
||||
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
|
||||
|
||||
init {
|
||||
launch {
|
||||
instances.addLast(this@BotImpl)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
KnownPacketId.values() /* load id classes */
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList()
|
||||
|
||||
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block)
|
||||
|
||||
fun instanceWhose(qq: UInt): Bot {
|
||||
instances.forEach {
|
||||
if (it.qqAccount == qq) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "Bot(${account.id})"
|
||||
|
||||
// region network
|
||||
|
||||
override val network: BotNetworkHandler<*> get() = _network
|
||||
private lateinit var _network: BotNetworkHandler<*>
|
||||
|
||||
override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
|
||||
configuration: BotConfiguration,
|
||||
cause: Throwable?
|
||||
): Job = launch {
|
||||
repeat(configuration.reconnectionRetryTimes) {
|
||||
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
|
||||
logger.info("Reconnected successfully")
|
||||
return@launch
|
||||
} else {
|
||||
delay(configuration.reconnectPeriodMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reinitializeNetworkHandler(
|
||||
configuration: BotConfiguration,
|
||||
cause: Throwable?
|
||||
): LoginResult {
|
||||
logger.info("BotAccount: ${qqAccount.toLong()}")
|
||||
logger.info("Initializing BotNetworkHandler")
|
||||
try {
|
||||
if (::_network.isInitialized) {
|
||||
_network.close(cause)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.error("Cannot close network handler", e)
|
||||
}
|
||||
_network = TIMBotNetworkHandler(this.coroutineContext + configuration, this)
|
||||
|
||||
return _network.login()
|
||||
}
|
||||
|
||||
override fun reinitializeNetworkHandlerAsync(
|
||||
configuration: BotConfiguration,
|
||||
cause: Throwable?
|
||||
): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
|
||||
|
||||
// endregion
|
||||
|
||||
// region contacts
|
||||
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
|
||||
|
||||
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
|
||||
|
||||
/**
|
||||
* 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
@JvmSynthetic
|
||||
override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) }
|
||||
|
||||
// NO INLINE!! to help Java
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt())
|
||||
|
||||
override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId())
|
||||
|
||||
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
|
||||
override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
|
||||
val info: RawGroupInfo = try {
|
||||
when (val response =
|
||||
withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>() }) {
|
||||
is RawGroupInfo -> response
|
||||
is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}")
|
||||
else -> assertUnreachable()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e)
|
||||
}
|
||||
|
||||
return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) }
|
||||
}
|
||||
|
||||
// NO INLINE!! to help Java
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
override suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let {
|
||||
groups.delegate.getOrNull(it) ?: inline {
|
||||
val info: RawGroupInfo = try {
|
||||
withSession { GroupPacket.QueryGroupInfo(qqAccount, GroupId(it).toInternalId(), sessionKey).sendAndExpect() }
|
||||
} catch (e: Exception) {
|
||||
e.logStacktrace()
|
||||
error("Cannot obtain group info for id ${it.toLong()}")
|
||||
}
|
||||
|
||||
return groups.delegate.getOrAdd(it) { Group(this, GroupId(it), info, coroutineContext) }
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
override fun close() {
|
||||
_network.close()
|
||||
this.supervisorJob.complete()
|
||||
groups.delegate.clear()
|
||||
qqs.delegate.clear()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.utils.LockFreeLinkedList
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.joinToString
|
||||
|
||||
|
||||
/**
|
||||
* 只读联系人列表, lock-free 实现
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
@Suppress("unused")
|
||||
class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLinkedList<C>) {
|
||||
/**
|
||||
* ID 列表的字符串表示.
|
||||
* 如:
|
||||
* ```
|
||||
* [123456, 321654, 123654]
|
||||
* ```
|
||||
*/
|
||||
val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]"
|
||||
|
||||
operator fun get(id: UInt): C = delegate[id]
|
||||
fun getOrNull(id: UInt): C? = delegate.getOrNull(id)
|
||||
fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null
|
||||
|
||||
val size: Int get() = delegate.size
|
||||
operator fun contains(element: C): Boolean = delegate.contains(element)
|
||||
fun containsAll(elements: Collection<C>): Boolean = elements.all { contains(it) }
|
||||
fun isEmpty(): Boolean = delegate.isEmpty()
|
||||
inline fun forEach(block: (C) -> Unit) = delegate.forEach(block)
|
||||
|
||||
override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
|
||||
}
|
||||
|
||||
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: UInt): C {
|
||||
forEach { if (it.id == id) return it }
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: UInt): C? {
|
||||
forEach { if (it.id == id) return it }
|
||||
return null
|
||||
}
|
||||
|
||||
fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier)
|
@ -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
|
||||
})
|
||||
)
|
||||
}
|
@ -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() } }
|
||||
|
||||
/**
|
||||
* 群成员
|
||||
|
@ -1,4 +1,4 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.network
|
||||
|
||||
@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor(
|
||||
*/
|
||||
val gtk: Int get() = _gtk
|
||||
|
||||
suspend inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
|
||||
suspend inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
|
||||
suspend inline fun UInt.qq(): QQ = bot.getQQ(this)
|
||||
inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
|
||||
inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
|
||||
inline fun UInt.qq(): QQ = bot.getQQ(this)
|
||||
|
||||
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt())
|
||||
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
|
||||
|
@ -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),
|
||||
TOUCH(0x0825u, TouchPacket),
|
||||
SESSION_KEY(0x0828u, RequestSessionPacket),
|
||||
LOGIN(0x0836u, SubmitPasswordPacket),
|
||||
CAPTCHA(0x00BAu, CaptchaPacket),
|
||||
SERVER_EVENT_1(0x00CEu, EventPacketFactory),
|
||||
SERVER_EVENT_2(0x0017u, EventPacketFactory),
|
||||
FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
|
||||
CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
|
||||
|
||||
inline HEARTBEAT(0x0058u, HeartbeatPacket),
|
||||
inline S_KEY(0x001Du, RequestSKeyPacket),
|
||||
inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
|
||||
inline GROUP_PACKET(0x0002u, GroupPacket),
|
||||
inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
|
||||
inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
|
||||
inline ADD_FRIEND(0x00A8u, AddFriendPacket),
|
||||
inline REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
|
||||
inline GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
|
||||
inline FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
|
||||
HEARTBEAT(0x0058u, HeartbeatPacket),
|
||||
S_KEY(0x001Du, RequestSKeyPacket),
|
||||
ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
|
||||
GROUP_PACKET(0x0002u, GroupPacket),
|
||||
SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
|
||||
CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
|
||||
ADD_FRIEND(0x00A8u, AddFriendPacket),
|
||||
REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
|
||||
GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
|
||||
FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
|
||||
|
||||
inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
|
||||
inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
|
||||
inline QUERY_NICKNAME(0x0126u, QueryNicknamePacket),
|
||||
REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
|
||||
REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
|
||||
QUERY_NICKNAME(0x0126u, QueryNicknamePacket),
|
||||
|
||||
inline QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket),
|
||||
QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket),
|
||||
|
||||
inline QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket)
|
||||
QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket)
|
||||
// 031F 查询 "新朋友" 记录
|
||||
|
||||
|
||||
@ -92,5 +92,9 @@ internal enum class KnownPacketId(override inline val value: UShort, override in
|
||||
|
||||
;
|
||||
|
||||
init {
|
||||
factory._id = this
|
||||
}
|
||||
|
||||
override fun toString(): String = (factory::class.simpleName ?: this.name) + "(${value.toUHexString()})"
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -1,5 +1,13 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package net.mamoe.mirai.network.protocol.tim.packet.action
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.Packet
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
|
||||
|
||||
|
||||
// 0001
|
||||
// 已确认 查好友列表的列表
|
||||
@ -15,6 +23,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
|
||||
// 00 00
|
||||
|
||||
|
||||
internal inline class FriendListList(val delegate: List<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
|
||||
when (val flag = readByte().toInt()) {
|
||||
0x02 -> GroupNotFound
|
||||
0x00 -> debugPrintIfFail("解析群信息") {
|
||||
discardExact(4) // group internal id
|
||||
val group = readUInt() // group id
|
||||
|
||||
discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
|
||||
val owner = readUInt()
|
||||
discardExact(22)
|
||||
val groupName = readUByteLVString()
|
||||
discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
|
||||
val owner = readUInt()
|
||||
discardExact(22)
|
||||
val groupName = readUByteLVString()
|
||||
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 00
|
||||
val announcement = readUByteLVString()
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 38 ... 未知
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 0A ...
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 00
|
||||
val announcement = readUByteLVString()
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 38 ... 未知
|
||||
discardExact(readUByte()) // 00
|
||||
discardExact(readUByte()) // 0A ...
|
||||
|
||||
discardExact(38)
|
||||
discardExact(38)
|
||||
|
||||
val stop = readUInt() // 标记读取群成员的结束
|
||||
discardExact(1) // 00
|
||||
val members = mutableMapOf<UInt, MemberPermission>()
|
||||
do {
|
||||
val qq = readUInt()
|
||||
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
|
||||
if (qq == owner) {
|
||||
continue
|
||||
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
|
||||
*/
|
||||
}
|
||||
|
||||
val permission = when (status.takeLowestOneBit()) {
|
||||
1 -> MemberPermission.ADMINISTRATOR
|
||||
else -> MemberPermission.MEMBER
|
||||
}
|
||||
members[qq] = permission
|
||||
} while (qq != stop && remaining != 0L)
|
||||
members[owner] = MemberPermission.OWNER
|
||||
return RawGroupInfo(group, owner, groupName, announcement, members)
|
||||
/*
|
||||
* 群 Mirai
|
||||
*
|
||||
* 00 00 00 03 01 41 00 04 01 40 23 04 40
|
||||
* B1 89 BE 09 群主
|
||||
*
|
||||
* 02 00 00 00 00 00 00 00 00 00 21 00 C8 01
|
||||
* 00 00 00 01 00 00
|
||||
* 00 2D
|
||||
*
|
||||
* 06 4D 69 72 61 69 20 群名
|
||||
* 00
|
||||
* 00
|
||||
* 00
|
||||
* 00
|
||||
* 00
|
||||
* 38 87 5F D8 E8 D4 E9 79 73 8A A4 21 1C 3E 2C 43 D0 23 55 53 49 D3 1C DB F6 1F 84 59 77 66 DA 9C D7 26 0F E3 BD E1 F2 B9 29 D1 F6 97 1C 42 5E B0 AF 09 51 72 DA 03 37 AB 65
|
||||
* 00
|
||||
* 0A 00 00 00 00 06 00 03 00 02 00
|
||||
* 01 00 04 00 04 00 00 00 01 00 05 00 04 5D 90 A7 25 00 06 00 04 04 08 00 00 00 07 00 04 00 00 05 80 00 09 00 01 01
|
||||
* B1 89 BE 09 00
|
||||
* 3E 03 3F A2 00 01
|
||||
* 48 76 54 DC 00 00
|
||||
* 76 E4 B8 DD 00 00
|
||||
* 89 1A 5E AC 00 00
|
||||
* B1 89 BE 09 00 00
|
||||
*/
|
||||
|
||||
/*
|
||||
* 群 XkWhXi
|
||||
*
|
||||
* 00 00 00 03 01 41 00 04 01 40 21 04 40
|
||||
* 3E 03 3F A2 群主
|
||||
*
|
||||
* 02 00 00 00 01 00 00 00 00 27 1A 00 C8 01
|
||||
* 00 00 00 01 00 00
|
||||
* F3 C8
|
||||
*
|
||||
* 06 58 6B 57 68 58 69
|
||||
* 00
|
||||
* 00
|
||||
* 3B E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6 0A E6 AC A2 E8 BF 8E E5 BC 80 E8 BD A6 EF BC 8C E5 8E BB 74 6D E7 9A 84 E7 BD 91 E8 AD A6
|
||||
* 00
|
||||
* 00
|
||||
* 38 EB 3B A5 90 AC E3 70 1F 42 51 B4 72 81 C8 F5 5A D8 80 69 B6 76 AD A4 AA CC 6A 17 4C 79 81 FF 82 04 BA 13 CE 28 DA 6C 3F 41 77 C0 77 40 B5 87 8E EE 29 20 65 FC 2D FF 63
|
||||
* 00
|
||||
* 0A 00 00 00 00 06 00 03 00 02 00
|
||||
* 01 00 04 00 04 00 00 00 05 00 05 00 04 57 94 6F 41 00 06 00 04 04 08 00 10 00 07 00 04 00 00 04 04 00 09 00 01 00
|
||||
* B1 89 BE 09 00 2D 5C 53 A6 00 01 2F 9B 1C F2 00 00 35 49 95 D1 00 01 3B FA 06 9F 00 00 3E 03 3F A2 00 00 42 C4 32 63 00 01 59 17 3E 05 00 01 6A 89 3E 3E 00 00 6D D7 4E CA 00 00 76 E4 B8 DD 00 00 7C BB 60 3C 00 01 7C BC D3 C1 00 01 87 73 86 9D 00 00 90 19 72 65 00 00 97 30 9A 6B 00 00 9C B1 E5 55 00 01 B1 89 BE 09 00 01
|
||||
*/
|
||||
|
||||
/*
|
||||
* 群 20秃顶28火葬30重生异世
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 群 Big convene' (与上面两个来自不同 bot)
|
||||
*
|
||||
* 00 00 00 03 01 01 00 04 01 00 80 01 40
|
||||
* 6C 18 F5 DA 群主
|
||||
*
|
||||
* 02 00 00 27 1B 00 00 00 00 27 1B 01 F4 01
|
||||
* 00 00 00 01 00 00
|
||||
* 0F 1F
|
||||
*
|
||||
* 0C 42 69 67 20 63 6F 6E 76 65 6E 65 27 00 群名
|
||||
* 00 96 E6 AF 95 E4 B8 9A E4 BA 86 EF BC 8C E5 B8 8C E6 9C 9B E5 A4 A7 E5 AE B6 E8 83 BD E5 A4 9F E5 83 8F E4 BB A5 E5 89 8D E9 82 A3 E6 A0 B7 E5 BC 80 E5 BF 83 EF BC 8C E5 AD A6 E4 B9 A0 E8 BF 9B E6 AD A5 EF BC 8C E5 A4 A9 E5 A4 A9 E5 BF AB E4 B9 90 E3 80 82 E6 AD A4 E7 BE A4 E7 A6 81 E6 AD A2 E9 AA 82 E4 BA BA EF BC 8C E5 88 B7 E5 B1 8F E6 9A B4 E5 8A 9B EF BC 8C E8 BF 9D E8 A7 84 E8 80 85 E7 A6 81 E8 A8 80 EF BC 8C E4 B8 A5 E9 87 8D E8 80 85 E5 B0 B1
|
||||
* 76 E8 BF 9B E7 BE A4 E6 97 B6 EF BC 8C E8 AF B7 E4 BF AE E6 94 B9 E6 AD A3 E7 A1 AE E5 A7 93 E5 90 8D E3 80 82 E4 B8 8D E8 83 BD 54 E5 90 8C E5 AD A6 EF BC 8C E5 A4 AA E8 BF 87 E5 88 86 E7 9A 84 54 21 28 E4 BA 92 E8 B5 9E E7 BE A4 EF BC 8C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF E8 81 8A E5 A4 A9 E8 80 85 E5 8F AF E4 BB A5 E4 BA 92 E8 B5 9E E5 AF B9 E6 96 B9
|
||||
* 00 38 D9 FD F5 21 A6 1F 8D 61 37 A1 7A 92 91 2A 2C 71 46 A9 B9 1C 45 EB 38 74 4A 74 EA 77 7D 14 DB 12 D0 B0 09 C2 AA 22 16 F1 D0 B9 97 21 F0 5A A0 06 59 A7 3B 2F 32 D2 B8 E3
|
||||
* 00 0F 00 00 00 00 06 00 03 00 02 01 01 00 04 00 04 00 00 00 15 00 05 00 04 52 7C C5 7C 00 06 00 04 00 00 00 20 00 07 00 04 00 00 00 00 00 09 00 01 00
|
||||
*
|
||||
* C5 15 BE BE 00 ???为啥这个只有一个呢
|
||||
* 1C ED 9F 9B 00 00
|
||||
* 26 D0 E1 3A 00 00
|
||||
* 2D 5C 53 A6 00 01 自己 管理员
|
||||
* 2D BD 28 D2 00 00
|
||||
* 2E 94 76 3E 00 00
|
||||
* 35 F3 BC F2 00 00
|
||||
* 37 D6 91 AB 00 00
|
||||
* 3A 60 1C 3E 00 80 10000000 群员, 好友
|
||||
* 3A 86 EA A3 00 48 01001000 群员 手机在线
|
||||
* 3D 7F E7 70 00 00
|
||||
* 3E 03 3F A2 00 09 00001001 好友, 特别关心, TIM PC 在线, 管理员
|
||||
* 41 47 0C DD 00 40 01000000 群员, 离线
|
||||
* 41 B6 32 A8 00 80
|
||||
* 44 C8 DA 23 00 00
|
||||
* 45 3E 1B 6A 00 80 10000000 群员 手机在线
|
||||
* 45 C6 59 E9 00 C0 群员
|
||||
* 4A BD C6 F9 00 00
|
||||
* 4C 67 45 E8 00 00
|
||||
* 4E AD C2 C2 00 80
|
||||
* 4F A0 F7 EC 00 80
|
||||
* 50 CB 11 E8 00 00
|
||||
* 58 22 21 90 00 00
|
||||
* 59 17 3E 05 00 01 管理员 好友
|
||||
* 5E 74 48 D9 00 00
|
||||
* 5E A2 B5 88 00 00
|
||||
* 66 A1 32 9B 00 40
|
||||
* 68 07 29 0A 00 00
|
||||
* 68 0F EF 4F 00 00
|
||||
* 69 8B 14 F3 00 80
|
||||
* 6A A5 27 4E 00 00
|
||||
* 6C 11 A0 89 00 81 10000001 管理员
|
||||
* 6C 18 F5 DA 00 08 群主
|
||||
* 6C 21 F8 E2 00 01 管理员
|
||||
* 71 F8 F5 18 00 00
|
||||
* 72 0B CC B6 00 00
|
||||
* 75 53 38 DF 00 00
|
||||
* 7A A1 8B 82 00 00
|
||||
* 7C 8C 1D 1B 00 00
|
||||
* 7C BC D3 C1 00 00
|
||||
* 84 2D B8 5F 00 00
|
||||
* 88 4C 33 76 00 00
|
||||
* 8C C8 0D 43 00 00
|
||||
* 90 B8 65 22 00 00
|
||||
* 91 54 89 E9 00 00
|
||||
* 9C E6 93 A5 00 01 管理员
|
||||
* 9D 59 6A 36 00 00
|
||||
* 9D 63 81 5C 00 00
|
||||
* 9E 31 AF AC 00 00
|
||||
* 9E 69 86 25 00 80
|
||||
* A1 FD CA 2D 00 00
|
||||
* A5 22 5C 48 00 00
|
||||
* A5 F2 9A B7 00 00
|
||||
* AF 25 74 9E 00 01
|
||||
* B1 50 24 00 00 00
|
||||
* B2 BD 81 A9 00 00
|
||||
* B5 0E B3 DD 00 00
|
||||
* B9 BF 0D BC 00 00
|
||||
* C5 15 BE BE 00 00
|
||||
*/
|
||||
else -> unsupportedFlag("GroupPacketResponse typed 0x72", flag.toUHexString())
|
||||
}
|
||||
}
|
||||
|
||||
else -> unsupported()
|
||||
else -> unsupportedType("GroupPacketResponse", packetType.toUHexString())
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -1,15 +1,17 @@
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.network.protocol.tim.packet.event
|
||||
|
||||
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()}")
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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> {
|
||||
/**
|
||||
|
@ -0,0 +1,22 @@
|
||||
@file:Suppress("unused", "UNUSED_PARAMETER")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
|
||||
/**
|
||||
* 在获取 [Group] 对象等操作时可能出现的异常
|
||||
*/
|
||||
class GroupNotFoundException : Exception {
|
||||
constructor()
|
||||
constructor(message: String?)
|
||||
constructor(message: String?, cause: Throwable?)
|
||||
constructor(cause: Throwable?)
|
||||
}
|
||||
|
||||
open class MiraiInternalException : Exception {
|
||||
constructor()
|
||||
constructor(message: String?)
|
||||
constructor(message: String?, cause: Throwable?)
|
||||
constructor(cause: Throwable?)
|
||||
}
|
@ -0,0 +1,686 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.atomicfu.AtomicRef
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.atomicfu.loop
|
||||
|
||||
fun <E> LockFreeLinkedList<E>.joinToString(
|
||||
separator: CharSequence = ", ",
|
||||
prefix: CharSequence = "[",
|
||||
postfix: CharSequence = "]",
|
||||
transform: ((E) -> CharSequence)? = null
|
||||
): String = prefix.toString() + buildString {
|
||||
this@joinToString.forEach {
|
||||
if (transform != null) {
|
||||
append(transform(it))
|
||||
} else append(it)
|
||||
append(separator)
|
||||
}
|
||||
}.dropLast(2) + postfix
|
||||
|
||||
/**
|
||||
* Returns a [List] containing all the elements in [this] in the same order
|
||||
*/
|
||||
fun <E> LockFreeLinkedList<E>.toList(): List<E> = toMutableList()
|
||||
|
||||
/**
|
||||
* Returns a [MutableList] containing all the elements in [this] in the same order
|
||||
*/
|
||||
fun <E> LockFreeLinkedList<E>.toMutableList(): MutableList<E> {
|
||||
val list = mutableListOf<E>()
|
||||
this.forEach { list.add(it) }
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of lock-free LinkedList.
|
||||
*
|
||||
* Modifying can be performed concurrently.
|
||||
* Iterating concurrency is guaranteed.
|
||||
*/
|
||||
open class LockFreeLinkedList<E> {
|
||||
@PublishedApi
|
||||
internal val tail: Tail<E> = Tail()
|
||||
|
||||
@PublishedApi
|
||||
internal val head: Head<E> = Head(tail)
|
||||
|
||||
fun removeFirst(): E {
|
||||
while (true) {
|
||||
val currentFirst = head.nextNode
|
||||
if (!currentFirst.isValidElementNode()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
if (head.compareAndSetNextNodeRef(currentFirst, currentFirst.nextNode)) {
|
||||
return currentFirst.nodeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun peekFirst(): E = head.nextNode.letValueIfValid { return it } ?: throw NoSuchElementException()
|
||||
|
||||
fun peekLast(): E = head.iterateBeforeFirst { it === tail }.letValueIfValid { return it } ?: throw NoSuchElementException()
|
||||
|
||||
fun removeLast(): E {
|
||||
while (true) {
|
||||
val beforeLast = head.iterateBeforeFirst { it.nextNode === tail }
|
||||
if (!beforeLast.isValidElementNode()) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
val last = beforeLast.nextNode
|
||||
if (beforeLast.nextNodeRef.compareAndSet(last, last.nextNode)) {
|
||||
return last.nodeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addLast(element: E) {
|
||||
val node = element.asNode(tail)
|
||||
|
||||
while (true) {
|
||||
val tail = head.iterateBeforeFirst { it === tail } // find the last node.
|
||||
if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun plusAssign(element: E) = this.addLast(element)
|
||||
|
||||
inline fun filteringGetOrAdd(filter: (E) -> Boolean, noinline supplier: () -> E): E {
|
||||
val node = LazyNode(tail, supplier)
|
||||
|
||||
while (true) {
|
||||
var current: Node<E> = head
|
||||
|
||||
findLastNode@ while (true) {
|
||||
if (current.isValidElementNode() && filter(current.nodeValue))
|
||||
return current.nodeValue
|
||||
|
||||
if (current.nextNode === tail) {
|
||||
if (current.compareAndSetNextNodeRef(tail, node)) { // ensure only one attempt can put the lazyNode in
|
||||
return node.nodeValue
|
||||
}
|
||||
}
|
||||
|
||||
current = current.nextNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi // limitation by atomicfu
|
||||
internal fun <E> Node<E>.compareAndSetNextNodeRef(expect: Node<E>, update: Node<E>) = this.nextNodeRef.compareAndSet(expect, update)
|
||||
|
||||
override fun toString(): String = joinToString()
|
||||
|
||||
@Suppress("unused")
|
||||
internal fun getLinkStructure(): String = buildString {
|
||||
head.childIterateReturnsLastSatisfying<Node<*>>({
|
||||
append(it.toString())
|
||||
append(" <- ")
|
||||
it.nextNode
|
||||
}, { it !is Tail })
|
||||
}.dropLast(4)
|
||||
|
||||
fun remove(element: E): Boolean {
|
||||
while (true) {
|
||||
val before = head.iterateBeforeNodeValue(element)
|
||||
val toRemove = before.nextNode
|
||||
if (toRemove === tail) {
|
||||
return false
|
||||
}
|
||||
if (toRemove.isRemoved()) {
|
||||
continue
|
||||
}
|
||||
toRemove.removed.value = true // logically remove: all the operations will recognize this node invalid
|
||||
|
||||
|
||||
// physically remove: try to fix the link
|
||||
var next: Node<E> = toRemove.nextNode
|
||||
while (next !== tail && next.isRemoved()) {
|
||||
next = next.nextNode
|
||||
}
|
||||
if (before.nextNodeRef.compareAndSet(toRemove, next)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val size: Int get() = head.countChildIterate<Node<E>>({ it.nextNode }, { it !is Tail }) - 1 // empty head is always included
|
||||
|
||||
operator fun contains(element: E): Boolean {
|
||||
forEach { if (it == element) return true }
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun containsAll(elements: Collection<E>): Boolean = elements.all { contains(it) }
|
||||
|
||||
fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() }
|
||||
|
||||
inline fun forEach(block: (E) -> Unit) {
|
||||
var node: Node<E> = head
|
||||
while (true) {
|
||||
node.letValueIfValid(block)
|
||||
node = node.nextNode
|
||||
if (node === tail) return
|
||||
}
|
||||
}
|
||||
|
||||
fun addAll(elements: Collection<E>) = elements.forEach { addLast(it) }
|
||||
|
||||
@Suppress("unused")
|
||||
fun clear() {
|
||||
val first = head.nextNode
|
||||
head.nextNode = tail
|
||||
first.childIterateReturnFirstUnsatisfying({
|
||||
val n = it.nextNode
|
||||
it.nextNode = tail
|
||||
it.removed.value = true
|
||||
n
|
||||
}, { it !== tail }) // clear the link structure, help GC.
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun removeAll(elements: Collection<E>): Boolean = elements.all { remove(it) }
|
||||
|
||||
/*
|
||||
|
||||
|
||||
private fun removeNode(node: Node<E>): Boolean {
|
||||
if (node == tail) {
|
||||
return false
|
||||
}
|
||||
while (true) {
|
||||
val before = head.iterateBeforeFirst { it === node }
|
||||
val toRemove = before.nextNode
|
||||
val next = toRemove.nextNode
|
||||
if (toRemove == tail) { // This
|
||||
return true
|
||||
}
|
||||
toRemove.nodeValue = null // logically remove first, then all the operations will recognize this node invalid
|
||||
|
||||
if (before.nextNodeRef.compareAndSet(toRemove, next)) { // physically remove: try to fix the link
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAt(index: Int): E {
|
||||
require(index >= 0) { "index must be >= 0" }
|
||||
val nodeBeforeIndex = head.iterateValidNodeNTimes(index)
|
||||
val value = nodeBeforeIndex.nodeValue
|
||||
if (value === null) noSuchElement()
|
||||
removeNode(nodeBeforeIndex)
|
||||
return value
|
||||
}
|
||||
|
||||
operator fun set(index: Int, element: E): E {
|
||||
while (true) {
|
||||
val nodeAtIndex = head.iterateValidNodeNTimes(index + 1)
|
||||
val originalValue = nodeAtIndex.nodeValue
|
||||
if (originalValue === null) noSuchElement() // this node has been removed.
|
||||
if (!nodeAtIndex.nodeValueRef.compareAndSet(null, element)) { // with concurrent compatibility
|
||||
continue
|
||||
}
|
||||
return originalValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the last index of the element in the list that is [equals] to [element], with concurrent compatibility.
|
||||
*
|
||||
* For a typical list, say `head <- Node#1(1) <- Node#2(2) <- Node#3(3) <- Node#4(4) <- Node#5(2) <- tail`,
|
||||
* the procedures of `lastIndexOf(2)` is:
|
||||
*
|
||||
* 1. Iterate each element, until 2 is found, accumulate the index found, which is 1
|
||||
* 2. Search again from the first matching element, which is Node#2
|
||||
* 3. Accumulate the index found.
|
||||
* 4. Repeat 2,3 until the `tail` is reached.
|
||||
*
|
||||
* Concurrent changes may influence the result.
|
||||
*/
|
||||
fun lastIndexOf(element: E): Int {
|
||||
var lastMatching: Node<E> = head
|
||||
var searchStartingFrom: Node<E> = lastMatching
|
||||
var index = 0 // accumulated index from each search
|
||||
|
||||
findTheLastMatchingElement@ while (true) { // iterate to find the last matching element.
|
||||
var timesOnThisTurn = if (searchStartingFrom === head) -1 else 0 // ignore the head
|
||||
val got = searchStartingFrom.nextNode.iterateBeforeFirst { timesOnThisTurn++; it.nodeValue == element }
|
||||
// find the first match starting from `searchStartingFrom`
|
||||
|
||||
if (got.isTail()) break@findTheLastMatchingElement // no further elements
|
||||
check(timesOnThisTurn >= 0) { "Internal check failed: too many times ran: $timesOnThisTurn" }
|
||||
|
||||
searchStartingFrom = got.nextNode
|
||||
index += timesOnThisTurn
|
||||
|
||||
if (!got.isRemoved()) lastMatching = got
|
||||
}
|
||||
|
||||
if (!lastMatching.isValidElementNode()) {
|
||||
// found is invalid means not found
|
||||
return -1
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
override fun listIterator(): MutableListIterator<E> = listIterator0(0)
|
||||
override fun listIterator(index: Int): MutableListIterator<E> = listIterator0(index)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun listIterator0(index: Int): MutableListIterator<E> {
|
||||
var first: Node<E> = head
|
||||
repeat(index) {
|
||||
first = first.nextNode
|
||||
if (first === tail) noSuchElement()
|
||||
}
|
||||
return object : MutableListIterator<E> {
|
||||
var currentNode: Node<E>
|
||||
get() = currentNodeRef.value
|
||||
set(value) {
|
||||
currentNodeRef.value = value
|
||||
}
|
||||
|
||||
private val currentNodeRef: AtomicRef<Node<E>> = atomic(first) // concurrent compatibility
|
||||
|
||||
override fun nextIndex(): Int = indexOfNode(currentNode)
|
||||
|
||||
|
||||
// region previous
|
||||
|
||||
var previousNode: Node<E>
|
||||
get() = previousNodeRef.value
|
||||
set(value) {
|
||||
previousNodeRef.value = value
|
||||
}
|
||||
private val previousNodeRef: AtomicRef<Node<E>> = atomic(head) // concurrent compatibility
|
||||
private val previousNodeIndexRef: AtomicInt = atomic(-1) // concurrent compatibility
|
||||
private val currentNodeAtTheMomentWhenPreviousNodeIsUpdated: AtomicRef<Node<E>> = atomic(currentNode)
|
||||
|
||||
override fun hasPrevious(): Boolean = previousIndex() == -1
|
||||
|
||||
private fun updatePrevious(): Boolean {
|
||||
while (true) {
|
||||
val localNodeAtTheMomentWhenPreviousNodeIsUpdated = currentNode
|
||||
var i = -1 // head
|
||||
var lastSatisfying: Node<E>? = null
|
||||
val foundNode = currentNode.childIterateReturnsLastSatisfying({ it.nextNode }, {
|
||||
i++
|
||||
if (it.isValidElementNode()) {
|
||||
lastSatisfying = it
|
||||
}
|
||||
it != currentNode
|
||||
})
|
||||
|
||||
if (localNodeAtTheMomentWhenPreviousNodeIsUpdated !== currentNode) {
|
||||
continue // current is concurrently changed, must retry
|
||||
}
|
||||
|
||||
if (!foundNode.isValidElementNode()) {
|
||||
// Current node is not found in the list, meaning it had been removed concurrently
|
||||
previousNode = head
|
||||
previousNodeIndexRef.value = -1
|
||||
return false
|
||||
}
|
||||
|
||||
if (lastSatisfying === null) {
|
||||
// All the previous nodes are logically removed.
|
||||
previousNode = head
|
||||
previousNodeIndexRef.value = -1
|
||||
return false
|
||||
}
|
||||
|
||||
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = localNodeAtTheMomentWhenPreviousNodeIsUpdated
|
||||
previousNode = lastSatisfying!! // false positive nullable warning
|
||||
previousNodeIndexRef.value = i
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun previous(): E {
|
||||
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
|
||||
// node list have been changed.
|
||||
if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
while (true) {
|
||||
val value = previousNode.nodeValue
|
||||
if (value != null) {
|
||||
currentNode = previousNode
|
||||
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head
|
||||
return value
|
||||
} else if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
}
|
||||
|
||||
override fun previousIndex(): Int {
|
||||
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
|
||||
// node list have been changed.
|
||||
if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
while (true) {
|
||||
val value = previousNodeIndexRef.value
|
||||
if (value != -1) return value
|
||||
else if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
override fun add(element: E) {
|
||||
val toAdd = element.asNode(tail)
|
||||
while (true) {
|
||||
val next = currentNode.nextNode
|
||||
toAdd.nextNode = next
|
||||
if (currentNode.nextNodeRef.compareAndSet(next, toAdd)) { // ensure the link is not changed
|
||||
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
|
||||
// node list have been changed.
|
||||
if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
return currentNode.nextNode !== tail
|
||||
}
|
||||
|
||||
override fun next(): E {
|
||||
while (true) {
|
||||
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
|
||||
// node list have been changed.
|
||||
if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
val nextNodeValue = currentNode.nextNode.nodeValue
|
||||
if (nextNodeValue !== null) {
|
||||
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head
|
||||
return nextNodeValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove() {
|
||||
if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility
|
||||
noSuchElement()
|
||||
}
|
||||
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head
|
||||
}
|
||||
|
||||
override fun set(element: E) {
|
||||
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
|
||||
// node list have been changed.
|
||||
if (!updatePrevious()) noSuchElement()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E> {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
operator fun get(index: Int): E {
|
||||
require(index >= 0) { "Index must be >= 0" }
|
||||
var i = index + 1 // 1 for head
|
||||
return head.iterateStopOnFirst { i-- == 0 }.nodeValue ?: noSuchElement()
|
||||
}
|
||||
|
||||
fun indexOf(element: E): Int {
|
||||
var i = -1 // head
|
||||
if (!head.iterateStopOnFirst {
|
||||
i++
|
||||
it.nodeValue == element
|
||||
}.isValidElementNode()) {
|
||||
return -1
|
||||
}
|
||||
return i - 1 // iteration is stopped at the next node
|
||||
}
|
||||
|
||||
private fun indexOfNode(node: Node<E>): Int {
|
||||
var i = -1 // head
|
||||
if (!head.iterateStopOnFirst {
|
||||
i++
|
||||
it == node
|
||||
}.isValidElementNode()) {
|
||||
return -1
|
||||
}
|
||||
return i - 1 // iteration is stopped at the next node
|
||||
}
|
||||
|
||||
operator fun iterator(): MutableIterator<E> = object : MutableIterator<E> {
|
||||
var currentNode: Node<E>
|
||||
get() = currentNodeRef.value
|
||||
set(value) {
|
||||
currentNodeRef.value = value
|
||||
}
|
||||
|
||||
private val currentNodeRef: AtomicRef<Node<E>> = atomic(head) // concurrent compatibility
|
||||
|
||||
/**
|
||||
* Check if
|
||||
*
|
||||
* **Notice That:**
|
||||
* if `hasNext` returned `true`, then the last remaining element is removed concurrently,
|
||||
* [next] will produce a [NoSuchElementException]
|
||||
*/
|
||||
override fun hasNext(): Boolean = !currentNode.iterateStopOnFirst { it.isValidElementNode() }.isTail()
|
||||
|
||||
/**
|
||||
* Iterate until the next node is not
|
||||
*/
|
||||
override fun next(): E {
|
||||
while (true) {
|
||||
val next = currentNode.nextNode
|
||||
if (next.isTail()) noSuchElement()
|
||||
|
||||
currentNode = next
|
||||
|
||||
val nodeValue = next.nodeValue
|
||||
if (nodeValue != null) { // next node is not removed, that's what we want
|
||||
return nodeValue
|
||||
} // or try again
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove() {
|
||||
if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility
|
||||
noSuchElement()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
// region internal
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun <E> E.asNode(nextNode: Node<E>): Node<E> = Node(nextNode, this)
|
||||
|
||||
/**
|
||||
* Self-iterate using the [iterator], until [mustBeTrue] returns `false`.
|
||||
* Returns the element at the last time when the [mustBeTrue] returns `true`
|
||||
*/
|
||||
@PublishedApi
|
||||
internal inline fun <N : Node<*>> N.childIterateReturnsLastSatisfying(iterator: (N) -> N, mustBeTrue: (N) -> Boolean): N {
|
||||
if (!mustBeTrue(this)) return this
|
||||
var value: N = this
|
||||
|
||||
while (true) {
|
||||
val newValue = iterator(value)
|
||||
if (mustBeTrue(newValue)) {
|
||||
value = newValue
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
|
||||
if (newValue is Tail<*>) return newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Self-iterate using the [iterator], until [mustBeTrue] returns `false`.
|
||||
* Returns the element at the first time when the [mustBeTrue] returns `false`
|
||||
*/
|
||||
private inline fun <E> E.childIterateReturnFirstUnsatisfying(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): E {
|
||||
if (!mustBeTrue(this)) return this
|
||||
var value: E = this
|
||||
|
||||
while (true) {
|
||||
val newValue = iterator(value)
|
||||
if (mustBeTrue(newValue)) {
|
||||
value = newValue
|
||||
} else {
|
||||
return newValue
|
||||
}
|
||||
|
||||
if (newValue is Tail<*>) return newValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Self-iterate using the [iterator], until [mustBeTrue] returns `false`.
|
||||
* Returns the count of elements being iterated.
|
||||
*/
|
||||
private inline fun <E> E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): Int {
|
||||
var count = 0
|
||||
var value: E = this
|
||||
if (!mustBeTrue(value)) return count
|
||||
|
||||
while (true) {
|
||||
count++
|
||||
val newValue = iterator(value)
|
||||
if (mustBeTrue(newValue)) {
|
||||
value = newValue
|
||||
} else {
|
||||
return count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal class LazyNode<E> @PublishedApi internal constructor(
|
||||
nextNode: Node<E>,
|
||||
private val valueComputer: () -> E
|
||||
) : Node<E>(nextNode, null) {
|
||||
private val initialized = atomic(false)
|
||||
|
||||
private val value: AtomicRef<E?> = atomic(null)
|
||||
|
||||
override val nodeValue: E
|
||||
get() {
|
||||
@Suppress("BooleanLiteralArgument") // false positive warning
|
||||
if (initialized.compareAndSet(false, true)) { // ensure only one lucky attempt can go into the if
|
||||
val value = valueComputer()
|
||||
this.value.value = value
|
||||
return value // fast path
|
||||
}
|
||||
value.loop {
|
||||
if (it != null) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal class Head<E>(nextNode: Node<E>) : Node<E>(nextNode, null) {
|
||||
override fun toString(): String = "Head"
|
||||
override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Head")
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal open class Tail<E> : Node<E>(null, null) {
|
||||
override fun toString(): String = "Tail"
|
||||
override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Tail")
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal open class Node<E>(
|
||||
nextNode: Node<E>?,
|
||||
private var initialNodeValue: E?
|
||||
) {
|
||||
/*
|
||||
internal val id: Int = nextId()
|
||||
companion object {
|
||||
private val idCount = atomic(0)
|
||||
internal fun nextId() = idCount.getAndIncrement()
|
||||
}*/
|
||||
|
||||
override fun toString(): String = "$nodeValue"
|
||||
|
||||
open val nodeValue: E get() = initialNodeValue ?: error("Internal error: nodeValue is not initialized")
|
||||
|
||||
val removed = atomic(false)
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
val nextNodeRef: AtomicRef<Node<E>> = atomic(nextNode ?: this)
|
||||
|
||||
inline fun <R> letValueIfValid(block: (E) -> R): R? {
|
||||
if (!this.isValidElementNode()) {
|
||||
return null
|
||||
}
|
||||
val value = this.nodeValue
|
||||
return if (value !== null) block(value) else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Short cut for accessing [nextNodeRef]
|
||||
*/
|
||||
var nextNode: Node<E>
|
||||
get() = nextNodeRef.value
|
||||
set(value) {
|
||||
nextNodeRef.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the former node of the last node whence [filter] returns true
|
||||
*/
|
||||
inline fun iterateBeforeFirst(filter: (Node<E>) -> Boolean): Node<E> =
|
||||
this.childIterateReturnsLastSatisfying({ it.nextNode }, { !filter(it) })
|
||||
|
||||
/**
|
||||
* Check if all the node which is not [Tail] matches the [condition]
|
||||
*
|
||||
* Head, which is this, is also being tested.
|
||||
* [Tail], is not being tested.
|
||||
*/
|
||||
inline fun allMatching(condition: (Node<E>) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail
|
||||
|
||||
/**
|
||||
* Stop on and returns the former element of the element that is [equals] to the [element]
|
||||
*
|
||||
* E.g.: for `head <- 1 <- 2 <- 3 <- tail`, `iterateStopOnNodeValue(2)` returns the node whose value is 1
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
internal inline fun iterateBeforeNodeValue(element: E): Node<E> = this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element }
|
||||
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun <E> Node<E>.isRemoved() = this.removed.value
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
internal inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved()
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
internal inline fun Node<*>.isHead(): Boolean = this is Head
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
internal inline fun Node<*>.isTail(): Boolean = this is Tail
|
||||
|
||||
// en dregion
|
@ -1,4 +1,4 @@
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
|
@ -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()
|
||||
|
@ -1,4 +1,4 @@
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.utils.io
|
||||
|
||||
@ -95,7 +95,7 @@ fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMa
|
||||
}
|
||||
|
||||
/**
|
||||
* 读扁平的 tag-UVarInt map. 重复的 tag 将不会只保留最后一个
|
||||
* 读扁平的 tag-UVarInt map. 重复的 tag 将只保留最后一个
|
||||
*
|
||||
* tag: UByte
|
||||
* value: UVarint
|
||||
@ -140,10 +140,11 @@ fun Map<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")
|
||||
|
@ -0,0 +1,139 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.test.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class GroupIdConversionsKtTest {
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
@Test
|
||||
fun toInternalId() {
|
||||
repeat(1000000) { _ ->
|
||||
val it = Random.nextInt()
|
||||
try {
|
||||
GroupId(it.toUInt()).toInternalId() shouldBeEqualTo GroupId(it.toUInt()).toInternalIdOld()
|
||||
} catch (e: Throwable) {
|
||||
println(it)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
@Test
|
||||
fun toId() {
|
||||
repeat(1000000) { _ ->
|
||||
val it = Random.nextInt()
|
||||
try {
|
||||
GroupInternalId(it.toUInt()).toId() shouldBeEqualTo GroupInternalId(it.toUInt()).toIdOld()
|
||||
} catch (e: Throwable) {
|
||||
println(it)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private fun GroupId.toInternalIdOld(): GroupInternalId {//求你别出错
|
||||
val left: Long = this.value.toString().let {
|
||||
if (it.length <= 6) {
|
||||
return GroupInternalId(this.value)
|
||||
}
|
||||
it.substring(0, it.length - 6).toLong()
|
||||
}
|
||||
val right: Long = this.value.toString().let {
|
||||
it.substring(it.length - 6).toLong()
|
||||
}
|
||||
|
||||
return GroupInternalId(
|
||||
when (left) {
|
||||
in 1..10 -> {
|
||||
((left + 202).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 11..19 -> {
|
||||
((left + 469).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 20..66 -> {
|
||||
((left + 208).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 67..156 -> {
|
||||
((left + 1943).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 157..209 -> {
|
||||
((left + 199).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 210..309 -> {
|
||||
((left + 389).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 310..499 -> {
|
||||
((left + 349).toString() + right.toString()).toUInt()
|
||||
}
|
||||
else -> this.value
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private fun GroupInternalId.toIdOld(): GroupId = with(value) {
|
||||
//求你别出错
|
||||
var left: UInt = this.toString().let {
|
||||
if (it.length <= 6) {
|
||||
return GroupId(value)
|
||||
}
|
||||
it.substring(0 until it.length - 6).toUInt()
|
||||
}
|
||||
|
||||
return GroupId(when (left.toInt()) {
|
||||
in 203..212 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 6).toUInt()
|
||||
}
|
||||
((left - 202u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 480..488 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 6).toUInt()
|
||||
}
|
||||
((left - 469u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 2100..2146 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 7).toUInt()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toUInt()
|
||||
((left - 208u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 2010..2099 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 6).toUInt()
|
||||
}
|
||||
((left - 1943u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 2147..2199 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 7).toUInt()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toUInt()
|
||||
((left - 199u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 4100..4199 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 7).toUInt()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toUInt()
|
||||
((left - 389u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
in 3800..3989 -> {
|
||||
val right: UInt = this.toString().let {
|
||||
it.substring(it.length - 7).toUInt()
|
||||
}
|
||||
left = left.toString().substring(0 until 3).toUInt()
|
||||
((left - 349u).toString() + right.toString()).toUInt()
|
||||
}
|
||||
else -> value
|
||||
})
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
@file:Suppress("RemoveRedundantBackticks", "NonAsciiCharacters")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.test.shouldBeEqualTo
|
||||
import net.mamoe.mirai.test.shouldBeTrue
|
||||
import org.junit.Test
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
internal class LockFreeLinkedListTest {
|
||||
init {
|
||||
GlobalScope.launch {
|
||||
delay(30 * 1000)
|
||||
exitProcess(-100)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAndGetSingleThreaded() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
list.addLast(1)
|
||||
list.addLast(2)
|
||||
list.addLast(3)
|
||||
list.addLast(4)
|
||||
|
||||
list.size shouldBeEqualTo 4
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAndGetConcurrent() = runBlocking {
|
||||
//withContext(Dispatchers.Default){
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
|
||||
list.concurrentDo(1000, 10) { addLast(1) }
|
||||
list.size shouldBeEqualTo 1000 * 10
|
||||
|
||||
list.concurrentDo(100, 10) {
|
||||
remove(1).shouldBeTrue()
|
||||
}
|
||||
|
||||
list.size shouldBeEqualTo 1000 * 10 - 100 * 10
|
||||
//}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAndGetMassConcurrentAccess() = runBlocking {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
|
||||
val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } }
|
||||
|
||||
//delay(1) // let addJob fly
|
||||
if (addJob.isCompleted) {
|
||||
error("Number of elements are not enough")
|
||||
}
|
||||
val foreachJob = async {
|
||||
list.concurrentDo(1, 10000) {
|
||||
forEach { it + it }
|
||||
}
|
||||
}
|
||||
val removeLastJob = async {
|
||||
list.concurrentDo(1, 15000) {
|
||||
removeLast() shouldBeEqualTo 1
|
||||
}
|
||||
}
|
||||
val removeFirstJob = async {
|
||||
list.concurrentDo(1, 10000) {
|
||||
removeFirst() shouldBeEqualTo 1
|
||||
}
|
||||
}
|
||||
val addJob2 = async {
|
||||
list.concurrentDo(1, 5000) {
|
||||
addLast(1)
|
||||
}
|
||||
}
|
||||
val removeExactJob = launch {
|
||||
list.concurrentDo(3, 1000) {
|
||||
remove(1).shouldBeTrue()
|
||||
}
|
||||
}
|
||||
val filteringGetOrAddJob = launch {
|
||||
list.concurrentDo(1, 10000) {
|
||||
filteringGetOrAdd({ it == 2 }, { 1 })
|
||||
}
|
||||
}
|
||||
joinAll(addJob, addJob2, foreachJob, removeLastJob, removeFirstJob, removeExactJob, filteringGetOrAddJob)
|
||||
|
||||
list.size shouldBeEqualTo 2 * 30000 - 1 * 15000 - 1 * 10000 + 1 * 5000 - 3 * 1000 + 1 * 10000
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeWhileForeach() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
repeat(10) { list.addLast(it) }
|
||||
list.forEach {
|
||||
list.remove(it + 1)
|
||||
}
|
||||
list.peekFirst() shouldBeEqualTo 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun remove() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
|
||||
assertFalse { list.remove(1) }
|
||||
assertEquals(0, list.size)
|
||||
|
||||
list.addLast(1)
|
||||
assertTrue { list.remove(1) }
|
||||
assertEquals(0, list.size)
|
||||
|
||||
list.addLast(2)
|
||||
assertFalse { list.remove(1) }
|
||||
assertEquals(1, list.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addAll() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
list.addAll(listOf(1, 2, 3, 4, 5))
|
||||
list.size shouldBeEqualTo 5
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clear() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
list.addAll(listOf(1, 2, 3, 4, 5))
|
||||
list.size shouldBeEqualTo 5
|
||||
list.clear()
|
||||
list.size shouldBeEqualTo 0
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
@Test
|
||||
fun withInlineClassElements() {
|
||||
val list = LockFreeLinkedList<UInt>()
|
||||
list.addAll(listOf(1u, 2u, 3u, 4u, 5u))
|
||||
list.size shouldBeEqualTo 5
|
||||
|
||||
list.toString() shouldBeEqualTo "[1, 2, 3, 4, 5]"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filteringGetOrAdd when add`() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
list.addAll(listOf(1, 2, 3, 4, 5))
|
||||
val value = list.filteringGetOrAdd({ it == 6 }, { 6 })
|
||||
|
||||
println("Check value")
|
||||
value shouldBeEqualTo 6
|
||||
println("Check size")
|
||||
println(list.getLinkStructure())
|
||||
list.size shouldBeEqualTo 6
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filteringGetOrAdd when get`() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
list.addAll(listOf(1, 2, 3, 4, 5))
|
||||
val value = list.filteringGetOrAdd({ it == 2 }, { 2 })
|
||||
|
||||
println("Check value")
|
||||
value shouldBeEqualTo 2
|
||||
println("Check size")
|
||||
println(list.getLinkStructure())
|
||||
list.size shouldBeEqualTo 5
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `filteringGetOrAdd when empty`() {
|
||||
val list = LockFreeLinkedList<Int>()
|
||||
val value = list.filteringGetOrAdd({ it == 2 }, { 2 })
|
||||
|
||||
println("Check value")
|
||||
value shouldBeEqualTo 2
|
||||
println("Check size")
|
||||
println(list.getLinkStructure())
|
||||
list.size shouldBeEqualTo 1
|
||||
}
|
||||
/*
|
||||
@Test
|
||||
fun indexOf() {
|
||||
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 3, 3)
|
||||
assertEquals(0, list.indexOf(1))
|
||||
assertEquals(2, list.indexOf(3))
|
||||
|
||||
assertEquals(-1, list.indexOf(4))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun iterator() {
|
||||
var list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2)
|
||||
list.forEach {
|
||||
it shouldBeEqualTo 2
|
||||
}
|
||||
|
||||
list = lockFreeLinkedListOf(1, 2)
|
||||
list.joinToString { it.toString() } shouldBeEqualTo "1, 2"
|
||||
|
||||
|
||||
list = lockFreeLinkedListOf(1, 2)
|
||||
val iterator = list.iterator()
|
||||
iterator.remove()
|
||||
var reached = false
|
||||
for (i in iterator) {
|
||||
i shouldBeEqualTo 2
|
||||
reached = true
|
||||
}
|
||||
reached shouldBeEqualTo true
|
||||
|
||||
list.joinToString { it.toString() } shouldBeEqualTo "2"
|
||||
iterator.remove()
|
||||
assertFailsWith<NoSuchElementException> { iterator.remove() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `lastIndexOf of exact 1 match at first`() {
|
||||
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2, 1)
|
||||
list.lastIndexOf(2) shouldBeEqualTo 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `lastIndexOf of exact 1 match`() {
|
||||
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2)
|
||||
list.lastIndexOf(2) shouldBeEqualTo 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `lastIndexOf of multiply matches`() {
|
||||
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 2)
|
||||
list.lastIndexOf(2) shouldBeEqualTo 2
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `lastIndexOf of no match`() {
|
||||
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2)
|
||||
list.lastIndexOf(3) shouldBeEqualTo -1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `lastIndexOf of many elements`() {
|
||||
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 4, 2, 3, 4, 5)
|
||||
list.lastIndexOf(4) shouldBeEqualTo 4
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
@MiraiExperimentalAPI
|
||||
internal suspend inline fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutines: Int, times: Int, crossinline todo: E.() -> Unit) =
|
||||
coroutineScope {
|
||||
repeat(numberOfCoroutines) {
|
||||
launch(start = CoroutineStart.UNDISPATCHED) {
|
||||
repeat(times) {
|
||||
todo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user