Commit d047a363 authored by liujiahua123123's avatar liujiahua123123

Merge remote-tracking branch 'origin/master'

parents aade2e51 7cd1bc73
......@@ -4,6 +4,19 @@
开发版本. 频繁更新, 不保证高稳定性
### `0.8.0` *2019/12/14*
协议
- 现在查询群资料时可处理群号无效的情况
- 现在能正常分辨禁言事件包
功能
- 增加无锁链表: LockFreeLinkedList, 并将 ContactList 的实现改为该无锁链表
- **ContactSystem.getQQ 不再是 `suspend`**
- ContactSystem.getGroup 仍是 `suspend`, 原因为需要查询群资料. 在群 ID 无效时抛出 `GroupNotFoundException`
优化
- 日志中, 发送给服务器的包将会被以名字记录, 而不是 id
### `0.7.5` *2019/12/09*
- 修复验证码包发出后无回复 (错误的验证码包)
......
# style guide
kotlin.code.style=official
# config
mirai_version=0.7.5
mirai_version=0.8.0
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin
......
......@@ -20,7 +20,6 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.addFriend
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.hexToUBytes
......@@ -81,6 +80,7 @@ suspend inline fun ApplicationCall.ok() = this.respond(HttpStatusCode.OK, "OK")
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
......@@ -107,14 +107,14 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.param(name
@Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class)
private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R? =
when {
R::class == Byte::class -> call.parameters[name]?.toByte()
R::class == Int::class -> call.parameters[name]?.toInt()
R::class == Short::class -> call.parameters[name]?.toShort()
R::class == Float::class -> call.parameters[name]?.toFloat()
R::class == Long::class -> call.parameters[name]?.toLong()
R::class == Double::class -> call.parameters[name]?.toDouble()
R::class == Boolean::class -> when (call.parameters[name]) {
when (R::class) {
Byte::class -> call.parameters[name]?.toByte()
Int::class -> call.parameters[name]?.toInt()
Short::class -> call.parameters[name]?.toShort()
Float::class -> call.parameters[name]?.toFloat()
Long::class -> call.parameters[name]?.toLong()
Double::class -> call.parameters[name]?.toDouble()
Boolean::class -> when (call.parameters[name]) {
"true" -> true
"false" -> false
"0" -> false
......@@ -123,13 +123,13 @@ private inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNul
else -> illegalParam("boolean", name)
}
R::class == String::class -> call.parameters[name]
String::class -> call.parameters[name]
R::class == UByte::class -> call.parameters[name]?.toUByte()
R::class == UInt::class -> call.parameters[name]?.toUInt()
R::class == UShort::class -> call.parameters[name]?.toUShort()
UByte::class -> call.parameters[name]?.toUByte()
UInt::class -> call.parameters[name]?.toUInt()
UShort::class -> call.parameters[name]?.toUShort()
R::class == UByteArray::class -> call.parameters[name]?.hexToUBytes()
R::class == ByteArray::class -> call.parameters[name]?.hexToBytes()
UByteArray::class -> call.parameters[name]?.hexToUBytes()
ByteArray::class -> call.parameters[name]?.hexToBytes()
else -> error(name::class.simpleName + " is not supported")
} as R?
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
data class BotAccount(
val id: UInt,
val password: String
) {
constructor(id: Long, password: String) : this(id.toUInt(), password)
}
\ No newline at end of file
......@@ -68,15 +68,15 @@ Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749
* @param remark 好友备注
*/
@UseExperimental(ExperimentalContracts::class)
suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = bot.withSession {
return when (CanAddFriendPacket(bot.qqAccount, id, bot.sessionKey).sendAndExpect<CanAddFriendResponse>()) {
suspend fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = withSession {
return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect<CanAddFriendResponse>()) {
is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED
is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED
is CanAddFriendResponse.ReadyToAdd,
is CanAddFriendResponse.RequireVerification -> {
val key = RequestFriendAdditionKeyPacket(bot.qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(bot.qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect<RequestFriendAdditionKeyPacket.Response>().key
AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect<AddFriendPacket.Response>()
AddFriendResult.WAITING_FOR_APPROVE
} //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥
......@@ -87,7 +87,7 @@ suspend fun Bot.ContactSystem.addFriend(id: UInt, message: String? = null, remar
/*is CanAddFriendResponse.ReadyToAdd -> {
// TODO: 2019/11/11 这不需要验证信息的情况
//AddFriendPacket(bot.qqAccount, id, bot.sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
//AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync<AddFriendPacket.Response>().await()
TODO()
}*/
}
......
@file:JvmMultifileClass
@file:JvmName("BotHelperKt")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE")
package net.mamoe.mirai
......@@ -26,26 +26,8 @@ import kotlin.jvm.JvmOverloads
*/
//Contacts
suspend inline fun Bot.getQQ(@PositiveNumbers number: Long): QQ = this.contacts.getQQ(number)
suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id))
suspend inline fun Bot.getQQ(number: UInt): QQ = this.contacts.getQQ(number)
suspend inline fun Bot.getGroup(id: UInt): Group = this.contacts.getGroup(GroupId(id))
suspend inline fun Bot.getGroup(@PositiveNumbers id: Long): Group =
this.contacts.getGroup(GroupId(id.coerceAtLeastOrFail(0).toUInt()))
suspend inline fun Bot.getGroup(id: GroupId): Group = this.contacts.getGroup(id)
suspend inline fun Bot.getGroup(internalId: GroupInternalId): Group = this.contacts.getGroup(internalId)
/**
* 取得群列表
*/
inline val Bot.groups: ContactList<Group> get() = this.contacts.groups
/**
* 取得好友列表
*/
inline val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
/**
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
......@@ -70,7 +52,7 @@ internal suspend inline fun Bot.sendPacket(packet: OutgoingPacket) =
* 使用在默认配置基础上修改的配置进行登录
*/
@UseExperimental(ExperimentalContracts::class)
suspend inline fun Bot.login(noinline configuration: BotConfiguration.() -> Unit): LoginResult {
suspend inline fun Bot.login(configuration: BotConfiguration.() -> Unit): LoginResult {
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
......@@ -91,7 +73,7 @@ suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() }
* 使用在默认配置基础上修改的配置进行登录, 返回 [this]
*/
@UseExperimental(ExperimentalContracts::class)
suspend inline fun Bot.alsoLogin(noinline configuration: BotConfiguration.() -> Unit): Bot {
suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bot {
contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
}
......@@ -108,14 +90,6 @@ suspend inline fun Bot.alsoLogin(message: String): Bot {
}
}
/**
* 添加好友
*/
@UseExperimental(ExperimentalContracts::class)
@JvmOverloads
suspend inline fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult =
contacts.addFriend(id, message, remark)
/**
* 取得机器人的 QQ 号
*/
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import kotlinx.coroutines.*
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.internal.Group
import net.mamoe.mirai.contact.internal.QQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupNotFound
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.RawGroupInfo
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.isSuccess
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import net.mamoe.mirai.utils.io.inline
import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
@PublishedApi
internal class BotImpl @PublishedApi internal constructor(
override val account: BotAccount,
override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"),
context: CoroutineContext
) : Bot, CoroutineScope {
private val supervisorJob = SupervisorJob(context[Job])
override val coroutineContext: CoroutineContext =
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
init {
launch {
instances.addLast(this@BotImpl)
}
}
companion object {
init {
KnownPacketId.values() /* load id classes */
}
@PublishedApi
internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList()
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block)
fun instanceWhose(qq: UInt): Bot {
instances.forEach {
if (it.qqAccount == qq) {
return it
}
}
throw NoSuchElementException()
}
}
override fun toString(): String = "Bot(${account.id})"
// region network
override val network: BotNetworkHandler<*> get() = _network
private lateinit var _network: BotNetworkHandler<*>
override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
configuration: BotConfiguration,
cause: Throwable?
): Job = launch {
repeat(configuration.reconnectionRetryTimes) {
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
logger.info("Reconnected successfully")
return@launch
} else {
delay(configuration.reconnectPeriodMillis)
}
}
}
override suspend fun reinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable?
): LoginResult {
logger.info("BotAccount: ${qqAccount.toLong()}")
logger.info("Initializing BotNetworkHandler")
try {
if (::_network.isInitialized) {
_network.close(cause)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
_network = TIMBotNetworkHandler(this.coroutineContext + configuration, this)
return _network.login()
}
override fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration,
cause: Throwable?
): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
// endregion
// region contacts
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
/**
* 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
*/
@UseExperimental(MiraiInternalAPI::class)
@JvmSynthetic
override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) }
// NO INLINE!! to help Java
@UseExperimental(MiraiInternalAPI::class)
override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt())
override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId())
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class)
override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline {
val info: RawGroupInfo = try {
when (val response =
withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>() }) {
is RawGroupInfo -> response
is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}")
else -> assertUnreachable()
}
} catch (e: Exception) {
throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e)
}
return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) }
}
// NO INLINE!! to help Java
@UseExperimental(MiraiInternalAPI::class)
override suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let {
groups.delegate.getOrNull(it) ?: inline {
val info: RawGroupInfo = try {
withSession { GroupPacket.QueryGroupInfo(qqAccount, GroupId(it).toInternalId(), sessionKey).sendAndExpect() }
} catch (e: Exception) {
e.logStacktrace()
error("Cannot obtain group info for id ${it.toLong()}")
}
return groups.delegate.getOrAdd(it) { Group(this, GroupId(it), info, coroutineContext) }
}
}
// endregion
@UseExperimental(MiraiInternalAPI::class)
override fun close() {
_network.close()
this.supervisorJob.complete()
groups.delegate.clear()
qqs.delegate.clear()
}
}
......@@ -8,7 +8,9 @@ import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.chain
import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
import net.mamoe.mirai.withSession
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
......@@ -54,55 +56,3 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R {
}
return bot.withSession(block)
}
/**
* 只读联系人列表
*/
@UseExperimental(MiraiInternalAPI::class)
inline class ContactList<C : Contact>(internal val mutable: MutableContactList<C>) : Map<UInt, C> {
/**
* ID 列表的字符串表示.
* 如:
* ```
* [123456, 321654, 123654]
* ```
*/
val idContentString: String get() = this.keys.joinToString(prefix = "[", postfix = "]") { it.toLong().toString() }
override fun toString(): String = mutable.toString()
// TODO: 2019/12/2 应该使用属性代理, 但属性代理会导致 UInt 内联错误. 等待 kotlin 修复后替换
override val size: Int get() = mutable.size
override fun containsKey(key: UInt): Boolean = mutable.containsKey(key)
override fun containsValue(value: C): Boolean = mutable.containsValue(value)
override fun get(key: UInt): C? = mutable[key]
override fun isEmpty(): Boolean = mutable.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<UInt, C>> get() = mutable.entries
override val keys: MutableSet<UInt> get() = mutable.keys
override val values: MutableCollection<C> get() = mutable.values
}
/**
* 可修改联系人列表. 只会在内部使用.
*/
@MiraiInternalAPI
inline class MutableContactList<C : Contact>(private val delegate: MutableMap<UInt, C> = linkedMapOf()) : MutableMap<UInt, C> {
override fun toString(): String = asIterable().joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") { it.value.toString() }
// TODO: 2019/12/2 应该使用属性代理, 但属性代理会导致 UInt 内联错误. 等待 kotlin 修复后替换
override val size: Int get() = delegate.size
override fun containsKey(key: UInt): Boolean = delegate.containsKey(key)
override fun containsValue(value: C): Boolean = delegate.containsValue(value)
override fun get(key: UInt): C? = delegate[key]
override fun isEmpty(): Boolean = delegate.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<UInt, C>> get() = delegate.entries
override val keys: MutableSet<UInt> get() = delegate.keys
override val values: MutableCollection<C> get() = delegate.values
override fun clear() = delegate.clear()
override fun put(key: UInt, value: C): C? = delegate.put(key, value)
override fun putAll(from: Map<out UInt, C>) = delegate.putAll(from)
override fun remove(key: UInt): C? = delegate.remove(key)
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.contact
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
/**
* 只读联系人列表, lock-free 实现
*/
@UseExperimental(MiraiInternalAPI::class)
@Suppress("unused")
class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLinkedList<C>) {
/**
* ID 列表的字符串表示.
* 如:
* ```
* [123456, 321654, 123654]
* ```
*/
val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]"
operator fun get(id: UInt): C = delegate[id]
fun getOrNull(id: UInt): C? = delegate.getOrNull(id)
fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null
val size: Int get() = delegate.size
operator fun contains(element: C): Boolean = delegate.contains(element)
fun containsAll(elements: Collection<C>): Boolean = elements.all { contains(it) }
fun isEmpty(): Boolean = delegate.isEmpty()
inline fun forEach(block: (C) -> Unit) = delegate.forEach(block)
override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
}
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: UInt): C {
forEach { if (it.id == id) return it }
throw NoSuchElementException()
}
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: UInt): C? {
forEach { if (it.id == id) return it }
return null
}
fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier)
......@@ -2,101 +2,50 @@
package net.mamoe.mirai.contact
fun GroupId.toInternalId(): GroupInternalId {//求你别出错
val left: Long = this.value.toString().let {
if (it.length < 6) {
return GroupInternalId(this.value)
}
it.substring(0, it.length - 6).toLong()
}
val right: Long = this.value.toString().let {
it.substring(it.length - 6).toLong()
import kotlin.math.pow
@Suppress("ObjectPropertyName")
private val `10EXP6` = 10.0.pow(6).toUInt()
fun GroupId.toInternalId(): GroupInternalId {
if (this.value <= `10EXP6`) {
return GroupInternalId(this.value)
}
val left: Long = this.value.toString().dropLast(6).toLong()
val right: Long = this.value.toString().takeLast(6).toLong()
return GroupInternalId(
when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toUInt()
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toUInt()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toUInt()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toUInt()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toUInt()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toUInt()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toUInt()
}
in 1..10 -> ((left + 202).toString() + right.toString()).toUInt()
in 11..19 -> ((left + 469).toString() + right.toString()).toUInt()
in 20..66 -> ((left + 208).toString() + right.toString()).toUInt()
in 67..156 -> ((left + 1943).toString() + right.toString()).toUInt()
in 157..209 -> ((left + 199).toString() + right.toString()).toUInt()
in 210..309 -> ((left + 389).toString() + right.toString()).toUInt()
in 310..499 -> ((left + 349).toString() + right.toString()).toUInt()
else -> this.value
}
)
}
fun GroupInternalId.toId(): GroupId = with(value) {
//求你别出错
var left: UInt = this.toString().let {
if (it.length < 6) {
return GroupId(value)
}
it.substring(0 until it.length - 6).toUInt()
fun GroupInternalId.toId(): GroupId = with(value.toString()) {
if (value < `10EXP6`) {
return GroupId(value)
}
val left: UInt = this.dropLast(6).toUInt()
return GroupId(when (left.toInt()) {
in 203..212 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 202u).toString() + right.toString()).toUInt()
return GroupId(
when (left.toInt()) {
in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 480..488 -> ((left - 469u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 2100..2146 -> ((left.toString().take(3).toUInt() - 208u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 2010..2099 -> ((left - 1943u).toString() + this.takeLast(6).toInt().toString()).toUInt()
in 2147..2199 -> ((left.toString().take(3).toUInt() - 199u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 4100..4199 -> ((left.toString().take(3).toUInt() - 389u).toString() + this.takeLast(7).toInt().toString()).toUInt()
in 3800..3989 -> ((left.toString().take(3).toUInt() - 349u).toString() + this.takeLast(7).toInt().toString()).toUInt()
else -> value
}
in 480..488 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 469u).toString() + right.toString()).toUInt()
}
in 2100..2146 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 208u).toString() + right.toString()).toUInt()
}
in 2010..2099 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 1943u).toString() + right.toString()).toUInt()
}
in 2147..2199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 199u).toString() + right.toString()).toUInt()
}
in 4100..4199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 389u).toString() + right.toString()).toUInt()
}
in 3800..3989 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 349u).toString() + right.toString()).toUInt()
}
else -> value
})
)
}
\ No newline at end of file
......@@ -3,6 +3,8 @@
package net.mamoe.mirai.contact.internal
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.data.Profile
......@@ -15,9 +17,7 @@ import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.network.sessionKey
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.logStacktrace
import net.mamoe.mirai.withSession
import kotlin.coroutines.CoroutineContext
......@@ -35,15 +35,12 @@ internal sealed class ContactImpl : Contact {
*/
@Suppress("FunctionName")
@PublishedApi
internal suspend fun Group(bot: Bot, groupId: GroupId, context: CoroutineContext): Group {
val info: RawGroupInfo = try {
bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, groupId.toInternalId(), sessionKey).sendAndExpect() }
} catch (e: Exception) {
e.logStacktrace()
error("Cannot obtain group info for id ${groupId.value}")
internal fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group =
GroupImpl(bot, groupId, context).apply {
this@apply.info = info.parseBy(this@apply)
launch { startUpdater() }
}
return GroupImpl(bot, groupId, context).apply { this.info = info.parseBy(this); startUpdater() }
}
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) :
......@@ -52,14 +49,15 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
override val internalId = GroupId(id).toInternalId()
internal lateinit var info: GroupInfo
internal lateinit var initialInfoJob: Job
override val owner: Member get() = info.owner
override val name: String get() = info.name
override val announcement: String get() = info.announcement
override val members: ContactList<Member> get() = info.members
override fun getMember(id: UInt): Member =
if (members.containsKey(id)) members[id]!!
else throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}")
override suspend fun sendMessage(message: MessageChain) {
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
......@@ -76,20 +74,18 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
@UseExperimental(MiraiInternalAPI::class)
override suspend fun startUpdater() {
subscribeAlways<MemberJoinEventPacket> {
// FIXME: 2019/11/29 非线程安全!!
members.mutable[it.member.id] = it.member
members.delegate.addLast(it.member)
}
subscribeAlways<MemberQuitEvent> {
// FIXME: 2019/11/29 非线程安全!!
members.mutable.remove(it.member.id)
members.delegate.remove(it.member)
}
}
override fun toString(): String = "Group(${this.id})"
}
@Suppress("FunctionName")
suspend inline fun QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { startUpdater() }
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun CoroutineScope.QQ(bot: Bot, id: UInt, coroutineContext: CoroutineContext): QQ = QQImpl(bot, id, coroutineContext).apply { launch { startUpdater() } }
@PublishedApi
internal data class QQImpl @PublishedApi internal constructor(override val bot: Bot, override val id: UInt, override val coroutineContext: CoroutineContext) :
......@@ -118,9 +114,9 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot:
override fun toString(): String = "QQ(${this.id})"
}
@Suppress("FunctionName")
suspend inline fun Member(delegate: QQ, group: Group, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, group, permission, coroutineContext).apply { startUpdater() }
@Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member =
MemberImpl(delegate, this, permission, coroutineContext).apply { launch { startUpdater() } }
/**
* 群成员
......
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
package net.mamoe.mirai.network
......@@ -74,9 +74,9 @@ abstract class BotSessionBase internal constructor(
*/
val gtk: Int get() = _gtk
suspend inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
suspend inline fun UInt.qq(): QQ = bot.getQQ(this)
inline fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt())
inline fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0))
inline fun UInt.qq(): QQ = bot.getQQ(this)
suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toUInt())
suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0))
......
......@@ -226,7 +226,9 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
return
if (!packet::class.annotations.filterIsInstance<NoLog>().any()) {
bot.logger.verbose("Packet received: $packet")
if ((packet as? BroadcastControllable)?.shouldBroadcast != false) {
bot.logger.verbose("Packet received: $packet")
}
}
when (packet) {
......@@ -239,7 +241,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) }
.also { temporaryPacketHandlers.removeAll(it) }
}.forEach {
it.doReceiveWithoutExceptions(packet)
it.doReceiveCatchingExceptions(packet)
}
if (factory is SessionPacketFactory<*>) {
......@@ -286,7 +288,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou
it::class.annotations.filterIsInstance<NoLog>().any()
}
}?.let {
bot.logger.verbose("Packet sent: ${it.packetId}")
bot.logger.verbose("Packet sent: ${it.name}")
}
PacketSentEvent(bot, packet).broadcast()
......
......@@ -60,7 +60,7 @@ internal class TemporaryPacketHandler<P : Packet, R>(
internal inline fun filter(session: BotSession, packet: Packet, sequenceId: UShort): Boolean =
expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) sequenceId == toSend.sequenceId else true
internal suspend inline fun doReceiveWithoutExceptions(packet: Packet) {
internal suspend inline fun doReceiveCatchingExceptions(packet: Packet) {
@Suppress("UNCHECKED_CAST")
val ret = try {
withContext(callerContext) {
......
......@@ -2,19 +2,6 @@
package net.mamoe.mirai.network.protocol.tim.packet
/**
* 包 ID. 除特殊外, [PacketFactory] 都需要这个注解来指定包 ID.
*/
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
internal annotation class AnnotatedId( // 注解无法在 JS 平台使用, 但现在暂不需要考虑 JS
val id: KnownPacketId
)
internal inline val AnnotatedId.value: UShort get() = id.value
/**
* 包的最后一次修改时间, 和分析时使用的 TIM 版本
*/
......
......@@ -24,7 +24,7 @@ internal class OutgoingPacket(
val sequenceId: UShort,
internal val delegate: ByteReadPacket
) : Packet {
private val name: String by lazy {
val name: String by lazy {
name ?: packetId.toString()
}
}
......
......@@ -10,7 +10,6 @@ import kotlinx.io.pool.useInstance
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.read
......@@ -26,20 +25,13 @@ import net.mamoe.mirai.utils.readProtoMap
*/
internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypter>(val decrypterType: DecrypterType<TDecrypter>) {
/**
* 2 Ubyte.
* 读取注解 [AnnotatedId]
*/
private val annotatedId: AnnotatedId
get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
?: error("Annotation AnnotatedId not found for class ${this::class.simpleName}")
@Suppress("PropertyName")
internal var _id: PacketId = NullPacketId
// TODO: 2019/11/22 修改 包 ID 为参数
/**
* 包 ID.
*/
open val id: PacketId by lazy { annotatedId.id }
open val id: PacketId get() = _id
/**
* **解码**服务器的回复数据包
......
......@@ -55,35 +55,35 @@ internal inline class IgnoredPacketId constructor(override val value: UShort) :
* 已知的 [matchPacketId]. 所有在 Mirai 中实现过的包都会使用这些 Id
*/
@Suppress("unused")
internal enum class KnownPacketId(override inline val value: UShort, override inline val factory: PacketFactory<*, *>) :
internal enum class KnownPacketId(override val value: UShort, override val factory: PacketFactory<*, *>) :
PacketId {
inline TOUCH(0x0825u, TouchPacket),
inline SESSION_KEY(0x0828u, RequestSessionPacket),
inline LOGIN(0x0836u, SubmitPasswordPacket),
inline CAPTCHA(0x00BAu, CaptchaPacket),
inline SERVER_EVENT_1(0x00CEu, EventPacketFactory),
inline SERVER_EVENT_2(0x0017u, EventPacketFactory),
inline FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
inline CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
inline HEARTBEAT(0x0058u, HeartbeatPacket),
inline S_KEY(0x001Du, RequestSKeyPacket),
inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
inline GROUP_PACKET(0x0002u, GroupPacket),
inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
inline ADD_FRIEND(0x00A8u, AddFriendPacket),
inline REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
inline GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
inline FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
inline REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
inline REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
inline QUERY_NICKNAME(0x0126u, QueryNicknamePacket),
inline QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket),
inline QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket)
TOUCH(0x0825u, TouchPacket),
SESSION_KEY(0x0828u, RequestSessionPacket),
LOGIN(0x0836u, SubmitPasswordPacket),
CAPTCHA(0x00BAu, CaptchaPacket),
SERVER_EVENT_1(0x00CEu, EventPacketFactory),
SERVER_EVENT_2(0x0017u, EventPacketFactory),
FRIEND_ONLINE_STATUS_CHANGE(0x0081u, FriendOnlineStatusChangedPacket),
CHANGE_ONLINE_STATUS(0x00ECu, ChangeOnlineStatusPacket),
HEARTBEAT(0x0058u, HeartbeatPacket),
S_KEY(0x001Du, RequestSKeyPacket),
ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
GROUP_PACKET(0x0002u, GroupPacket),
SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
ADD_FRIEND(0x00A8u, AddFriendPacket),
REQUEST_FRIEND_ADDITION_KEY(0x00AEu, RequestFriendAdditionKeyPacket),
GROUP_IMAGE_ID(0x0388u, GroupImagePacket),
FRIEND_IMAGE_ID(0x0352u, FriendImagePacket),
REQUEST_PROFILE_AVATAR(0x0031u, RequestProfileAvatarPacket),
REQUEST_PROFILE_DETAILS(0x003Cu, RequestProfileDetailsPacket),
QUERY_NICKNAME(0x0126u, QueryNicknamePacket),
QUERY_PREVIOUS_NAME(0x01BCu, QueryPreviousNamePacket),
QUERY_FRIEND_REMARK(0x003Eu, QueryFriendRemarkPacket)
// 031F 查询 "新朋友" 记录
......@@ -92,5 +92,9 @@ internal enum class KnownPacketId(override inline val value: UShort, override in
;
init {
factory._id = this
}
override fun toString(): String = (factory::class.simpleName ?: this.name) + "(${value.toUHexString()})"
}
......@@ -19,7 +19,6 @@ import net.mamoe.mirai.withSession
* - 昵称
* - 共同群内的群名片
*/
@AnnotatedId(KnownPacketId.QUERY_PREVIOUS_NAME)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() {
operator fun invoke(
......@@ -77,7 +76,6 @@ class PreviousNameList(
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
operator fun invoke(
......@@ -155,7 +153,6 @@ inline class FriendAdditionKey(val value: IoBuffer)
/**
* 请求一个 32 位 Key, 在添加好友时发出
*/
@AnnotatedId(KnownPacketId.REQUEST_FRIEND_ADDITION_KEY)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() {
operator fun invoke(
......@@ -182,7 +179,6 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri
/**
* 请求添加好友
*/
@AnnotatedId(KnownPacketId.ADD_FRIEND)
internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>() {
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
@Suppress("FunctionName")
......
......@@ -92,7 +92,6 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse {
* - 服务器已经存有这个图片
* - 服务器未存有, 返回一个 key 用于客户端上传
*/
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.11.16", timVersion = "2.3.2 (21173)")
internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
@Suppress("FunctionName")
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
// 0001
// 已确认 查好友列表的列表
......@@ -15,6 +23,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
// 00 00
internal inline class FriendListList(val delegate: List<FriendList>): Packet
internal object QueryFriendListListPacket : SessionPacketFactory<FriendList>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendList {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
// 0134
......
......@@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.ACCOUNT_INFO)
internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
operator fun invoke(
qq: UInt,
......
......@@ -99,7 +99,6 @@ internal class ImageUploadInfo(
/**
* 获取 Image Id 和上传用的一个 uKey
*/
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.11.22", timVersion = "2.3.2 (21173)")
internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
......
......@@ -17,7 +17,6 @@ inline class NicknameMap(val delegate: Map<UInt, String>) : Packet
/**
* 批量查询昵称.
*/
@AnnotatedId(KnownPacketId.QUERY_NICKNAME)
internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
/**
* 单个查询.
......@@ -135,7 +134,6 @@ body=03 00 00 00 00 00 00 00 00 00 00 00 03 8E 3C A6 A0 EE EF 02 07 6C 01 14 E8
/**
* 请求获取头像
*/ // ? 这个包的数据跟下面那个包一样
@AnnotatedId(KnownPacketId.REQUEST_PROFILE_AVATAR)
internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>() {
//00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
operator fun invoke(
......@@ -159,7 +157,6 @@ internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>()
*
* @see Profile
*/
@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS)
internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfileDetailsResponse>() {
//00 01 3E F8 FB E3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
//00 01 B1 89 BE 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
......@@ -211,7 +208,6 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
}
}
@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS)
internal data class RequestProfileDetailsResponse(
val qq: UInt,
val profile: Profile
......
......@@ -15,7 +15,6 @@ import net.mamoe.mirai.utils.io.writeZero
*/
inline class FriendNameRemark(val value: String) : Packet
@AnnotatedId(KnownPacketId.QUERY_FRIEND_REMARK)
internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() {
/**
* 查询好友的备注
......
......@@ -11,7 +11,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.md5
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
operator fun invoke(
......
......@@ -7,9 +7,7 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.AnnotatedId
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.SessionPacketFactory
......@@ -23,7 +21,6 @@ data class FriendStatusChanged(
/**
* 好友在线状态改变
*/
@AnnotatedId(KnownPacketId.FRIEND_ONLINE_STATUS_CHANGE)
internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged {
......
......@@ -13,7 +13,6 @@ import net.mamoe.mirai.contact.internal.MemberImpl
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact
......@@ -76,7 +75,7 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
discardExact(1) // 01
val qq = bot.getQQ(readUInt())
val member = Member(qq, group, MemberPermission.MEMBER, qq.coroutineContext)
val member = group.Member(qq, MemberPermission.MEMBER, qq.coroutineContext)
return if (readByte().toInt() == 0x03) {
MemberJoinEventPacket(member, null)
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.toUHexString
// region mute
/**
......@@ -59,6 +61,7 @@ class MemberUnmuteEvent(
/**
* 机器人被解除禁言事件
*/
@Suppress("SpellCheckingInspection")
class BeingUnmutedEvent(
override val operator: Member
) : UnmuteEvent() {
......@@ -73,15 +76,24 @@ sealed class UnmuteEvent : EventOfMute() {
// endregion
internal object Unknown0x02DCPacketFlag0x0EMaybeMutePacket : EventOfMute() {
override val operator: Member get() = error("Getting a field from Unknown0x02DCPacketFlag0x0EMaybeMutePacket")
override val group: Group get() = error("Getting a field from Unknown0x02DCPacketFlag0x0EMaybeMutePacket")
override fun toString(): String = "Unknown0x02DCPacketFlag0x0EMaybeMutePacket"
}
sealed class EventOfMute : EventPacket {
abstract val operator: Member
abstract val group: Group
}
// TODO: 2019/12/14 这可能不只是禁言的包.
internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute {
//取消
//00 00 00 11 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
//00 00 00 11 00
// 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
// 01 01
// 22 96 29 7B
// 0C 01
......@@ -92,7 +104,8 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
// 00 00 00 00
// 禁言
//00 00 00 11 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
//00 00 00 11 00
// 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00
// 01
// 01
// 22 96 29 7B
......@@ -104,28 +117,42 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
// 01
// 76 E4 B8 DD
// 00 27 8D 00
discardExact(19)
discardExact(2)
val group = bot.getGroup(readUInt())
discardExact(2)
val operator = group.getMember(readUInt())
discardExact(4) //time
discardExact(2)
val memberQQ = readUInt()
val durationSeconds = readUInt().toInt()
return if (durationSeconds == 0) {
if (memberQQ == bot.qqAccount) {
BeingUnmutedEvent(operator)
} else {
MemberUnmuteEvent(group.getMember(memberQQ), operator)
discardExact(3)
return when (val flag = readByte().toUInt()) {
0x0Eu -> {
//00 00 00 0E 00 08 00 02 00 01 00
// 0A 00 04 01 00 00 00 35 DB 60 A2 11 00 3E 08 07 20 A2 C1 ED AE 03 5A 34 08 A2 FF 8C F0 03 1A 19 08 F4 0E 10 FE 8C D3 EF 05 18 84 A1 F8 F9 06 20 00 28 00 30 A2 FF 8C F0 03 2A 0D 08 00 12 09 08 F4 0E 10 00 18 01 20 00 30 00 38 00
Unknown0x02DCPacketFlag0x0EMaybeMutePacket
}
} else {
if (memberQQ == bot.qqAccount) {
BeingMutedEvent(durationSeconds, operator)
} else {
MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator)
0x11u -> {
discardExact(15)
discardExact(2)
val group = bot.getGroup(readUInt())
discardExact(2)
val operator = group.getMember(readUInt())
discardExact(4) //time
discardExact(2)
val memberQQ = readUInt()
val durationSeconds = readUInt().toInt()
if (durationSeconds == 0) {
if (memberQQ == bot.qqAccount) {
BeingUnmutedEvent(operator)
} else {
MemberUnmuteEvent(group.getMember(memberQQ), operator)
}
} else {
if (memberQQ == bot.qqAccount) {
BeingMutedEvent(durationSeconds, operator)
} else {
MemberMuteEvent(group.getMember(memberQQ), durationSeconds, operator)
}
}
}
else -> error("Unsupported flag in 0x02DC packet. flag=$flag, remainning=${readBytes().toUHexString()}")
}
}
}
\ No newline at end of file
......@@ -11,7 +11,6 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
......@@ -117,6 +116,9 @@ data class GroupMessage(
override val message: MessageChain
) : MessagePacket<Member, Group>() {
/*
01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04
*/
override val subject: Group get() = group
inline fun At.member(): Member = group.getMember(this.target)
......
......@@ -12,7 +12,6 @@ internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
override val value: ByteArray = TIMProtocol.key00BA
}
@AnnotatedId(KnownPacketId.CAPTCHA)
internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(CaptchaKey) {
/**
* 请求验证码传输
......
......@@ -16,7 +16,6 @@ import net.mamoe.mirai.utils.io.writeQQ
/**
* 改变在线状态: "我在线上", "隐身" 等
*/
@AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS)
internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
operator fun invoke(
bot: UInt,
......
......@@ -13,7 +13,6 @@ import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ
@NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
operator fun invoke(
bot: UInt,
......@@ -31,5 +30,4 @@ internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>(
}
@NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
internal object HeartbeatPacketResponse : Packet, Subscribable
\ No newline at end of file
......@@ -44,7 +44,6 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr
/**
* 提交密码
*/
@AnnotatedId(KnownPacketId.LOGIN)
internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
operator fun invoke(
bot: UInt,
......
......@@ -23,7 +23,6 @@ internal inline class SKey(
* 请求 `SKey`
* SKey 用于 http api
*/
@AnnotatedId(KnownPacketId.S_KEY)
internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
operator fun invoke(
bot: UInt,
......
......@@ -9,7 +9,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.localIpAddress
@AnnotatedId(KnownPacketId.SESSION_KEY)
internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
operator fun invoke(
bot: UInt,
......
......@@ -20,7 +20,6 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.TOUCH)
internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
operator fun invoke(
bot: UInt,
......@@ -45,7 +44,7 @@ internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>
}
}
internal sealed class TouchResponse : Packet {
internal sealed class TouchResponse : Packet {
class OK(
var loginTime: Int,
val loginIP: String,
......
......@@ -62,6 +62,12 @@ class BotConfiguration : CoroutineContext.Element {
* 验证码处理器
*/
var captchaSolver: CaptchaSolver = DefaultCaptchaSolver
/**
* 登录完成后几秒会收到好友消息的历史记录,
* 这些历史记录不会触发事件.
* 这个选项为是否把这些记录添加到日志
*/
var logPreviousMessages: Boolean = false
companion object Key : CoroutineContext.Key<BotConfiguration> {
/**
......
@file:Suppress("unused", "UNUSED_PARAMETER")
package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Group
/**
* 在获取 [Group] 对象等操作时可能出现的异常
*/
class GroupNotFoundException : Exception {
constructor()
constructor(message: String?)
constructor(message: String?, cause: Throwable?)
constructor(cause: Throwable?)
}
open class MiraiInternalException : Exception {
constructor()
constructor(message: String?)
constructor(message: String?, cause: Throwable?)
constructor(cause: Throwable?)
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils
......
......@@ -54,6 +54,17 @@ internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
return bytes.toReadPacket()
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal inline fun <R> ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R {
val bytes = this.readBytes()
try {
return block(bytes.toReadPacket())
} catch (e: Throwable) {
DebugLogger.debug("Error in ByteReadPacket $name=" + bytes.toUHexString())
throw e
}
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this"))
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket {
val bytes = this.readBytes()
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE")
package net.mamoe.mirai.utils.io
......@@ -95,7 +95,7 @@ fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMa
}
/**
* 读扁平的 tag-UVarInt map. 重复的 tag 将不会只保留最后一个
* 读扁平的 tag-UVarInt map. 重复的 tag 将只保留最后一个
*
* tag: UByte
* value: UVarint
......@@ -140,10 +140,11 @@ fun Map<UInt, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 1) =
}
})
@Suppress("NOTHING_TO_INLINE")
internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported")
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}")
internal inline fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}")
internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed")
@JvmName("printTLVStringMap")
......
package net.mamoe.mirai.contact
import net.mamoe.mirai.test.shouldBeEqualTo
import org.junit.Test
import kotlin.random.Random
internal class GroupIdConversionsKtTest {
@UseExperimental(ExperimentalUnsignedTypes::class)
@Test
fun toInternalId() {
repeat(1000000) { _ ->
val it = Random.nextInt()
try {
GroupId(it.toUInt()).toInternalId() shouldBeEqualTo GroupId(it.toUInt()).toInternalIdOld()
} catch (e: Throwable) {
println(it)
throw e
}
}
}
@UseExperimental(ExperimentalUnsignedTypes::class)
@Test
fun toId() {
repeat(1000000) { _ ->
val it = Random.nextInt()
try {
GroupInternalId(it.toUInt()).toId() shouldBeEqualTo GroupInternalId(it.toUInt()).toIdOld()
} catch (e: Throwable) {
println(it)
throw e
}
}
}
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private fun GroupId.toInternalIdOld(): GroupInternalId {//求你别出错
val left: Long = this.value.toString().let {
if (it.length <= 6) {
return GroupInternalId(this.value)
}
it.substring(0, it.length - 6).toLong()
}
val right: Long = this.value.toString().let {
it.substring(it.length - 6).toLong()
}
return GroupInternalId(
when (left) {
in 1..10 -> {
((left + 202).toString() + right.toString()).toUInt()
}
in 11..19 -> {
((left + 469).toString() + right.toString()).toUInt()
}
in 20..66 -> {
((left + 208).toString() + right.toString()).toUInt()
}
in 67..156 -> {
((left + 1943).toString() + right.toString()).toUInt()
}
in 157..209 -> {
((left + 199).toString() + right.toString()).toUInt()
}
in 210..309 -> {
((left + 389).toString() + right.toString()).toUInt()
}
in 310..499 -> {
((left + 349).toString() + right.toString()).toUInt()
}
else -> this.value
}
)
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private fun GroupInternalId.toIdOld(): GroupId = with(value) {
//求你别出错
var left: UInt = this.toString().let {
if (it.length <= 6) {
return GroupId(value)
}
it.substring(0 until it.length - 6).toUInt()
}
return GroupId(when (left.toInt()) {
in 203..212 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 202u).toString() + right.toString()).toUInt()
}
in 480..488 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 469u).toString() + right.toString()).toUInt()
}
in 2100..2146 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 208u).toString() + right.toString()).toUInt()
}
in 2010..2099 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 6).toUInt()
}
((left - 1943u).toString() + right.toString()).toUInt()
}
in 2147..2199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 199u).toString() + right.toString()).toUInt()
}
in 4100..4199 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 389u).toString() + right.toString()).toUInt()
}
in 3800..3989 -> {
val right: UInt = this.toString().let {
it.substring(it.length - 7).toUInt()
}
left = left.toString().substring(0 until 3).toUInt()
((left - 349u).toString() + right.toString()).toUInt()
}
else -> value
})
}
\ No newline at end of file
@file:Suppress("RemoveRedundantBackticks", "NonAsciiCharacters")
package net.mamoe.mirai.utils
import kotlinx.coroutines.*
import net.mamoe.mirai.test.shouldBeEqualTo
import net.mamoe.mirai.test.shouldBeTrue
import org.junit.Test
import kotlin.system.exitProcess
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@MiraiExperimentalAPI
internal class LockFreeLinkedListTest {
init {
GlobalScope.launch {
delay(30 * 1000)
exitProcess(-100)
}
}
@Test
fun addAndGetSingleThreaded() {
val list = LockFreeLinkedList<Int>()
list.addLast(1)
list.addLast(2)
list.addLast(3)
list.addLast(4)
list.size shouldBeEqualTo 4
}
@Test
fun addAndGetConcurrent() = runBlocking {
//withContext(Dispatchers.Default){
val list = LockFreeLinkedList<Int>()
list.concurrentDo(1000, 10) { addLast(1) }
list.size shouldBeEqualTo 1000 * 10
list.concurrentDo(100, 10) {
remove(1).shouldBeTrue()
}
list.size shouldBeEqualTo 1000 * 10 - 100 * 10
//}
}
@Test
fun addAndGetMassConcurrentAccess() = runBlocking {
val list = LockFreeLinkedList<Int>()
val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } }
//delay(1) // let addJob fly
if (addJob.isCompleted) {
error("Number of elements are not enough")
}
val foreachJob = async {
list.concurrentDo(1, 10000) {
forEach { it + it }
}
}
val removeLastJob = async {
list.concurrentDo(1, 15000) {
removeLast() shouldBeEqualTo 1
}
}
val removeFirstJob = async {
list.concurrentDo(1, 10000) {
removeFirst() shouldBeEqualTo 1
}
}
val addJob2 = async {
list.concurrentDo(1, 5000) {
addLast(1)
}
}
val removeExactJob = launch {
list.concurrentDo(3, 1000) {
remove(1).shouldBeTrue()
}
}
val filteringGetOrAddJob = launch {
list.concurrentDo(1, 10000) {
filteringGetOrAdd({ it == 2 }, { 1 })
}
}
joinAll(addJob, addJob2, foreachJob, removeLastJob, removeFirstJob, removeExactJob, filteringGetOrAddJob)
list.size shouldBeEqualTo 2 * 30000 - 1 * 15000 - 1 * 10000 + 1 * 5000 - 3 * 1000 + 1 * 10000
}
@Test
fun removeWhileForeach() {
val list = LockFreeLinkedList<Int>()
repeat(10) { list.addLast(it) }
list.forEach {
list.remove(it + 1)
}
list.peekFirst() shouldBeEqualTo 0
}
@Test
fun remove() {
val list = LockFreeLinkedList<Int>()
assertFalse { list.remove(1) }
assertEquals(0, list.size)
list.addLast(1)
assertTrue { list.remove(1) }
assertEquals(0, list.size)
list.addLast(2)
assertFalse { list.remove(1) }
assertEquals(1, list.size)
}
@Test
fun addAll() {
val list = LockFreeLinkedList<Int>()
list.addAll(listOf(1, 2, 3, 4, 5))
list.size shouldBeEqualTo 5
}
@Test
fun clear() {
val list = LockFreeLinkedList<Int>()
list.addAll(listOf(1, 2, 3, 4, 5))
list.size shouldBeEqualTo 5
list.clear()
list.size shouldBeEqualTo 0
}
@UseExperimental(ExperimentalUnsignedTypes::class)
@Test
fun withInlineClassElements() {
val list = LockFreeLinkedList<UInt>()
list.addAll(listOf(1u, 2u, 3u, 4u, 5u))
list.size shouldBeEqualTo 5
list.toString() shouldBeEqualTo "[1, 2, 3, 4, 5]"
}
@Test
fun `filteringGetOrAdd when add`() {
val list = LockFreeLinkedList<Int>()
list.addAll(listOf(1, 2, 3, 4, 5))
val value = list.filteringGetOrAdd({ it == 6 }, { 6 })
println("Check value")
value shouldBeEqualTo 6
println("Check size")
println(list.getLinkStructure())
list.size shouldBeEqualTo 6
}
@Test
fun `filteringGetOrAdd when get`() {
val list = LockFreeLinkedList<Int>()
list.addAll(listOf(1, 2, 3, 4, 5))
val value = list.filteringGetOrAdd({ it == 2 }, { 2 })
println("Check value")
value shouldBeEqualTo 2
println("Check size")
println(list.getLinkStructure())
list.size shouldBeEqualTo 5
}
@Test
fun `filteringGetOrAdd when empty`() {
val list = LockFreeLinkedList<Int>()
val value = list.filteringGetOrAdd({ it == 2 }, { 2 })
println("Check value")
value shouldBeEqualTo 2
println("Check size")
println(list.getLinkStructure())
list.size shouldBeEqualTo 1
}
/*
@Test
fun indexOf() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 3, 3)
assertEquals(0, list.indexOf(1))
assertEquals(2, list.indexOf(3))
assertEquals(-1, list.indexOf(4))
}
@Test
fun iterator() {
var list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2)
list.forEach {
it shouldBeEqualTo 2
}
list = lockFreeLinkedListOf(1, 2)
list.joinToString { it.toString() } shouldBeEqualTo "1, 2"
list = lockFreeLinkedListOf(1, 2)
val iterator = list.iterator()
iterator.remove()
var reached = false
for (i in iterator) {
i shouldBeEqualTo 2
reached = true
}
reached shouldBeEqualTo true
list.joinToString { it.toString() } shouldBeEqualTo "2"
iterator.remove()
assertFailsWith<NoSuchElementException> { iterator.remove() }
}
@Test
fun `lastIndexOf of exact 1 match at first`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2, 1)
list.lastIndexOf(2) shouldBeEqualTo 0
}
@Test
fun `lastIndexOf of exact 1 match`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2)
list.lastIndexOf(2) shouldBeEqualTo 1
}
@Test
fun `lastIndexOf of multiply matches`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 2)
list.lastIndexOf(2) shouldBeEqualTo 2
}
@Test
fun `lastIndexOf of no match`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2)
list.lastIndexOf(3) shouldBeEqualTo -1
}
@Test
fun `lastIndexOf of many elements`() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 4, 2, 3, 4, 5)
list.lastIndexOf(4) shouldBeEqualTo 4
}
*/
}
@UseExperimental(ExperimentalCoroutinesApi::class)
@MiraiExperimentalAPI
internal suspend inline fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutines: Int, times: Int, crossinline todo: E.() -> Unit) =
coroutineScope {
repeat(numberOfCoroutines) {
launch(start = CoroutineStart.UNDISPATCHED) {
repeat(times) {
todo()
}
}
}
}
\ No newline at end of file
......@@ -41,7 +41,7 @@ fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id
dependencies {
implementation(project(":mirai-core"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
// runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
......
......@@ -3,7 +3,7 @@ apply plugin: "java"
dependencies {
api project(":mirai-core")
runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
// runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
}
......@@ -4,8 +4,8 @@ apply plugin: "application"
dependencies {
api project(":mirai-core")
runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
//runtime files("../../mirai-core/build/classes/atomicfu/jvm/main") // classpath is not set correctly by IDE
//runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment