diff --git a/README.md b/README.md index 45b309974..96bd521f1 100644 --- a/README.md +++ b/README.md @@ -31,17 +31,18 @@ repositories{ 您需要将 `VERSION` 替换为最新的版本(如 `0.5.1`): [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) Mirai 目前还处于实验性阶段, 建议您时刻保持最新版本. +现在 Mirai 只支持 TIM PC 协议. **common** ```kotlin -implementation("net.mamoe:mirai-core-common:VERSION") +implementation("net.mamoe:mirai-core-timpc-common:VERSION") ``` **jvm** ```kotlin -implementation("net.mamoe:mirai-core-jvm:VERSION") +implementation("net.mamoe:mirai-core-timpc-jvm:VERSION") ``` **android** ```kotlin -implementation("net.mamoe:mirai-core-android:VERSION") +implementation("net.mamoe:mirai-core-timpc-android:VERSION") ``` ## Try diff --git a/mirai-api-http/build.gradle.kts b/mirai-api-http/build.gradle.kts index 0df368b37..4b7d22846 100644 --- a/mirai-api-http/build.gradle.kts +++ b/mirai-api-http/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { sourceSets["main"].apply { dependencies { - implementation(project(":mirai-core")) + implementation(project(":mirai-core-timpc")) implementation(kotlin("stdlib-jdk8", kotlinVersion)) implementation(kotlin("stdlib-jdk7", kotlinVersion)) diff --git a/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt b/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt index 4c990a704..bc4017a34 100644 --- a/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt +++ b/mirai-api-http/src/main/kotlin/net.mamoe.mirai.api.http/MiraiHttpApplication.kt @@ -17,9 +17,7 @@ import io.ktor.util.pipeline.ContextDsl import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineInterceptor 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.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToUBytes @@ -40,7 +38,7 @@ fun Application.mirai() { } mirai("/sendGroupMessage") { - Bot.instanceWhose(qq = param("bot")).getGroup(param("group")).sendMessage(param("message")) + Bot.instanceWhose(qq = param("bot")).getGroup(param("group")).sendMessage(param("message")) call.ok() } diff --git a/mirai-core-timpc/build.gradle.kts b/mirai-core-timpc/build.gradle.kts new file mode 100644 index 000000000..381e00d08 --- /dev/null +++ b/mirai-core-timpc/build.gradle.kts @@ -0,0 +1,110 @@ +@file:Suppress("UNUSED_VARIABLE") + +plugins { + kotlin("multiplatform") + id("kotlinx-atomicfu") + id("com.android.library") + id("kotlinx-serialization") + `maven-publish` + id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME +} + +apply(from = rootProject.file("gradle/publish.gradle")) + +val kotlinVersion: String by rootProject.ext +val atomicFuVersion: String by rootProject.ext +val coroutinesVersion: String by rootProject.ext +val kotlinXIoVersion: String by rootProject.ext +val coroutinesIoVersion: String by rootProject.ext + +val klockVersion: String by rootProject.ext +val ktorVersion: String by rootProject.ext + +val serializationVersion: String by rootProject.ext + +fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" + +fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" + + +description = "QQ protocol library" + +kotlin { + android("android") { + publishAllLibraryVariants() + project.android { + compileSdkVersion(29) + + defaultConfig { + minSdkVersion(15) + } + + // sourceSets.filterIsInstance(com.android.build.gradle.api.AndroidSourceSet::class.java).forEach { + // it.manifest.srcFile("src/androidMain/res/AndroidManifest.xml") + // it.res.srcDirs(file("src/androidMain/res")) + // } + //(sourceSets["main"] as AndroidSourceSet).java.srcDirs(file("src/androidMain/kotlin")) + } + } + + jvm("jvm") { + } + + sourceSets { + all { + languageSettings.enableLanguageFeature("InlineClasses") + languageSettings.useExperimentalAnnotation("kotlin.Experimental") + + dependencies { + api(project(":mirai-core")) + + api(kotlin("stdlib", kotlinVersion)) + api(kotlin("serialization", kotlinVersion)) + + api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") + api(kotlinx("io", kotlinXIoVersion)) + api(kotlinx("coroutines-io", coroutinesIoVersion)) + api(kotlinx("coroutines-core", coroutinesVersion)) + } + } + commonMain { + dependencies { + } + } + commonTest { + dependencies { + api(kotlin("test-annotations-common")) + api(kotlin("test-common")) + } + } + + val androidMain by getting { + dependencies { + } + } + + val androidTest by getting { + dependencies { + api(kotlin("test", kotlinVersion)) + api(kotlin("test-junit", kotlinVersion)) + api(kotlin("test-annotations-common")) + api(kotlin("test-common")) + } + } + + val jvmMain by getting { + dependencies { + } + } + + val jvmTest by getting { + dependencies { + api(kotlin("test", kotlinVersion)) + api(kotlin("test-junit", kotlinVersion)) + implementation("org.pcap4j:pcap4j-distribution:1.8.2") + + //runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE + } + } + } +} \ No newline at end of file diff --git a/mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt b/mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt new file mode 100644 index 000000000..756b7ba83 --- /dev/null +++ b/mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt @@ -0,0 +1,18 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.timpc + +import kotlinx.io.InputStream +import kotlinx.io.streams.inputStream +import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.utils.MiraiLogger +import kotlin.coroutines.CoroutineContext + +internal actual class TIMPCBot actual constructor( + account: BotAccount, + logger: MiraiLogger?, + context: CoroutineContext +) : TIMPCBotBase(account, logger, context) { + suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherAndroid.kt b/mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt similarity index 56% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherAndroid.kt rename to mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt index 123586f9a..10e631a2f 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherAndroid.kt +++ b/mirai-core-timpc/src/androidMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt @@ -1,4 +1,4 @@ -package net.mamoe.mirai.network.protocol.timpc +package net.mamoe.mirai.timpc.network import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher @@ -9,4 +9,5 @@ import java.util.concurrent.Executors * * JVM: 独立的 4 thread 调度器 */ -internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() \ No newline at end of file +internal actual val NetworkDispatcher: CoroutineDispatcher + get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher() \ No newline at end of file diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPC.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPC.kt new file mode 100644 index 000000000..c508fd0a0 --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPC.kt @@ -0,0 +1,76 @@ +@file:Suppress("FunctionName", "unused", "SpellCheckingInspection") + +package net.mamoe.mirai.timpc + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.BotFactory +import net.mamoe.mirai.timpc.TIMPC.Bot +import net.mamoe.mirai.utils.MiraiLogger +import kotlin.coroutines.coroutineContext + +/** + * TIM PC 协议的 [Bot] 构造器. + */ +object TIMPC : BotFactory { + /** + * 在当前 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * suspend fun myProcess(){ + * TIMPC.Bot(account, logger) + * } + * ``` + */ + override suspend fun Bot(account: BotAccount, logger: MiraiLogger?): Bot = + TIMPCBot(account, logger, coroutineContext) + + /** + * 在当前 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * suspend fun myProcess(){ + * TIMPC.Bot(account, logger) + * } + * ``` + */ + override suspend fun Bot(qq: Long, password: String, logger: MiraiLogger?): Bot = + TIMPCBot(BotAccount(qq, password), logger, coroutineContext) + + /** + * 在特定的 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * fun myProcess(){ + * TIMPC.run { + * GlobalScope.Bot(account, logger) + * } + * } + * ``` + */ + override fun CoroutineScope.Bot(qq: Long, password: String, logger: MiraiLogger?): Bot = + TIMPCBot(BotAccount(qq, password), logger, coroutineContext) + + /** + * 在特定的 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * fun myProcess(){ + * TIMPC.run { + * GlobalScope.Bot(account, logger) + * } + * } + * ``` + */ + override fun CoroutineScope.Bot(account: BotAccount, logger: MiraiLogger?): Bot = + TIMPCBot(account, logger, coroutineContext) +} \ No newline at end of file diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPCBot.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPCBot.kt new file mode 100644 index 000000000..e1332c944 --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/TIMPCBot.kt @@ -0,0 +1,243 @@ +package net.mamoe.mirai.timpc + +import kotlinx.coroutines.* +import net.mamoe.mirai.Bot +import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.BotImpl +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.ImageId0x03 +import net.mamoe.mirai.message.data.ImageId0x06 +import net.mamoe.mirai.network.data.* +import net.mamoe.mirai.qqAccount +import net.mamoe.mirai.timpc.internal.RawGroupInfo +import net.mamoe.mirai.timpc.network.GroupImpl +import net.mamoe.mirai.timpc.network.MemberImpl +import net.mamoe.mirai.timpc.network.QQImpl +import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler +import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler +import net.mamoe.mirai.timpc.network.packet.KnownPacketId +import net.mamoe.mirai.timpc.network.packet.OutgoingPacket +import net.mamoe.mirai.timpc.network.packet.SessionKey +import net.mamoe.mirai.timpc.network.packet.action.* +import net.mamoe.mirai.timpc.utils.assertUnreachable +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail +import net.mamoe.mirai.utils.io.logStacktrace +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmSynthetic + +internal expect class TIMPCBot constructor( + account: BotAccount, + logger: MiraiLogger?, + context: CoroutineContext +) : TIMPCBotBase + +@UseExperimental(MiraiInternalAPI::class) +internal abstract class TIMPCBotBase constructor( + account: BotAccount, + logger: MiraiLogger?, + context: CoroutineContext +) : BotImpl(account, logger ?: DefaultLogger("Bot(" + account.id + ")"), context) { + companion object { + init { + KnownPacketId.values() /* load id classes */ + } + } + + final override val network: TIMBotNetworkHandler get() = _network + + inline val sessionKey: SessionKey get() = network.sessionKey + + private lateinit var _network: TIMBotNetworkHandler + + override suspend fun login(configuration: BotConfiguration) = + reinitializeNetworkHandler(configuration, null) + + // shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close + internal fun tryReinitializeNetworkHandler( + configuration: BotConfiguration, + cause: Throwable? + ): Job = launch { + repeat(configuration.reconnectionRetryTimes) { + try { + reinitializeNetworkHandler(configuration, cause) + logger.info("Reconnected successfully") + return@launch + } catch (e: LoginFailedException){ + delay(configuration.reconnectPeriodMillis) + } + } + } + + private suspend fun reinitializeNetworkHandler( + configuration: BotConfiguration, + cause: Throwable? + ) { + logger.info("BotAccount: $qqAccount") + 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 as TIMPCBot) + + _network.login() + } + + final override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult { + return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect()) { + is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED + is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED + + is CanAddFriendResponse.ReadyToAdd, + is CanAddFriendResponse.RequireVerification -> { + val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect().key + AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect() + AddFriendResult.WAITING_FOR_APPROVE + } //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥 + + // 似乎 RequireVerification 和 ReadyToAdd 判断错了. 需要重新检查一下 + + // TODO: 2019/11/11 需要验证问题的情况 + + /*is CanAddFriendResponse.ReadyToAdd -> { + // TODO: 2019/11/11 这不需要验证信息的情况 + + //AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync().await() + TODO() + }*/ + } + } + + final override suspend fun approveFriendAddRequest(id: Long, remark: String?) { + AddFriendPacket.Approve(qqAccount, sessionKey, 0, id, remark).sendAndExpect() + } + + + // region contacts + + final override val groups: ContactList = ContactList(LockFreeLinkedList()) + final override val qqs: ContactList = ContactList(LockFreeLinkedList()) + + /** + * 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个. + */ + @UseExperimental(MiraiInternalAPI::class) + @JvmSynthetic + final override fun getQQ(id: Long): QQ = qqs.delegate.getOrAdd(id) { QQ(id) } + + @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class) + final override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline { + val info: RawGroupInfo = try { + when (val response = + GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect()) { + is RawGroupInfo -> response + is GroupNotFound -> throw GroupNotFoundException("id=${id.value}") + else -> assertUnreachable() + } + } catch (e: Exception) { + throw IllegalStateException("Cannot obtain group info for id ${id.value}", e) + } + + return groups.delegate.getOrAdd(id.value) { Group(id, info) } + } + + final override suspend fun getGroup(internalId: GroupInternalId): Group = + getGroup0(internalId.toId().value) + + private suspend inline fun getGroup0(id: Long): Group = + groups.delegate.getOrNull(id) ?: inline { + val info: RawGroupInfo = try { + GroupPacket.QueryGroupInfo(qqAccount, GroupId(id).toInternalId(), sessionKey).sendAndExpect() + } catch (e: Exception) { + e.logStacktrace() + error("Cannot obtain group info for id $id") + } + + return groups.delegate.getOrAdd(id) { Group(GroupId(id), info) } + } + + @UseExperimental(MiraiInternalAPI::class) + final override suspend fun getGroup(id: Long): Group = getGroup0(id.coerceAtLeastOrFail(0)) + + + internal suspend inline fun OutgoingPacket.sendAndExpectAsync( + checkSequence: Boolean = true, + noinline handler: suspend (P) -> R + ): Deferred { + val deferred: CompletableDeferred = CompletableDeferred(coroutineContext[Job]) + network.temporaryPacketHandlers.addLast( + TemporaryPacketHandler( + expectationClass = P::class, + deferred = deferred, + checkSequence = if (checkSequence) this.sequenceId else null, + callerContext = coroutineContext + deferred, + handler = handler + ) + ) + network.socket.sendPacket(this) + return deferred + } + + internal suspend inline fun OutgoingPacket.sendAndExpectAsync(checkSequence: Boolean = true): Deferred

= + sendAndExpectAsync(checkSequence) { it } + + internal suspend inline fun OutgoingPacket.sendAndExpect( + checkSequence: Boolean = true, + timeoutMillis: Long = 5.secondsToMillis, + crossinline mapper: (P) -> R + ): R = withTimeout(timeoutMillis) { sendAndExpectAsync(checkSequence) { mapper(it) }.await() } + + internal suspend inline fun OutgoingPacket.sendAndExpect( + checkSequence: Boolean = true, + timeoutMillist: Long = 5.secondsToMillis + ): P = withTimeout(timeoutMillist) { sendAndExpectAsync(checkSequence) { it }.await() } + + internal suspend inline fun OutgoingPacket.send() = network.socket.sendPacket(this) + + + final override suspend fun Image.getLink(): ImageLink = when (val id = this.id) { + is ImageId0x03 -> GroupImagePacket.RequestImageLink(qqAccount, sessionKey, id).sendAndExpect().requireSuccess() + is ImageId0x06 -> FriendImagePacket.RequestImageLink(qqAccount, sessionKey, id).sendAndExpect() + else -> assertUnreachable() + } + + @Suppress("FunctionName") + @PublishedApi + internal fun CoroutineScope.Group(groupId: GroupId, info: RawGroupInfo): Group { + return GroupImpl(this@TIMPCBotBase as TIMPCBot, groupId, coroutineContext).apply { this.info = info.parseBy(this); launch { startUpdater() } } + } + + @Suppress("FunctionName") + @PublishedApi + internal fun Group.Member(qq: QQ, permission: MemberPermission): Member { + return MemberImpl(qq, this, permission, coroutineContext).apply { launch { startUpdater() } } + } + + @Suppress("FunctionName") + internal fun CoroutineScope.QQ(id: Long): QQ = + QQImpl(this@TIMPCBotBase as TIMPCBot, id, coroutineContext).apply { launch { startUpdater() } } +} + +internal inline fun inline(block: () -> R): R = block() + +internal suspend fun TIMPCBot.sendPacket(toSend: OutgoingPacket) = this.network.socket.sendPacket(toSend) + +/** + * 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. + * 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法 + */ +@UseExperimental(ExperimentalContracts::class) +internal inline fun Contact.withTIMPCBot(block: TIMPCBot.() -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return (bot as TIMPCBot).run(block) +} diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/internal/RawGroupInfo.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/internal/RawGroupInfo.kt new file mode 100644 index 000000000..94b90d38c --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/internal/RawGroupInfo.kt @@ -0,0 +1,37 @@ +package net.mamoe.mirai.timpc.internal + +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.network.data.GroupInfo +import net.mamoe.mirai.timpc.TIMPCBot +import net.mamoe.mirai.timpc.network.packet.action.GroupPacket +import net.mamoe.mirai.utils.LockFreeLinkedList +import net.mamoe.mirai.utils.MiraiInternalAPI + +data class RawGroupInfo( + val group: Long, + val owner: Long, + val name: String, + val announcement: String, + /** + * 含群主 + */ + val members: Map +) : GroupPacket.InfoResponse { + + @UseExperimental(MiraiInternalAPI::class) + fun parseBy(group: Group): GroupInfo = group.withBot { + this as? TIMPCBot ?: error("internal error: wrong Bot instance passed") + + val memberList = LockFreeLinkedList() + members.forEach { entry: Map.Entry -> + memberList.addLast(entry.key.qq().let { group.Member(it, entry.value) }) + } + return GroupInfo( + group, + this@RawGroupInfo.owner.qq().let { group.Member(it, MemberPermission.OWNER) }, + this@RawGroupInfo.name, + this@RawGroupInfo.announcement, + ContactList(memberList) + ) + } +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt similarity index 51% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt index d16b74c43..3191bf2e9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/ContactImpl.kt @@ -1,24 +1,29 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.contact.internal +package net.mamoe.mirai.timpc.network import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import net.mamoe.mirai.Bot +import kotlinx.coroutines.withContext import net.mamoe.mirai.contact.* -import net.mamoe.mirai.contact.data.Profile import net.mamoe.mirai.event.subscribeAlways -import net.mamoe.mirai.message.MessageChain -import net.mamoe.mirai.network.protocol.timpc.packet.action.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberJoinEventPacket -import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberQuitEvent -import net.mamoe.mirai.network.qqAccount -import net.mamoe.mirai.network.sessionKey +import net.mamoe.mirai.message.data.ImageId +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.network.data.FriendNameRemark +import net.mamoe.mirai.network.data.GroupInfo +import net.mamoe.mirai.network.data.PreviousNameList +import net.mamoe.mirai.network.data.Profile import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.sendPacket -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.withSession +import net.mamoe.mirai.timpc.TIMPCBot +import net.mamoe.mirai.timpc.internal.RawGroupInfo +import net.mamoe.mirai.timpc.network.packet.action.* +import net.mamoe.mirai.timpc.network.packet.event.MemberJoinEventPacket +import net.mamoe.mirai.timpc.network.packet.event.MemberQuitEvent +import net.mamoe.mirai.timpc.sendPacket +import net.mamoe.mirai.timpc.utils.assertUnreachable +import net.mamoe.mirai.timpc.withTIMPCBot +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.io.toUHexString import kotlin.coroutines.CoroutineContext internal sealed class ContactImpl : Contact { @@ -30,22 +35,12 @@ internal sealed class ContactImpl : Contact { internal abstract suspend fun startUpdater() } -/** - * 构造 [Group] - */ -@Suppress("FunctionName") -@PublishedApi -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() } - } - - @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") -internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId, override val coroutineContext: CoroutineContext) : +internal class GroupImpl internal constructor(bot: TIMPCBot, val groupId: GroupId, override val coroutineContext: CoroutineContext) : ContactImpl(), Group, CoroutineScope { - override val id: UInt get() = groupId.value + override val bot: TIMPCBot by bot.unsafeWeakRef() + + override val id: Long get() = groupId.value override val internalId = GroupId(id).toInternalId() internal lateinit var info: GroupInfo @@ -56,19 +51,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr override val announcement: String get() = info.announcement override val members: ContactList get() = info.members - override fun getMember(id: UInt): Member = - members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}") + override fun getMember(id: Long): Member = + members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is $id in group ${groupId.value}") override suspend fun sendMessage(message: MessageChain) { bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message)) } - override suspend fun updateGroupInfo(): GroupInfo = bot.withSession { + override suspend fun uploadImage(image: ExternalImage): ImageId = withTIMPCBot { + val userContext = coroutineContext + val response = GroupImagePacket.RequestImageId(bot.qqAccount, internalId, image, sessionKey).sendAndExpect() + + withContext(userContext) { + when (response) { + is ImageUploadInfo -> response.uKey?.let { + Http.postImage( + htcmd = "0x6ff0071", + uin = bot.qqAccount, + groupId = GroupId(id), + imageInput = image.input, + inputSize = image.inputSize, + uKeyHex = it.toUHexString("") + ) + } // if null: image already exists + + // TODO: 2019/11/17 超过大小的情况 + //is Overfile -> throw OverFileSizeMaxException() + else -> assertUnreachable() + } + } + + return image.groupImageId + } + + override suspend fun updateGroupInfo(): GroupInfo = withTIMPCBot { GroupPacket.QueryGroupInfo(qqAccount, internalId, sessionKey).sendAndExpect().parseBy(this@GroupImpl).also { info = it } } - override suspend fun quit(): QuitGroupResponse = bot.withSession { - GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect() + override suspend fun quit(): Boolean = withTIMPCBot { + GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect().isSuccess } @UseExperimental(MiraiInternalAPI::class) @@ -84,25 +105,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr override fun toString(): String = "Group(${this.id})" } -@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) : +internal class QQImpl @PublishedApi internal constructor(bot: TIMPCBot, override val id: Long, override val coroutineContext: CoroutineContext) : ContactImpl(), QQ, CoroutineScope { + override val bot: TIMPCBot by bot.unsafeWeakRef() + override suspend fun sendMessage(message: MessageChain) = bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message)) - override suspend fun queryProfile(): Profile = bot.withSession { + override suspend fun uploadImage(image: ExternalImage): ImageId = withTIMPCBot { + FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image).sendAndExpect().let { + when (it) { + is FriendImageUKey -> { + Http.postImage( + htcmd = "0x6ff0070", + uin = bot.qqAccount, + groupId = null, + uKeyHex = it.uKey.toUHexString(""), + imageInput = image.input, + inputSize = image.inputSize + ) + it.imageId + } + is FriendImageAlreadyExists -> it.imageId + is FriendImageOverFileSizeMax -> throw OverFileSizeMaxException() + else -> error("This shouldn't happen") + } + } + } + + override suspend fun queryProfile(): Profile = withTIMPCBot { RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey).sendAndExpect().profile } - override suspend fun queryPreviousNameList(): PreviousNameList = bot.withSession { + override suspend fun queryPreviousNameList(): PreviousNameList = withTIMPCBot { QueryPreviousNamePacket(bot.qqAccount, sessionKey, id).sendAndExpect() } - override suspend fun queryRemark(): FriendNameRemark = bot.withSession { + override suspend fun queryRemark(): FriendNameRemark = withTIMPCBot { QueryFriendRemarkPacket(bot.qqAccount, sessionKey, id).sendAndExpect() } @@ -114,10 +155,6 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot: override fun toString(): String = "QQ(${this.id})" } -@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() } } - /** * 群成员 */ @@ -130,13 +167,13 @@ internal data class MemberImpl( ) : QQ by delegate, CoroutineScope, Member, ContactImpl() { override fun toString(): String = "Member(id=${this.id}, group=${group.id}, permission=$permission)" - override suspend fun mute(durationSeconds: Int): Boolean = bot.withSession { + override suspend fun mute(durationSeconds: Int): Boolean = withTIMPCBot { require(durationSeconds > 0) { "duration must be greater than 0 second" } require(durationSeconds <= 30 * 24 * 3600) { "duration must be no more than 30 days" } if (permission == MemberPermission.OWNER) return false val operator = group.getMember(bot.qqAccount) - check(operator.id != id) { "The bot is the owner of group ${group.id.toLong()}, it cannot mute itself!" } + check(operator.id != id) { "The bot is the owner of group ${group.id}, it cannot mute itself!" } when (operator.permission) { MemberPermission.MEMBER -> return false MemberPermission.ADMINISTRATOR -> if (permission == MemberPermission.ADMINISTRATOR) return false @@ -153,7 +190,7 @@ internal data class MemberImpl( // TODO: 2019/12/6 更新群成员信息 } - override suspend fun unmute(): Unit = bot.withSession { + override suspend fun unmute(): Unit = withTIMPCBot { GroupPacket.Mute(qqAccount, group.internalId, sessionKey, id, 0u).sendAndExpect() } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMBotNetworkHandler.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMBotNetworkHandler.kt similarity index 80% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMBotNetworkHandler.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMBotNetworkHandler.kt index 8ff564757..9b9d41435 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMBotNetworkHandler.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMBotNetworkHandler.kt @@ -1,30 +1,28 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc +package net.mamoe.mirai.timpc.network import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.io.core.* import kotlinx.io.pool.useInstance import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.* -import net.mamoe.mirai.event.events.BeforePacketSendEvent +import net.mamoe.mirai.event.BroadcastControllable +import net.mamoe.mirai.event.Cancellable +import net.mamoe.mirai.event.Subscribable +import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotLoginSucceedEvent -import net.mamoe.mirai.event.events.PacketSentEvent -import net.mamoe.mirai.event.events.ServerPacketReceivedEvent import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter -import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.protocol.timpc.packet.login.* +import net.mamoe.mirai.network.data.LoginResult +import net.mamoe.mirai.network.data.Packet import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.OnlineStatus -import net.mamoe.mirai.utils.currentBotConfiguration +import net.mamoe.mirai.timpc.TIMPCBot +import net.mamoe.mirai.timpc.network.handler.DataPacketSocketAdapter +import net.mamoe.mirai.timpc.network.handler.TemporaryPacketHandler +import net.mamoe.mirai.timpc.network.packet.* +import net.mamoe.mirai.timpc.network.packet.login.* +import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.* import kotlin.coroutines.CoroutineContext -import kotlin.properties.Delegates import kotlin.random.Random /** @@ -39,9 +37,9 @@ internal expect val NetworkDispatcher: CoroutineDispatcher * * @see BotNetworkHandler */ -internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, override val bot: Bot) : - BotNetworkHandler, CoroutineScope { - +internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, bot: TIMPCBot) : + BotNetworkHandler(), CoroutineScope { + override val bot: TIMPCBot by bot.unsafeWeakRef() override val supervisor: CompletableJob = SupervisorJob(coroutineContext[Job]) override val coroutineContext: CoroutineContext = @@ -50,52 +48,43 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou ?: "an unnamed coroutine"} under TIMBotNetworkHandler", e) } + supervisor - override lateinit var socket: BotSocketAdapter + lateinit var socket: BotSocketAdapter private set - private val temporaryPacketHandlers = mutableListOf>() - private val handlersLock = Mutex() + internal val temporaryPacketHandlers = LockFreeLinkedList>() private var heartbeatJob: Job? = null - suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*, *>) { - handlersLock.withLock { - temporaryPacketHandlers.add(temporaryPacketHandler) - } - temporaryPacketHandler.send(this.session) - } + override suspend fun login() { - override suspend fun login(): LoginResult { - return withContext(this.coroutineContext) { - TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip -> - bot.logger.info("Connecting server $ip") - try { - withTimeout(3000) { - socket = BotSocketAdapter(ip) - } - } catch (e: Exception) { - return@withContext LoginResult.NETWORK_UNAVAILABLE + TIMProtocol.SERVER_IP.sortedBy { Random.nextInt() }.forEach { ip -> + bot.logger.info("Connecting server $ip") + try { + withTimeout(3000) { + socket = BotSocketAdapter(ip) } - - loginResult = CompletableDeferred() - - socket.resendTouch().takeIf { it != LoginResult.TIMEOUT }?.let { return@withContext it } - - bot.logger.warning("Timeout. Retrying next server") - - socket.close() + } catch (e: Exception) { + throw LoginFailedException(LoginResult.NETWORK_UNAVAILABLE) } - return@withContext LoginResult.TIMEOUT + + loginResult = CompletableDeferred() + + val result = socket.resendTouch() ?: return // success + result.takeIf { it != LoginResult.TIMEOUT }?.let { throw LoginFailedException(it) } + + bot.logger.warning("Timeout. Retrying next server") + + socket.close() } + throw LoginFailedException(LoginResult.TIMEOUT) } - internal var loginResult: CompletableDeferred = CompletableDeferred() - - override lateinit var session: BotSession + internal var loginResult: CompletableDeferred = CompletableDeferred() //private | internal - private var sessionKey: SessionKey by Delegates.notNull() + private var _sessionKey: SessionKey? = null + internal val sessionKey: SessionKey get() = _sessionKey ?: error("sessionKey is not yet initialized") override suspend fun awaitDisconnection() { heartbeatJob?.join() @@ -187,43 +176,38 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou } } - internal suspend fun resendTouch(): LoginResult /* = coroutineScope */ { + internal suspend fun resendTouch(): LoginResult? /* = coroutineScope */ { loginHandler?.close() loginHandler = LoginHandler() - val expect = expectPacket() - launch { processReceive() } - launch { - if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expect.join() } == null) { - loginResult.complete(LoginResult.TIMEOUT) + expectingTouchResponse = Job(supervisor) + try { + launch { processReceive() } + launch { + if (withTimeoutOrNull(currentBotConfiguration().touchTimeoutMillis) { expectingTouchResponse!!.join() } == null) { + loginResult.complete(LoginResult.TIMEOUT) + } } - } - sendPacket(TouchPacket(bot.qqAccount, serverIp, false)) + sendPacket(TouchPacket(bot.qqAccount, serverIp, false)) - return loginResult.await() + return loginResult.await() + } finally { + expectingTouchResponse = null + } } - private suspend inline fun expectPacket(): CompletableDeferred

{ - val receiving = CompletableDeferred

(coroutineContext[Job]) - subscribe> { - if (it.packet is P && it.bot === bot) { - receiving.complete(it.packet) - ListeningStatus.STOPPED - } else - ListeningStatus.LISTENING - } - return receiving - } + private var expectingTouchResponse: CompletableJob? = null private suspend fun handlePacket0( sequenceId: UShort, packet: TPacket, factory: PacketFactory ) { - if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled) - return + if (packet is TouchPacket.TouchResponse) { + expectingTouchResponse?.complete() + } if (!packet::class.annotations.filterIsInstance().any()) { if ((packet as? BroadcastControllable)?.shouldBroadcast != false) { @@ -236,12 +220,10 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou is Subscribable -> if ((packet as? BroadcastControllable)?.shouldBroadcast != false) packet.broadcast() } - // Remove first to release the lock - handlersLock.withLock { - temporaryPacketHandlers.filter { it.filter(session, packet, sequenceId) } - .also { temporaryPacketHandlers.removeAll(it) } - }.forEach { - it.doReceiveCatchingExceptions(packet) + temporaryPacketHandlers.forEach { + if (it.filter(packet, sequenceId) && temporaryPacketHandlers.remove(it)) { + it.doReceivePassingExceptionsToDeferred(packet) + } } if (factory is SessionPacketFactory<*>) { @@ -256,10 +238,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou internal suspend fun sendPacket(packet: OutgoingPacket): Unit = withContext(coroutineContext + CoroutineName("sendPacket")) { check(channel.isOpen) { "channel is not open" } - if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) { - return@withContext - } - packet.delegate.use { built -> val buffer = IoBuffer.Pool.borrow() try { @@ -291,8 +269,6 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou bot.logger.verbose("Packet sent: ${it.name}") } - PacketSentEvent(bot, packet).broadcast() - Unit } @@ -466,8 +442,8 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou } is RequestSessionPacket.SessionKeyResponse -> { - sessionKey = packet.sessionKey - bot.logger.info("sessionKey = ${sessionKey.value.toUHexString()}") + _sessionKey = packet.sessionKey + bot.logger.info("sessionKey = ${packet.sessionKey.value.toUHexString()}") setOnlineStatus(OnlineStatus.ONLINE)//required } @@ -475,14 +451,11 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou is ChangeOnlineStatusPacket.ChangeOnlineStatusResponse -> { BotLoginSucceedEvent(bot).broadcast() - - session = BotSession(sessionKey) - val configuration = currentBotConfiguration() heartbeatJob = this@TIMBotNetworkHandler.launch { while (socket.isOpen) { delay(configuration.heartbeatPeriodMillis) - with(session) { + with(bot) { class HeartbeatTimeoutException : CancellationException("heartbeat timeout") if (withTimeoutOrNull(configuration.heartbeatTimeoutMillis) { @@ -500,7 +473,7 @@ internal class TIMBotNetworkHandler internal constructor(coroutineContext: Corou } bot.logger.info("Successfully logged in") - loginResult.complete(LoginResult.SUCCESS) + loginResult.complete(null) this.close()//The LoginHandler is useless since then } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMProtocol.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMProtocol.kt similarity index 98% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMProtocol.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMProtocol.kt index 625d7afa5..4580e90dc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/TIMProtocol.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMProtocol.kt @@ -1,6 +1,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc +package net.mamoe.mirai.timpc.network import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.solveIpAddress diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/DataPacketSocketAdapter.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/DataPacketSocketAdapter.kt similarity index 67% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/DataPacketSocketAdapter.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/DataPacketSocketAdapter.kt index 2ecfb1ca9..206f3fea9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/DataPacketSocketAdapter.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/DataPacketSocketAdapter.kt @@ -1,14 +1,9 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.handler +package net.mamoe.mirai.timpc.network.handler import kotlinx.io.core.Closeable import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.events.ServerPacketReceivedEvent -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket -import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.PlatformDatagramChannel /** diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/TemporaryPacketHandler.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/TemporaryPacketHandler.kt new file mode 100644 index 000000000..edd2f4400 --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/handler/TemporaryPacketHandler.kt @@ -0,0 +1,37 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package net.mamoe.mirai.timpc.network.handler + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.withContext +import net.mamoe.mirai.network.data.Packet +import kotlin.coroutines.CoroutineContext +import kotlin.reflect.KClass + +internal class TemporaryPacketHandler

( + private val expectationClass: KClass

, + private val deferred: CompletableDeferred, + private val checkSequence: UShort? = null, + /** + * 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行 + */ + private val callerContext: CoroutineContext, + private val handler: suspend (P) -> R +) { + internal fun filter(packet: Packet, sequenceId: UShort): Boolean = + expectationClass.isInstance(packet) && if (checkSequence != null) sequenceId == checkSequence else true + + internal suspend inline fun doReceivePassingExceptionsToDeferred(packet: Packet) { + @Suppress("UNCHECKED_CAST") + val ret = try { + withContext(callerContext) { + handler(packet as P) + } + } catch (e: Throwable) { + deferred.completeExceptionally(e) + return + } + deferred.complete(ret) + } +} + diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Annotations.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Annotations.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Annotations.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Annotations.kt index 7fabe848c..196ee1155 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Annotations.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Annotations.kt @@ -1,6 +1,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused") -package net.mamoe.mirai.network.protocol.timpc.packet +package net.mamoe.mirai.timpc.network.packet /** * 包的最后一次修改时间, 和分析时使用的 TIM 版本 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Decrypters.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Decrypters.kt similarity index 75% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Decrypters.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Decrypters.kt index 8e2e39c6f..a330aaf75 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Decrypters.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Decrypters.kt @@ -1,8 +1,12 @@ -package net.mamoe.mirai.network.protocol.timpc.packet +package net.mamoe.mirai.timpc.network.packet +import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.IoBuffer +import kotlinx.io.core.writeFully import net.mamoe.mirai.utils.decryptBy +import net.mamoe.mirai.utils.encryptBy +import net.mamoe.mirai.utils.io.encryptAndWrite /** @@ -15,6 +19,7 @@ internal inline class SessionKey(override val value: ByteArray) : DecrypterByteA /** * [ByteArray] 解密器 */ +@PublishedApi internal interface DecrypterByteArray : Decrypter { val value: ByteArray override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(value) @@ -50,4 +55,8 @@ internal interface Decrypter { operator fun plus(another: Decrypter): Decrypter = LinkedDecrypter { another.decrypt(this.decrypt(it)) } } -internal interface DecrypterType \ No newline at end of file +internal interface DecrypterType + +@PublishedApi +internal inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) = + this.encryptAndWrite(key.value, encoder) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/OutgoingPacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/OutgoingPacket.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt index 4742196f9..8cb546594 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/OutgoingPacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/OutgoingPacket.kt @@ -1,13 +1,13 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "MemberVisibilityCanBePrivate") -package net.mamoe.mirai.network.protocol.timpc.packet +package net.mamoe.mirai.timpc.network.packet import kotlinx.io.core.* import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.utils.io.encryptAndWrite +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.writeQQ import kotlin.contracts.ExperimentalContracts @@ -39,13 +39,11 @@ internal abstract class SessionPacketFactory : PacketFactory.handlePacket(packet: TPacket) {} + open suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {} } /** * 构造一个待发送给服务器的数据包. - * - * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. */ @UseExperimental(ExperimentalContracts::class) @JvmOverloads @@ -76,13 +74,11 @@ internal inline fun PacketFactory<*, *>.buildOutgoingPacket( /** * 构造一个待发送给服务器的会话数据包. - * - * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. */ @UseExperimental(ExperimentalContracts::class) @JvmOverloads internal inline fun PacketFactory<*, *>.buildSessionPacket( - bot: UInt, + bot: Long, sessionKey: SessionKey, name: String? = null, id: PacketId = this.id, @@ -105,13 +101,11 @@ internal inline fun PacketFactory<*, *>.buildSessionPacket( /** * 构造一个待发送给服务器的会话数据包. - * - * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. */ @UseExperimental(ExperimentalContracts::class) @JvmOverloads internal fun PacketFactory<*, *>.buildSessionProtoPacket( - bot: UInt, + bot: Long, sessionKey: SessionKey, name: String? = null, id: PacketId = this.id, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Packet.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Packet.kt similarity index 84% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Packet.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Packet.kt index a1f2e4739..41833e612 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/Packet.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/Packet.kt @@ -1,14 +1,10 @@ -package net.mamoe.mirai.network.protocol.timpc.packet +package net.mamoe.mirai.timpc.network.packet import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.readBytes +import net.mamoe.mirai.network.data.Packet import net.mamoe.mirai.utils.io.toUHexString -/** - * 一个包的数据 (body) - */ -interface Packet - /** * 被忽略的数据包. */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketFactory.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketFactory.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt index 898ce1468..e18f36e9f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketFactory.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketFactory.kt @@ -1,6 +1,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet +package net.mamoe.mirai.timpc.network.packet import kotlinx.atomicfu.atomic import kotlinx.io.core.ByteReadPacket @@ -10,6 +10,7 @@ import kotlinx.io.pool.useInstance import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.data.Packet import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.read @@ -36,7 +37,7 @@ internal abstract class PacketFactory): TPacket + abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TPacket @Suppress("DEPRECATION") fun ByteReadPacket.decodeProtoPacket( @@ -69,7 +70,7 @@ internal abstract class PacketFactory() { - override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownPacket) { + override suspend fun BotNetworkHandler.handlePacket(packet: UnknownPacket) { ByteArrayPool.useInstance { packet.body.readAvailable(it) bot.logger.debug("UnknownPacket(${packet.id.value.toUHexString()}) = " + it.toUHexString()) @@ -80,7 +81,7 @@ internal object UnknownPacketFactory : SessionPacketFactory() { override suspend fun ByteReadPacket.decode( id: PacketId, sequenceId: UShort, - handler: BotNetworkHandler<*> + handler: BotNetworkHandler ): UnknownPacket { return UnknownPacket(id, this) } @@ -90,6 +91,6 @@ internal object IgnoredPacketFactory : SessionPacketFactory() { override suspend fun ByteReadPacket.decode( id: PacketId, sequenceId: UShort, - handler: BotNetworkHandler<*> + handler: BotNetworkHandler ): IgnoredPacket = IgnoredPacket(id) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketId.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketId.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketId.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketId.kt index b928a54a7..a40d0be92 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/PacketId.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/PacketId.kt @@ -1,11 +1,11 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet +package net.mamoe.mirai.timpc.network.packet -import net.mamoe.mirai.network.protocol.timpc.packet.action.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacketFactory -import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendOnlineStatusChangedPacket -import net.mamoe.mirai.network.protocol.timpc.packet.login.* +import net.mamoe.mirai.timpc.network.packet.action.* +import net.mamoe.mirai.timpc.network.packet.event.EventPacketFactory +import net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket +import net.mamoe.mirai.timpc.network.packet.login.* import net.mamoe.mirai.utils.io.toUHexString diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/AddContact.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/AddContact.kt similarity index 87% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/AddContact.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/AddContact.kt index 86562831e..bbfd9ee71 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/AddContact.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/AddContact.kt @@ -1,15 +1,16 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.core.* import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.network.data.PreviousNameList +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.withSession /** @@ -22,9 +23,9 @@ import net.mamoe.mirai.withSession @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") internal object QueryPreviousNamePacket : SessionPacketFactory() { operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey, - target: UInt + target: Long ): OutgoingPacket = buildSessionPacket(bot, sessionKey) { writeZero(2) writeQQ(bot) @@ -41,7 +42,7 @@ internal object QueryPreviousNamePacket : SessionPacketFactory // [00 00 00 10] 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B // [00 00 00 0F] E4 B8 B6 E6 9A 97 E8 A3 94 E5 89 91 E9 AD 94 - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): PreviousNameList { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): PreviousNameList { // 00 00 00 01 00 00 00 0F E8 87 AA E5 8A A8 E9 A9 BE E9 A9 B6 31 2E 33 val count = readUInt().toInt() @@ -54,19 +55,6 @@ internal object QueryPreviousNamePacket : SessionPacketFactory } } -/** - * 曾用名列表 - * - * 曾用名可能是: - * - 昵称 - * - 共同群内的群名片 - */ -class PreviousNameList( - list: List -) : Packet, List by list { - override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ") -} - // 需要验证消息 // 0065 发送 03 07 57 37 E8 // 0065 接受 03 07 57 37 E8 10 40 00 00 10 14 20 00 00 00 00 00 00 00 01 00 00 00 00 00 @@ -79,20 +67,21 @@ class PreviousNameList( @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") internal object CanAddFriendPacket : SessionPacketFactory() { operator fun invoke( - bot: UInt, - qq: UInt, + bot: Long, + qq: Long, sessionKey: SessionKey ): OutgoingPacket = buildSessionPacket(bot, sessionKey) { writeQQ(qq) } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CanAddFriendResponse = - handler.bot.withSession { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CanAddFriendResponse = + with(handler.bot) { if (remaining > 20) {//todo check - return CanAddFriendResponse.AlreadyAdded(readUInt().qq()) + return CanAddFriendResponse.AlreadyAdded(readQQ().qq()) } - val qq: QQ = readUInt().qq() + val qq: QQ = readQQ().qq() + readUByteLVByteArray() // debugDiscardExact(1) return when (val state = readUByte().toUInt()) { @@ -148,7 +137,7 @@ internal sealed class CanAddFriendResponse : EventPacket { 接受 03 00 00 00 00 01 30 5D 12 93 30 00 14 00 00 00 00 10 30 36 35 39 E4 B8 80 E7 BE 8E E5 A4 A9 E9 9D 99 02 0A 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 1E */ -inline class FriendAdditionKey(val value: IoBuffer) +internal inline class FriendAdditionKey(val value: IoBuffer) /** * 请求一个 32 位 Key, 在添加好友时发出 @@ -156,8 +145,8 @@ inline class FriendAdditionKey(val value: IoBuffer) @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") internal object RequestFriendAdditionKeyPacket : SessionPacketFactory() { operator fun invoke( - bot: UInt, - qq: UInt, + bot: Long, + qq: Long, sessionKey: SessionKey ) = buildSessionPacket(bot, sessionKey) { //01 00 01 02 B3 74 F6 @@ -165,7 +154,7 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory): Response { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response { //01 00 01 00 00 20 01 C2 76 47 98 38 A1 FF AB 64 04 A9 81 1F CC 2B 2B A6 29 FC 97 80 A6 90 2D 26 C8 37 EE 1D 8A FA discardExact(4) return Response(FriendAdditionKey(readIoBuffer(readUShort().toInt()))) @@ -183,8 +172,8 @@ internal object AddFriendPacket : SessionPacketFactory @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @Suppress("FunctionName") fun RequestAdd( - bot: UInt, - qq: UInt, + bot: Long, + qq: Long, sessionKey: SessionKey, /** * 验证消息 @@ -250,13 +239,13 @@ internal object AddFriendPacket : SessionPacketFactory @Suppress("FunctionName") @PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)") fun Approve( - bot: UInt, + bot: Long, sessionKey: SessionKey, /** * 好友列表分组的组的 ID. "我的好友" 为 0 */ friendListId: Short, - qq: UInt, + qq: Long, /** * 备注. 不设置则需要为 `null` TODO 需要确认是否还需发送一个设置备注包. 因为测试时若有备注则会多发一个包并且包里面有所设置的备注 */ @@ -284,7 +273,7 @@ internal object AddFriendPacket : SessionPacketFactory } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response { //02 02 B3 74 F6 00 //02 B3 74 F6 是QQ号 return Response } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendImage.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendImage.kt similarity index 86% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendImage.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendImage.kt index 3171b8e6a..d7155edb3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendImage.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendImage.kt @@ -1,53 +1,19 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.charsets.Charsets import kotlinx.io.core.* -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.message.ImageId -import net.mamoe.mirai.message.ImageId0x06 -import net.mamoe.mirai.message.requireLength +import net.mamoe.mirai.message.data.ImageId +import net.mamoe.mirai.message.data.ImageId0x06 +import net.mamoe.mirai.message.data.requireLength import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket -import net.mamoe.mirai.network.qqAccount -import net.mamoe.mirai.qqAccount +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.network.data.ImageLink +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.Http import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.withSession - - -/** - * 上传图片 - * 挂起直到上传完成或失败 - * - * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数 - * - * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 - */ -suspend fun QQ.uploadImage(image: ExternalImage): ImageId = bot.withSession { - FriendImagePacket.RequestImageId(qqAccount, sessionKey, id, image).sendAndExpect().let { - when (it) { - is FriendImageUKey -> { - Http.postImage( - htcmd = "0x6ff0070", - uin = bot.qqAccount, - groupId = null, - uKeyHex = it.uKey.toUHexString(""), - imageInput = image.input, - inputSize = image.inputSize - ) - it.imageId - } - is FriendImageAlreadyExists -> it.imageId - is FriendImageOverFileSizeMax -> throw OverFileSizeMaxException() - else -> error("This shouldn't happen") - } - } -} // region FriendImageResponse @@ -58,7 +24,7 @@ internal interface FriendImageResponse : EventPacket * 图片数据地址. */ // TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug -data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink { +internal data class FriendImageLink(override inline val original: String) : FriendImageResponse, ImageLink { override fun toString(): String = "FriendImageLink($original)" } @@ -96,9 +62,9 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse { internal object FriendImagePacket : SessionPacketFactory() { @Suppress("FunctionName") fun RequestImageId( - bot: UInt, + bot: Long, sessionKey: SessionKey, - target: UInt, + target: Long, image: ExternalImage ): OutgoingPacket = buildSessionPacket( bot, @@ -119,8 +85,8 @@ internal object FriendImagePacket : SessionPacketFactory() writeTV(0x08_01u) writeUVarIntLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) { - writeTUVarint(0x08u, bot) - writeTUVarint(0x10u, target) + writeTUVarint(0x08u, bot.toUInt()) + writeTUVarint(0x10u, target.toUInt()) writeTV(0x18_00u) writeTLV(0x22u, image.md5) writeTUVarint(0x28u, image.inputSize.toUInt()) @@ -150,7 +116,7 @@ internal object FriendImagePacket : SessionPacketFactory() @Suppress("FunctionName") fun RequestImageLink( - bot: UInt, + bot: Long, sessionKey: SessionKey, imageId: ImageId ): OutgoingPacket { @@ -207,8 +173,8 @@ internal object FriendImagePacket : SessionPacketFactory() writeUByte(0x1Au) writeUByte(0x47u) - writeTUVarint(0x08u, bot) - writeTUVarint(0x10u, bot) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事. + writeTUVarint(0x08u, bot.toUInt()) + writeTUVarint(0x10u, bot.toUInt()) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事. writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1)) writeHex("20 02 30 04 38 20 40 FF 01 50 00 6A 05 32 36 39 33 33 78 01") } @@ -217,7 +183,7 @@ internal object FriendImagePacket : SessionPacketFactory() override suspend fun ByteReadPacket.decode( id: PacketId, sequenceId: UShort, - handler: BotNetworkHandler<*> + handler: BotNetworkHandler ): FriendImageResponse { // 上传图片, 成功获取ID diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendList.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendList.kt similarity index 86% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendList.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendList.kt index 459c65d6b..2e335bf39 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/FriendList.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/FriendList.kt @@ -1,12 +1,12 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.Packet -import net.mamoe.mirai.network.protocol.timpc.packet.PacketId -import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.packet.PacketId +import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory // 0001 @@ -26,7 +26,7 @@ import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory internal inline class FriendListList(val delegate: List): Packet internal object QueryFriendListListPacket : SessionPacketFactory() { - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): 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. } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GradeInfo.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GradeInfo.kt similarity index 74% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GradeInfo.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GradeInfo.kt index 259a4666c..6329677af 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GradeInfo.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GradeInfo.kt @@ -1,14 +1,14 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.writeFully import kotlinx.io.core.writeUByte import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.utils.io.encryptAndWrite +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.writeQQ /** @@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ */ internal object RequestAccountInfoPacket : SessionPacketFactory() { operator fun invoke( - qq: UInt, + qq: Long, sessionKey: SessionKey ): OutgoingPacket = buildOutgoingPacket { writeQQ(qq) @@ -35,5 +35,5 @@ internal object RequestAccountInfoPacket : SessionPacketFactory): Response = Response + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response = Response } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupImage.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupImage.kt similarity index 81% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupImage.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupImage.kt index 3b8a001bd..3cb7a94b9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupImage.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupImage.kt @@ -1,62 +1,21 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "RUNTIME_ANNOTATION_NOT_SUPPORTED") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action -import kotlinx.coroutines.withContext import kotlinx.io.core.ByteReadPacket import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupInternalId -import net.mamoe.mirai.contact.withSession -import net.mamoe.mirai.message.ImageId -import net.mamoe.mirai.message.ImageId0x03 -import net.mamoe.mirai.message.requireLength +import net.mamoe.mirai.message.data.ImageId0x03 +import net.mamoe.mirai.message.data.requireLength import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket -import net.mamoe.mirai.qqAccount +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.network.data.ImageLink +import net.mamoe.mirai.timpc.network.packet.* +import net.mamoe.mirai.timpc.utils.assertUnreachable import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.Http -import net.mamoe.mirai.utils.assertUnreachable import net.mamoe.mirai.utils.io.toUHexString - -/** - * 上传群图片 - * 挂起直到上传完成或失败 - * - * 在 JVM 下, `SendImageUtilsJvm.kt` 内有多个捷径函数 - * - * @throws OverFileSizeMaxException 如果文件过大, 服务器拒绝接收时 - */ -suspend fun Group.uploadImage(image: ExternalImage): ImageId = withSession { - val userContext = coroutineContext - val response = GroupImagePacket.RequestImageId(bot.qqAccount, internalId, image, sessionKey).sendAndExpect() - - withContext(userContext) { - when (response) { - is ImageUploadInfo -> response.uKey?.let { - Http.postImage( - htcmd = "0x6ff0071", - uin = bot.qqAccount, - groupId = GroupId(id), - imageInput = image.input, - inputSize = image.inputSize, - uKeyHex = it.toUHexString("") - ) - } - - // TODO: 2019/11/17 超过大小的情况 - //is Overfile -> throw OverFileSizeMaxException() - else -> assertUnreachable() - } - } - - return image.groupImageId -} - internal interface GroupImageResponse : EventPacket // endregion @@ -193,7 +152,7 @@ internal object GroupImagePacket : SessionPacketFactory() { @Suppress("FunctionName") fun RequestImageId( - bot: UInt, + bot: Long, groupInternalId: GroupInternalId, image: ExternalImage, sessionKey: SessionKey @@ -215,7 +174,7 @@ internal object GroupImagePacket : SessionPacketFactory() { private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u) @Suppress("FunctionName") fun RequestImageLink( - bot: UInt, + bot: Long, sessionKey: SessionKey, imageId: ImageId0x03 ): OutgoingPacket { @@ -242,7 +201,7 @@ internal object GroupImagePacket : SessionPacketFactory() { ) } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): GroupImageResponse { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): GroupImageResponse { @Serializable data class GroupImageResponseProto( diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupPacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupPacket.kt similarity index 83% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupPacket.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupPacket.kt index b65861c4b..df1ea70c7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/GroupPacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/GroupPacket.kt @@ -1,88 +1,34 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.core.* -import net.mamoe.mirai.contact.* -import net.mamoe.mirai.contact.internal.Member -import net.mamoe.mirai.message.MessageChain +import net.mamoe.mirai.contact.GroupInternalId +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.groupInternalId +import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.internal.toPacket import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.utils.LockFreeLinkedList -import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.internal.RawGroupInfo +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* +import net.mamoe.mirai.timpc.utils.unsupportedFlag +import net.mamoe.mirai.timpc.utils.unsupportedType import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.withSession +import kotlin.collections.mutableMapOf +import kotlin.collections.set -/** - * 群资料 - */ -@Suppress("MemberVisibilityCanBePrivate") // 将来使用 -class GroupInfo( - internal var _group: Group, - internal var _owner: Member, - internal var _name: String, - internal var _announcement: String, - internal var _members: ContactList -) { - val group: Group get() = _group - val owner: Member get() = _owner - val name: String get() = _name - val announcement: String get() = _announcement - val members: ContactList get() = _members - - override fun toString(): String = - "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, - val name: String, - val announcement: String, - /** - * 含群主 - */ - val members: Map -) : GroupPacket.InfoResponse { - - @UseExperimental(MiraiInternalAPI::class) - fun parseBy(group: Group): GroupInfo = group.bot.withSession { - val memberList = LockFreeLinkedList() - members.forEach { entry: Map.Entry -> - memberList.addLast(entry.key.qq().let { group.Member(it, entry.value, it.coroutineContext) }) - } - return GroupInfo( - group, - this@RawGroupInfo.owner.qq().let { group.Member(it, MemberPermission.OWNER, it.coroutineContext) }, - this@RawGroupInfo.name, - this@RawGroupInfo.announcement, - ContactList(memberList) - ) - } -} - -/** - * 退出群的返回 - */ -inline class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, GroupPacket.GroupPacketResponse { - val group: GroupInternalId get() = _group ?: error("request failed") - val isSuccess: Boolean get() = _group != null - - override fun toString(): String = "GroupPacket.QuitResponse" -} - @Suppress("FunctionName") internal object GroupPacket : SessionPacketFactory() { @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") fun Message( - bot: UInt, + bot: Long, groupInternalId: GroupInternalId, sessionKey: SessionKey, message: MessageChain @@ -109,7 +55,7 @@ internal object GroupPacket : SessionPacketFactory + handler: BotNetworkHandler ): GroupPacketResponse { return when (val packetType = readUByte().toUInt()) { 0x2Au -> MessageResponse @@ -181,7 +137,7 @@ internal object GroupPacket : SessionPacketFactory { if (readByte().toInt() == 0) { - QuitGroupResponse(readUInt().groupInternalId()) + QuitGroupResponse(readUInt().toLong().groupInternalId()) } else { QuitGroupResponse(null) } @@ -218,10 +174,10 @@ internal object GroupPacket : SessionPacketFactory() + val members = mutableMapOf() do { - val qq = readUInt() + val qq = readUInt().toLong() val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态 if (qq == owner) { continue diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/HttpAPIAccessor.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/HttpAPIAccessor.kt similarity index 92% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/HttpAPIAccessor.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/HttpAPIAccessor.kt index d96021142..29052322f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/HttpAPIAccessor.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/HttpAPIAccessor.kt @@ -1,6 +1,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import io.ktor.client.HttpClient import io.ktor.client.request.post @@ -17,7 +17,7 @@ import net.mamoe.mirai.contact.GroupId @Suppress("SpellCheckingInspection") internal suspend inline fun HttpClient.postImage( htcmd: String, - uin: UInt, + uin: Long, groupId: GroupId?, imageInput: Input, inputSize: Long, @@ -30,7 +30,7 @@ internal suspend inline fun HttpClient.postImage( path("cgi-bin/httpconn") parameters["htcmd"] = htcmd - parameters["uin"] = uin.toLong().toString() + parameters["uin"] = uin.toString() if (groupId != null) parameters["groupcode"] = groupId.value.toLong().toString() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Image.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Image.kt similarity index 82% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Image.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Image.kt index f45aa516e..a39f94586 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Image.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Image.kt @@ -1,27 +1,7 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "NO_REFLECTION_IN_CLASS_PATH") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action -import io.ktor.client.request.get -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.readBytes -import net.mamoe.mirai.utils.Http - -/** - * 图片文件过大 - */ -class OverFileSizeMaxException : IllegalStateException() - -interface ImageLink { - /** - * 原图 - */ - val original: String - - suspend fun downloadAsByteArray(): ByteArray = download().readBytes() - - suspend fun download(): ByteReadPacket = Http.get(original) -} /* /** @@ -32,8 +12,8 @@ interface ImageLink { @PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)") object SubmitImageFilenamePacket : PacketFactory { operator fun invoke( - bot: UInt, - target: UInt, + bot: Long, + target: Long, filename: String, sessionKey: SessionKey ): OutgoingPacket = buildOutgoingPacket { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Profile.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Profile.kt similarity index 96% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Profile.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Profile.kt index 8d4ff7f95..27d133dd2 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/Profile.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/Profile.kt @@ -1,13 +1,14 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import io.ktor.util.date.GMTDate import kotlinx.io.core.* -import net.mamoe.mirai.contact.data.Gender -import net.mamoe.mirai.contact.data.Profile +import net.mamoe.mirai.network.data.Gender +import net.mamoe.mirai.network.data.Profile import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* inline class AvatarLink(val value: String) : Packet @@ -23,13 +24,13 @@ internal object QueryNicknamePacket : SessionPacketFactory() { */ @PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)") operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey, - qq: UInt + qq: Long ): OutgoingPacket = buildSessionPacket(bot, sessionKey) { writeHex("03 00 00 00 00 00 00 00 00 00 00") writeByte(1) - writeUInt(qq) + writeQQ(qq) } /** @@ -38,7 +39,7 @@ internal object QueryNicknamePacket : SessionPacketFactory() { */ @PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)") operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey, qq: Array ): OutgoingPacket = buildSessionPacket(bot, sessionKey) { @@ -109,7 +110,7 @@ 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 04 5D EC AF 48 */ - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): NicknameMap { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): NicknameMap { //03 00 00 00 00 00 00 00 00 00 00 12 04 14 37 val type = readUByte().toInt() if (type == 15) { @@ -137,16 +138,16 @@ 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 internal object RequestProfileAvatarPacket : SessionPacketFactory() { //00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 operator fun invoke( - bot: UInt, - qq: UInt, + bot: Long, + qq: Long, sessionKey: SessionKey ): OutgoingPacket = buildSessionPacket(bot, sessionKey) { writeUShort(0x01u) - writeUInt(qq) + writeQQ(qq) writeHex("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") } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): AvatarLink { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): AvatarLink { println(" RequestProfileAvatarPacket body=${this.readBytes().toUHexString()}") TODO() } @@ -163,19 +164,19 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory): RequestProfileDetailsResponse { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): RequestProfileDetailsResponse { discardExact(3) - val qq = readUInt() + val qq = readUInt().toLong() discardExact(6) val map = readTLVMap(tagSize = 2, expectingEOF = true) //map.printTLVMap("Profile(qq=$qq) raw=") @@ -193,7 +194,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory Gender.FEMALE 0x01u -> Gender.MALE else -> Gender.SECRET // 猜的 - //else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toUHexString()}") + //else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toHexString()}") }, birthday = map[0x4E3Fu]?.let { GMTDate(it.toUInt().toLong()) }, personalStatement = map[0x4E33u]?.encodeToString(), @@ -209,7 +210,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory() { /** * 查询好友的备注 */ @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)") operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey, - target: UInt + target: Long ): OutgoingPacket = buildSessionPacket( bot, sessionKey ) { @@ -32,7 +28,7 @@ internal object QueryFriendRemarkPacket : SessionPacketFactory writeZero(1) } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendNameRemark { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendNameRemark { //0D 00 5D DA 3D 0F 59 17 3E 05 00 00 06 E6 9F 90 E4 B9 90 00 00 00 00 00 00 discardExact(11) return FriendNameRemark(readUShortLVString()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/RequestFriendListPacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/RequestFriendListPacket.kt similarity index 63% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/RequestFriendListPacket.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/RequestFriendListPacket.kt index b97cf0d3f..48cb56a61 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/RequestFriendListPacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/RequestFriendListPacket.kt @@ -1,19 +1,20 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.writeZero class FriendList : Packet -@PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)") internal object RequestFriendListPacket : SessionPacketFactory() { + @PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)") operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey ): OutgoingPacket = buildSessionPacket( bot, sessionKey, version = TIMProtocol.version0x02 @@ -22,7 +23,7 @@ internal object RequestFriendListPacket : SessionPacketFactory() { writeZero(4) } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendList { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendList { TODO() } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/SendFriendMessagePacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/SendFriendMessagePacket.kt similarity index 80% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/SendFriendMessagePacket.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/SendFriendMessagePacket.kt index b306f4513..98a3366f7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/action/SendFriendMessagePacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/action/SendFriendMessagePacket.kt @@ -1,21 +1,22 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.action +package net.mamoe.mirai.timpc.network.packet.action import kotlinx.io.core.* -import net.mamoe.mirai.message.MessageChain +import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.internal.toPacket import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.md5 @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") internal object SendFriendMessagePacket : SessionPacketFactory() { operator fun invoke( - botQQ: UInt, - targetQQ: UInt, + botQQ: Long, + targetQQ: Long, sessionKey: SessionKey, message: MessageChain ): OutgoingPacket = buildSessionPacket(botQQ, sessionKey) { @@ -25,14 +26,11 @@ internal object SendFriendMessagePacket : SessionPacketFactory): Response = Response + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response = Response } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/AndroidOnlineStatusChange.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/AndroidOnlineStatusChange.kt similarity index 84% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/AndroidOnlineStatusChange.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/AndroidOnlineStatusChange.kt index a0623f57c..069ee8dc9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/AndroidOnlineStatusChange.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/AndroidOnlineStatusChange.kt @@ -1,12 +1,12 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.protocol.timpc.packet.Packet -import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.packet.PacketVersion import net.mamoe.mirai.utils.io.readBoolean diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/ConnectionOccupiedEvent.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/ConnectionOccupiedEvent.kt similarity index 67% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/ConnectionOccupiedEvent.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/ConnectionOccupiedEvent.kt index ba2b4c6f6..926300147 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/ConnectionOccupiedEvent.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/ConnectionOccupiedEvent.kt @@ -1,20 +1,14 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.ConnectionOccupiedEvent import net.mamoe.mirai.utils.io.encodeToString -/** - * 被挤下线. 只能获取到中文的消息 - */ -inline class ConnectionOccupiedEvent(val message: String) : EventPacket { - override fun toString(): String = "ConnectionOccupiedEvent(${message.replace("\n", "")})" -} - internal object ConnectionOccupiedPacketHandler : KnownEventParserAndHandler(0x0030u) { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ConnectionOccupiedEvent { discardExact(6) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacketFactory.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/EventPacketFactory.kt similarity index 81% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacketFactory.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/EventPacketFactory.kt index 255c845c0..c95d6fd68 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacketFactory.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/EventPacketFactory.kt @@ -1,13 +1,13 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.* import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.sessionKey +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.qqAccount import net.mamoe.mirai.utils.io.readIoBuffer @@ -15,16 +15,16 @@ import net.mamoe.mirai.utils.io.readIoBuffer * 事件的识别 ID. 在 ACK 时使用 */ internal class EventPacketIdentity( - val from: UInt,//对于好友消息, 这个是发送人 - val to: UInt,//对于好友消息, 这个是bot + val from: Long,//对于好友消息, 这个是发送人 + val to: Long,//对于好友消息, 这个是bot internal val uniqueId: IoBuffer//8 ) { override fun toString(): String = "($from->$to)" } internal fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) { - writeUInt(from) - writeUInt(to) + writeUInt(from.toUInt()) + writeUInt(to.toUInt()) writeFully(uniqueId) } @@ -39,20 +39,16 @@ internal fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> = @NoLog @Suppress("FunctionName") internal object EventPacketFactory : PacketFactory(SessionKey) { - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Packet { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Packet { val eventIdentity = EventPacketIdentity( - from = readUInt(), - to = readUInt(), + from = readUInt().toLong(), // clear semantic, don't readQQ() or readGroup() + to = readUInt().toLong(), // clear semantic uniqueId = readIoBuffer(8) ) (handler as TIMBotNetworkHandler).socket.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity)) discardExact(2) // 1F 40 return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also { - if (it is MessagePacket<*, *>) { - it.botVar = handler.bot - } - if (it is EventParserAndHandler<*>) { @Suppress("UNCHECKED_CAST") with(it as EventParserAndHandler) { @@ -68,7 +64,7 @@ internal object EventPacketFactory : PacketFactory(SessionKe operator fun invoke( id: PacketId, sequenceId: UShort, - bot: UInt, + bot: Long, sessionKey: SessionKey, identity: EventPacketIdentity ): OutgoingPacket = buildSessionPacket(name = "EventPacket", id = id, sequenceId = sequenceId, bot = bot, sessionKey = sessionKey) { @@ -84,7 +80,7 @@ internal interface EventParserAndHandler { /** * 在 [BotNetworkHandler] 下处理这个包. 广播事件等. */ - suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {} + suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {} } internal abstract class KnownEventParserAndHandler(override val id: UShort) : EventParserAndHandler { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendAddRequestEventPacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendAddRequestEventPacket.kt similarity index 82% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendAddRequestEventPacket.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendAddRequestEventPacket.kt index e6fb9ef46..36e459843 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendAddRequestEventPacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendAddRequestEventPacket.kt @@ -1,44 +1,19 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact -import kotlinx.io.core.readUInt import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion -import net.mamoe.mirai.network.protocol.timpc.packet.action.AddFriendPacket -import net.mamoe.mirai.network.qqAccount +import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent +import net.mamoe.mirai.timpc.network.packet.PacketVersion +import net.mamoe.mirai.utils.io.readQQ import net.mamoe.mirai.utils.io.readUShortLVString -import net.mamoe.mirai.withSession -import kotlin.jvm.JvmOverloads -/** - * 陌生人请求添加机器人账号为好友 - */ -data class ReceiveFriendAddRequestEvent( - val qq: QQ, - /** - * 验证消息 - */ - val message: String -) : EventPacket { - /** - * 同意这个请求 - * - * @param remark 备注名, 不设置则需为 `null` - */ - @JvmOverloads - suspend fun approve(remark: String? = null): Unit = qq.bot.withSession { - AddFriendPacket.Approve(qqAccount, sessionKey, 0, qq.id, remark).sendAndExpect() - } -} - @PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)") internal object FriendAddRequestEventPacket : KnownEventParserAndHandler(0x02DFu) { - override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ReceiveFriendAddRequestEvent = bot.withSession { + override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ReceiveFriendAddRequestEvent = with(bot) { // 00 00 00 08 00 0A 00 04 01 00 // 00 00 00 01 // 76 E4 B8 DD @@ -88,7 +63,7 @@ Mirai 20:35:23 : Packet received: UnknownEventPacket(id=00 BB, identity=(7610254 discardExact(10 + 4) // 00 00 00 08 00 0A 00 04 01 00 00 00 00 01 discardExact(4) // bot account uint discardExact(4) // 00 00 00 01 - val qq = readUInt().qq() + val qq = readQQ().qq() discardExact(4) // bot account uint discardExact(3) // 02 00 00 恒定 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendConversationIniliaze.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendConversationIniliaze.kt similarity index 70% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendConversationIniliaze.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendConversationIniliaze.kt index 959e33234..4c954af93 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendConversationIniliaze.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendConversationIniliaze.kt @@ -1,23 +1,25 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readUInt import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.timpc.network.packet.PacketVersion +import net.mamoe.mirai.utils.io.readQQ data class FriendConversationInitialize( - val qq: UInt + val qq: Long ) : EventPacket @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") internal object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler(0x0079u) { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize { discardExact(4)// 00 00 00 00 - return FriendConversationInitialize(readUInt()) + return FriendConversationInitialize(readQQ()) } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendOnlineStatusChanged.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendOnlineStatusChanged.kt similarity index 68% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendOnlineStatusChanged.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendOnlineStatusChanged.kt index 6254acbaa..757e5f920 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/FriendOnlineStatusChanged.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/FriendOnlineStatusChanged.kt @@ -1,30 +1,26 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket 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.event.events.FriendStatusChanged import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId -import net.mamoe.mirai.network.protocol.timpc.packet.PacketId -import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory +import net.mamoe.mirai.timpc.network.packet.KnownPacketId +import net.mamoe.mirai.timpc.network.packet.PacketId +import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory import net.mamoe.mirai.utils.OnlineStatus - -data class FriendStatusChanged( - val qq: QQ, - val status: OnlineStatus -) : EventPacket +import net.mamoe.mirai.utils.io.readQQ /** * 好友在线状态改变 */ internal object FriendOnlineStatusChangedPacket : SessionPacketFactory() { - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged { - val qq = readUInt() + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendStatusChanged { + val qq = readQQ() discardExact(8) val statusId = readUByte() val status = OnlineStatus(statusId) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/GroupFileUpload.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/GroupFileUpload.kt similarity index 83% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/GroupFileUpload.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/GroupFileUpload.kt index 3a674a0e0..27fd9c472 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/GroupFileUpload.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/GroupFileUpload.kt @@ -1,10 +1,11 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.timpc.network.packet.PacketVersion import net.mamoe.mirai.utils.io.debugPrint diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Ignored.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Ignored.kt similarity index 89% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Ignored.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Ignored.kt index aabc65394..4f334d8e9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Ignored.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Ignored.kt @@ -1,9 +1,10 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.Bot +import net.mamoe.mirai.network.data.EventPacket import net.mamoe.mirai.utils.io.toUHexString internal inline class IgnoredEventPacket(val id: UShort) : EventPacket { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberJoin.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberJoin.kt similarity index 84% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberJoin.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberJoin.kt index 185a102e3..171a93ff8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberJoin.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberJoin.kt @@ -1,21 +1,19 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket -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.contact.MemberPermission -import net.mamoe.mirai.contact.internal.Member -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.network.BotNetworkHandler +import net.mamoe.mirai.timpc.TIMPCBot import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.discardExact +import net.mamoe.mirai.utils.io.readQQ /** * 成员加入前的事件. 群的成员列表中还没有这个人 @@ -71,20 +69,23 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler.handlePacket(packet: MemberJoinEventPacket) { + override suspend fun BotNetworkHandler.handlePacket(packet: MemberJoinEventPacket) { PreMemberJoinEvent(packet).broadcast() packet.broadcast() PostMemberJoinEvent(packet).broadcast() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberKickEvent.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberKickEvent.kt similarity index 84% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberKickEvent.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberKickEvent.kt index c85767597..be1bcb1f7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberKickEvent.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberKickEvent.kt @@ -1,18 +1,15 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.readUByte -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.network.data.EventPacket import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.utils.io.discardExact -import net.mamoe.mirai.utils.io.toUHexString -import net.mamoe.mirai.utils.io.unsupported +import net.mamoe.mirai.utils.io.* /** * 群成员列表变动事件. @@ -53,21 +50,21 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler MemberQuitEvent(member, _operator = null) - 0x03 -> MemberQuitEvent(member, _operator = group.getMember(readUInt())) - else -> unsupported("Unsupported type " + type.toUHexString()) + 0x03 -> MemberQuitEvent(member, _operator = group.getMember(readQQ())) + else -> error("Unsupported type " + type.toUHexString()) } // 某群员主动离开, 群号 853343432 @@ -94,5 +91,4 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler(0x02DCu) { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute { @@ -133,12 +66,12 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl 0x11u -> debugPrintIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回?? discardExact(15) discardExact(2) - val group = bot.getGroup(readUInt()) + val group = bot.getGroup(readQQ()) discardExact(2) - val operator = group.getMember(readUInt()) + val operator = group.getMember(readQQ()) discardExact(4) //time discardExact(2) - val memberQQ = readUInt() + val memberQQ = readQQ() val durationSeconds = readUInt().toInt() if (durationSeconds == 0) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberPermission.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberPermission.kt similarity index 83% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberPermission.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberPermission.kt index 76ed112cc..d52ab24bd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MemberPermission.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MemberPermission.kt @@ -1,18 +1,18 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact -import kotlinx.io.core.readUInt import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.protocol.timpc.packet.Packet -import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.packet.PacketVersion +import net.mamoe.mirai.utils.io.readQQ data class MemberPermissionChangePacket( - val groupId: UInt, - val qq: UInt, + val groupId: Long, + val qq: Long, val kind: Kind ) : Packet { enum class Kind { @@ -35,7 +35,7 @@ internal object GroupMemberPermissionChangedEventFactory : KnownEventParserAndHa // 取消管理员 // 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00 discardExact(remaining - 5) - val qq = readUInt() + val qq = readQQ() val kind = when (readByte().toInt()) { 0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR 0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MessageEvent.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MessageEvent.kt new file mode 100644 index 000000000..26c373433 --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/MessageEvent.kt @@ -0,0 +1,103 @@ +package net.mamoe.mirai.timpc.network.packet.event + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.discardExact +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.message.GroupMessage +import net.mamoe.mirai.message.internal.readMessageChain +import net.mamoe.mirai.message.FriendMessage +import net.mamoe.mirai.timpc.network.packet.PacketVersion +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.io.* + + +@UseExperimental(ExperimentalUnsignedTypes::class) +internal object GroupMessageEventParserAndHandler : KnownEventParserAndHandler(0x0052u) { + + @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") + override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupMessage { + discardExact(31) + val groupNumber = readGroup() + discardExact(1) + val qq = readQQ() + + discardExact(48) + readUShortLVByteArray() + discardExact(2)//2个0x00 + + //debugPrintIfFail { + val message = readMessageChain() + + var senderPermission: MemberPermission = MemberPermission.MEMBER + var senderName = "" + val map = readTLVMap(true) + if (map.containsKey(18u)) { + map.getValue(18u).read { + val tlv = readTLVMap(true) + senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) { + null -> MemberPermission.MEMBER + 0x08u -> MemberPermission.OWNER + 0x10u -> MemberPermission.ADMINISTRATOR + else -> { + tlv.printTLVMap("TLV(tag=18) Map") + MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used") + MemberPermission.MEMBER + } + } + + senderName = when { + tlv.containsKey(0x01u) -> kotlinx.io.core.String(tlv.getValue(0x01u))//这个人的qq昵称 + tlv.containsKey(0x02u) -> kotlinx.io.core.String(tlv.getValue(0x02u))//这个人的群名片 + else -> { + tlv.printTLVMap("TLV(tag=18) Map") + MiraiLogger.warning("Could not determine senderName") + "null" + } + } + } + } + + val group = bot.getGroup(groupNumber) + return GroupMessage( + bot = bot, + group = group, + senderName = senderName, + permission = senderPermission, + sender = group.getMember(qq), + message = message + ) + } +} + +// endregion + +// region friend message + + +@Suppress("unused") +@UseExperimental(ExperimentalUnsignedTypes::class) +internal object FriendMessageEventParserAndHandler : KnownEventParserAndHandler(0x00A6u) { + + @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") + override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendMessage { + discardExact(2) + val l1 = readShort() + discardExact(1)//0x00 + val previous = readByte().toInt() == 0x08 + discardExact(l1.toInt() - 2) + //java.io.EOFException: Only 49 bytes were discarded of 69 requested + //抖动窗口消息 + discardExact(69) + readUShortLVByteArray()//font + discardExact(2)//2个0x00 + val message = readMessageChain() + return FriendMessage( + bot = bot, + previous = previous, + sender = bot.getQQ(identity.from), + message = message + ) + } +} +// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Unknown.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Unknown.kt similarity index 85% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Unknown.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Unknown.kt index 3284f2fbe..89caf36ed 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/Unknown.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/event/Unknown.kt @@ -1,12 +1,13 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.timpc.network.packet.event import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.readBytes import kotlinx.io.pool.useInstance import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.data.EventPacket import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.toUHexString @@ -28,11 +29,11 @@ Mirai 21:54:15 : Packet received: UnknownEventPacket(id=00 57, identity=(9205034 internal class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): UnknownEventPacket { - // MiraiLogger.debug("UnknownEventPacket(${id.toUHexString()}) = ${readBytes().toUHexString()}") + // MiraiLogger.debug("UnknownEventPacket(${id.toHexString()}) = ${readBytes().toHexString()}") return UnknownEventPacket(id, identity, this) //TODO the cause is that `this` reference. } - override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownEventPacket) { + override suspend fun BotNetworkHandler.handlePacket(packet: UnknownEventPacket) { ByteArrayPool.useInstance { packet.body.readAvailable(it) bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Captcha.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Captcha.kt similarity index 97% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Captcha.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Captcha.kt index c8c423ce1..f42dd56fb 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Captcha.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Captcha.kt @@ -1,11 +1,12 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "FunctionName") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.* import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* internal object CaptchaKey : DecrypterByteArray, DecrypterType { @@ -17,7 +18,7 @@ internal object CaptchaPacket : PacketFactory): CaptchaResponse = + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CaptchaResponse = when (val flag = readByte().toUInt()) { 0x14u -> {//00 05 00 00 00 00 00 00 38 CaptchaResponse.Correct().apply { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/ChangeOnlineStatusPacket.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/ChangeOnlineStatusPacket.kt similarity index 74% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/ChangeOnlineStatusPacket.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/ChangeOnlineStatusPacket.kt index b7f957f48..0771e148e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/ChangeOnlineStatusPacket.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/ChangeOnlineStatusPacket.kt @@ -1,15 +1,15 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.writeFully import kotlinx.io.core.writeUByte import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.OnlineStatus -import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeQQ @@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ */ internal object ChangeOnlineStatusPacket : PacketFactory(NoDecrypter) { operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey, loginStatus: OnlineStatus ): OutgoingPacket = buildOutgoingPacket { @@ -31,10 +31,10 @@ internal object ChangeOnlineStatusPacket : PacketFactory): ChangeOnlineStatusResponse = + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): ChangeOnlineStatusResponse = ChangeOnlineStatusResponse } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Heartbeat.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Heartbeat.kt similarity index 72% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Heartbeat.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Heartbeat.kt index 06f00c7f4..c4d1c26f6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Heartbeat.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Heartbeat.kt @@ -1,21 +1,21 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.writeFully import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.utils.io.encryptAndWrite +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeQQ @NoLog internal object HeartbeatPacket : SessionPacketFactory() { operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey ): OutgoingPacket = buildOutgoingPacket { writeQQ(bot) @@ -25,7 +25,7 @@ internal object HeartbeatPacket : SessionPacketFactory( } } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): HeartbeatPacketResponse = + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): HeartbeatPacketResponse = HeartbeatPacketResponse } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/PasswordSubmission.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/PasswordSubmission.kt similarity index 95% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/PasswordSubmission.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/PasswordSubmission.kt index 33fcf60ea..f6affcbc4 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/PasswordSubmission.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/PasswordSubmission.kt @@ -1,17 +1,16 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.* -import net.mamoe.mirai.contact.data.Gender +import net.mamoe.mirai.network.data.Gender import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.decryptBy -import net.mamoe.mirai.utils.encryptBy +import net.mamoe.mirai.network.data.LoginResult +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* +import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.writeCRC32 internal object ShareKey : DecrypterByteArray, DecrypterType { override val value: ByteArray = TIMProtocol.shareKey @@ -46,7 +45,7 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr */ internal object SubmitPasswordPacket : PacketFactory(SubmitPasswordResponseDecrypter) { operator fun invoke( - bot: UInt, + bot: Long, password: String, loginTime: Int, loginIP: String, @@ -109,7 +108,7 @@ internal object SubmitPasswordPacket : PacketFactory): LoginResponse { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): LoginResponse { val size = remaining.toInt() return when { size == 229 || size == 271 || size == 207 || size == 165 /* TODO CHECK 165 */ -> { @@ -267,7 +266,7 @@ internal inline class SessionResponseDecryptionKey(private val delegate: IoBuffe } private fun BytePacketBuilder.writePart1( - qq: UInt, + qq: Long, password: String, loginTime: Int, loginIP: String, @@ -296,7 +295,7 @@ private fun BytePacketBuilder.writePart1( this.writeFully(TIMProtocol.passwordSubmissionTLV2) this.writeHex("00 1A")//tag this.writeHex("00 40")//length - this.writeFully(TIMProtocol.passwordSubmissionTLV2.encryptBy(privateKey)) + this.writeFully(TIMProtocol.passwordSubmissionTLV2.encryptBy(privateKey.value)) this.writeFully(TIMProtocol.constantData1) this.writeFully(TIMProtocol.constantData2) this.writeQQ(qq) @@ -310,6 +309,29 @@ private fun BytePacketBuilder.writePart1( this.writeHex("60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6")//key } +private fun BytePacketBuilder.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) { + val firstMD5 = md5(password) + val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray()) + + this.encryptAndWrite(secondMD5) { + writeRandom(4) + writeHex("00 02") + writeQQ(qq) + writeFully(TIMProtocol.constantData2) + writeHex("00 00 01") + + writeFully(firstMD5) + writeInt(loginTime) + writeByte(0) + writeZero(4 * 3) + writeIP(loginIP) + writeZero(8) + writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾 + writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16 + writeFully(privateKey.value) + } +} + private fun BytePacketBuilder.writePart2() { this.writeHex("03 12")//tag diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/SKey.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/SKey.kt similarity index 73% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/SKey.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/SKey.kt index 01881a7de..176851afa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/SKey.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/SKey.kt @@ -1,19 +1,15 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "FunctionName") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.writeFully import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.qqAccount +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.withSession - -internal fun BotSession.RequestSKeyPacket(): OutgoingPacket = RequestSKeyPacket(qqAccount, sessionKey) internal inline class SKey( val value: String @@ -25,7 +21,7 @@ internal inline class SKey( */ internal object RequestSKeyPacket : SessionPacketFactory() { operator fun invoke( - bot: UInt, + bot: Long, sessionKey: SessionKey ): OutgoingPacket = buildOutgoingPacket { writeQQ(bot) @@ -35,7 +31,7 @@ internal object RequestSKeyPacket : SessionPacketFactory() { } } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SKey { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): SKey { //11 00 97 D7 0F 1C FD 50 7A 41 DD 4D 66 93 EF 8C 85 D1 84 3D 66 95 9D E5 B4 96 A5 E3 92 37 28 D8 80 DA EF 8C 85 D1 84 3D 66 95 9D E5 B4 96 A5 E3 92 37 28 D8 80 DA discardExact(4) @@ -44,9 +40,9 @@ internal object RequestSKeyPacket : SessionPacketFactory() { } } - override suspend fun BotNetworkHandler<*>.handlePacket(packet: SKey) = bot.withSession { - _sKey = packet.value - _cookies = "uin=o$qqAccount;skey=$sKey;" + override suspend fun BotNetworkHandler.handlePacket(packet: SKey) { + // _sKey = packet.value + // _cookies = "uin=o$qqAccount;skey=$sKey;" // TODO: 2019/11/27 SKEY 实现 /* diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Session.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Session.kt similarity index 95% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Session.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Session.kt index 425f42142..010ea95c1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Session.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Session.kt @@ -1,17 +1,18 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.* import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.localIpAddress internal object RequestSessionPacket : PacketFactory(SessionResponseDecryptionKey) { operator fun invoke( - bot: UInt, + bot: Long, serverIp: String, token38: IoBuffer, token88: IoBuffer, @@ -65,7 +66,7 @@ internal object RequestSessionPacket : PacketFactory): SessionKeyResponse { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): SessionKeyResponse { when (remaining) { 407L -> { discardExact(25)//todo test diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Touch.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Touch.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Touch.kt rename to mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Touch.kt index e80c0fc23..42c79bed9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/Touch.kt +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/packet/login/Touch.kt @@ -1,14 +1,15 @@ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.network.protocol.timpc.packet.login +package net.mamoe.mirai.timpc.network.packet.login import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import kotlinx.io.core.writeFully import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.* +import net.mamoe.mirai.network.data.Packet +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* import net.mamoe.mirai.utils.io.* internal object TouchKey : DecrypterByteArray, DecrypterType { @@ -22,7 +23,7 @@ internal object TouchKey : DecrypterByteArray, DecrypterType { */ internal object TouchPacket : PacketFactory(TouchKey) { operator fun invoke( - bot: UInt, + bot: Long, serverIp: String, isRedirect: Boolean ): OutgoingPacket = buildOutgoingPacket { @@ -60,7 +61,7 @@ internal object TouchPacket : PacketFactory } } - override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TouchResponse { + override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TouchResponse { when (val flag = readByte().toUByte().toInt()) { 0xFE -> { discardExact(94) diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/Unreachable.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/Unreachable.kt new file mode 100644 index 000000000..a805c4e8a --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/Unreachable.kt @@ -0,0 +1,7 @@ +package net.mamoe.mirai.timpc.utils + +/** + * 表示这里是不可到达的位置. + */ +@Suppress("NOTHING_TO_INLINE") +internal inline fun assertUnreachable(): Nothing = error("This clause should not be reached") diff --git a/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/unsupportedFlag.kt b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/unsupportedFlag.kt new file mode 100644 index 000000000..dbaa24658 --- /dev/null +++ b/mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/utils/unsupportedFlag.kt @@ -0,0 +1,11 @@ +package net.mamoe.mirai.timpc.utils + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.readBytes +import net.mamoe.mirai.utils.io.toUHexString + +internal fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = + error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}") + +internal fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = + error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}") diff --git a/mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt b/mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt new file mode 100644 index 000000000..81f64456b --- /dev/null +++ b/mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/TIMPCBot.kt @@ -0,0 +1,30 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.timpc + +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.withContext +import kotlinx.io.InputStream +import kotlinx.io.core.use +import kotlinx.io.streams.inputStream +import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.toExternalImage +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO +import kotlin.coroutines.CoroutineContext + +internal actual class TIMPCBot actual constructor( + account: BotAccount, + logger: MiraiLogger?, + context: CoroutineContext +) : TIMPCBotBase(account, logger, context) { + suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() + suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(IO) { downloadAsStream().use { ImageIO.read(it) } } + suspend inline fun Image.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() } + + suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) } +} \ No newline at end of file diff --git a/mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt b/mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt new file mode 100644 index 000000000..10e631a2f --- /dev/null +++ b/mirai-core-timpc/src/jvmMain/kotlin/net/mamoe/mirai/timpc/network/NetworkDispatcher.kt @@ -0,0 +1,13 @@ +package net.mamoe.mirai.timpc.network + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +/** + * 包处理协程调度器. + * + * JVM: 独立的 4 thread 调度器 + */ +internal actual val NetworkDispatcher: CoroutineDispatcher + get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher() \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/mirai/test/BadQQFilter.kt b/mirai-core-timpc/src/jvmTest/kotlin/BadQQFilter.kt similarity index 89% rename from mirai-core/src/jvmTest/kotlin/mirai/test/BadQQFilter.kt rename to mirai-core-timpc/src/jvmTest/kotlin/BadQQFilter.kt index 1518dc71c..a0f038d22 100644 --- a/mirai-core/src/jvmTest/kotlin/mirai/test/BadQQFilter.kt +++ b/mirai-core-timpc/src/jvmTest/kotlin/BadQQFilter.kt @@ -1,14 +1,12 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package mirai.test - import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot -import net.mamoe.mirai.login -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult +import net.mamoe.mirai.network.data.LoginResult +import net.mamoe.mirai.timpc.TIMPC import java.util.* /** @@ -44,8 +42,8 @@ suspend fun main() { .map { Pair(it[0].toLong(), it[1]) } .forEach { (qq, password) -> runBlocking { - val bot = Bot( - qq.toUInt(), + val bot = TIMPC.Bot( + qq, if (password.endsWith(".")) password.substring(0, password.length - 1) else password ) diff --git a/mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDebugger.kt b/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt similarity index 87% rename from mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDebugger.kt rename to mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt index cdf29db33..5dc3027b5 100644 --- a/mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDebugger.kt +++ b/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt @@ -1,29 +1,28 @@ @file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS") -package mirai.test.packetdebugger - +import PacketDebugger.dataReceived +import PacketDebugger.dataSent +import PacketDebugger.qq +import PacketDebugger.sessionKey import io.ktor.util.date.GMTDate import kotlinx.coroutines.* -import kotlinx.io.core.* +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.discardExact +import kotlinx.io.core.readBytes +import kotlinx.io.core.readUShort import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.internal.ArrayListSerializer import kotlinx.serialization.json.Json -import mirai.test.packetdebugger.PacketDebugger.dataReceived -import mirai.test.packetdebugger.PacketDebugger.dataSent -import mirai.test.packetdebugger.PacketDebugger.qq -import mirai.test.packetdebugger.PacketDebugger.sessionKey import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter -import net.mamoe.mirai.network.protocol.timpc.packet.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.IgnoredEventPacket -import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaKey -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult -import net.mamoe.mirai.network.protocol.timpc.packet.login.ShareKey -import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchKey +import net.mamoe.mirai.timpc.TIMPC +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.timpc.network.packet.* +import net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket +import net.mamoe.mirai.timpc.network.packet.login.CaptchaKey +import net.mamoe.mirai.timpc.network.packet.login.ShareKey +import net.mamoe.mirai.timpc.network.packet.login.TouchKey import net.mamoe.mirai.utils.DecryptionFailedException import net.mamoe.mirai.utils.decryptBy import net.mamoe.mirai.utils.io.* @@ -37,7 +36,6 @@ import java.nio.charset.Charset import java.util.concurrent.Executors import kotlin.concurrent.thread import kotlin.coroutines.CoroutineContext -import kotlin.io.use /** * 避免 print 重叠. 单线程处理足够调试 @@ -189,7 +187,7 @@ internal object PacketDebugger { /** * null 则不筛选 */ - val qq: UInt? = 761025446u + val qq: Long? = 761025446 /** * 打开后则记录每一个包到文件. */ @@ -208,7 +206,7 @@ internal object PacketDebugger { discardExact(3) val id = matchPacketId(readUShort()) val sequenceId = readUShort() - val packetQQ = readUInt() + val packetQQ = readQQ() if (id == KnownPacketId.HEARTBEAT || (qq != null && packetQQ != qq)) return@read @@ -308,7 +306,7 @@ internal object PacketDebugger { if (IgnoredPacketIdList.contains(id)) { return } - val packetQQ = readUInt() + val packetQQ = readQQ() if (qq != null && packetQQ != qq) { return@read } @@ -378,27 +376,12 @@ when (idHex.substring(0, 5)) { } -internal object DebugNetworkHandler : BotNetworkHandler, CoroutineScope { +internal object DebugNetworkHandler : BotNetworkHandler(), CoroutineScope { override val supervisor: CompletableJob = SupervisorJob() - override val socket: DataPacketSocketAdapter = object : DataPacketSocketAdapter { - override val serverIp: String - get() = "" - override val channel: PlatformDatagramChannel - get() = error("UNSUPPORTED") - override val isOpen: Boolean - get() = true - override fun close() { - } + override val bot: Bot = TIMPC.run { this@DebugNetworkHandler.Bot(qq ?: 0L, "", null) } - override val owner: Bot - get() = bot - - } - override val bot: Bot = Bot(qq ?: 0u, "", coroutineContext) - override val session = BotSession(bot, SessionKey(byteArrayOf())) - - override suspend fun login(): LoginResult = LoginResult.SUCCESS + override suspend fun login() {} override suspend fun awaitDisconnection() { } diff --git a/mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDecoder.kt b/mirai-core-timpc/src/jvmTest/kotlin/packetdebugger/PacketDecoder.kt similarity index 81% rename from mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDecoder.kt rename to mirai-core-timpc/src/jvmTest/kotlin/packetdebugger/PacketDecoder.kt index 93792698b..ad9f72294 100644 --- a/mirai-core/src/jvmTest/kotlin/mirai/test/packetdebugger/PacketDecoder.kt +++ b/mirai-core-timpc/src/jvmTest/kotlin/packetdebugger/PacketDecoder.kt @@ -1,11 +1,10 @@ -package mirai.test.packetdebugger +package packetdebugger import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readUShort -import net.mamoe.mirai.network.protocol.timpc.packet.PacketId -import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaPacket -import net.mamoe.mirai.network.protocol.timpc.packet.matchPacketId +import net.mamoe.mirai.timpc.network.packet.PacketId +import net.mamoe.mirai.timpc.network.packet.matchPacketId import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/mirai-core-timpc/src/main/AndroidManifest.xml b/mirai-core-timpc/src/main/AndroidManifest.xml new file mode 100644 index 000000000..67cacfdb0 --- /dev/null +++ b/mirai-core-timpc/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt similarity index 92% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt index c0f5aac0c..a793e5310 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt @@ -1,5 +1,6 @@ -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.message +import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.utils.MiraiInternalAPI @@ -8,7 +9,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI * 平台相关扩展 */ @UseExperimental(MiraiInternalAPI::class) -actual abstract class MessagePacket : MessagePacketBase() { +actual abstract class MessagePacket actual constructor(bot: Bot) : MessagePacketBase(bot) { // suspend inline fun uploadImage(image: Bitmap): Image = subject.uploadImage(image) //suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image) //suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/BotSession.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/BotSession.kt deleted file mode 100644 index 96ec49a2d..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/BotSession.kt +++ /dev/null @@ -1,30 +0,0 @@ -package net.mamoe.mirai.network - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.io.core.use -import kotlinx.io.streams.inputStream -import net.mamoe.mirai.Bot -import net.mamoe.mirai.message.Image -import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey -import net.mamoe.mirai.utils.MiraiInternalAPI -import java.io.InputStream - -/** - * Android 平台相关扩展. 详情查看 [BotSessionBase] - * - * @author Him188moe - */ -@UseExperimental(MiraiInternalAPI::class) -actual class BotSession internal actual constructor( - bot: Bot, - sessionKey: SessionKey -) : BotSessionBase(bot, sessionKey) { - - suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() - suspend inline fun Image.downloadAsBitmap(): Bitmap = withContext(Dispatchers.IO) { downloadAsStream().use { BitmapFactory.decodeStream(it) } } - //suspend inline fun Image.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() } - -} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/PlatformBotSession.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/PlatformBotSession.kt deleted file mode 100644 index a0dd316ef..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/network/PlatformBotSession.kt +++ /dev/null @@ -1,2 +0,0 @@ -package net.mamoe.mirai.network - diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt index 3cb99d518..390e92429 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt @@ -3,8 +3,6 @@ package net.mamoe.mirai.utils import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.util.KtorExperimentalAPI -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.readBytes import java.io.ByteArrayOutputStream import java.io.DataInput import java.io.EOFException @@ -24,7 +22,7 @@ actual val deviceName: String get() = InetAddress.getLocalHost().hostName * Ktor HttpClient. 不同平台使用不同引擎. */ @KtorExperimentalAPI -internal actual val Http: HttpClient +actual val Http: HttpClient get() = HttpClient(CIO) /** diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRef.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRefAndroid.kt similarity index 100% rename from mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRef.kt rename to mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/WeakRefAndroid.kt diff --git a/mirai-core/src/androidTest/java/ECDHTest.kt b/mirai-core/src/androidTest/java/ECDHTest.kt new file mode 100644 index 000000000..024087ca0 --- /dev/null +++ b/mirai-core/src/androidTest/java/ECDHTest.kt @@ -0,0 +1,8 @@ +import net.mamoe.mirai.timpc.network.TIMProtocol +import net.mamoe.mirai.utils.io.toUHexString + + +fun main() { + + println(EcdhCrypt().calShareKeyMd5ByPeerPublicKey(TIMProtocol.publicKey).toUHexString()) +} \ No newline at end of file diff --git a/mirai-core/src/androidTest/java/EcdhCrypt.java b/mirai-core/src/androidTest/java/EcdhCrypt.java new file mode 100644 index 000000000..01c86bf1f --- /dev/null +++ b/mirai-core/src/androidTest/java/EcdhCrypt.java @@ -0,0 +1,224 @@ +import net.mamoe.mirai.utils.PlatformUtilsAndroidKt; + +import javax.crypto.KeyAgreement; +import java.security.*; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +public class EcdhCrypt { + public static final String DEFAULT_PUB_KEY = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128"; + public static final String DEFAULT_SHARE_KEY = "4da0f614fc9f29c2054c77048a6566d7"; + public static final String S_PUB_KEY = "04928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8"; + public static final String X509_S_PUB_KEY = "3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8"; + public static byte[] _c_pri_key; + public static byte[] _c_pub_key; + private static byte[] _g_share_key; + private static boolean initFlg; + public static PrivateKey pkcs8PrivateKey; + private static boolean userOpenSSLLib; + public static PublicKey x509PublicKey; + + static { + EcdhCrypt.initFlg = false; + EcdhCrypt.userOpenSSLLib = true; + EcdhCrypt._c_pub_key = new byte[0]; + EcdhCrypt._c_pri_key = new byte[0]; + EcdhCrypt._g_share_key = new byte[0]; + } + + public EcdhCrypt() { + /// util.loadLibrary("wtecdh", context); + } + + public static String buf_to_string(final byte[] array) { + String s; + if (array == null) { + s = ""; + } else { + String string = ""; + int n = 0; + while (true) { + s = string; + if (n >= array.length) { + break; + } + string = string + Integer.toHexString(array[n] >> 4 & 0xF) + Integer.toHexString(array[n] & 0xF); + ++n; + } + } + return s; + } + + private byte[] calShareKeyByBouncycastle(final byte[] array) { + String str = "3046301006072A8648CE3D020106052B8104001F03320004"; + try { + if (array.length < 30) { + str = "302E301006072A8648CE3D020106052B8104001F031A00"; + } + final PublicKey constructX509PublicKey = this.constructX509PublicKey(str + buf_to_string(array)); + final KeyAgreement instance = KeyAgreement.getInstance("ECDH", "BC"); + instance.init(EcdhCrypt.pkcs8PrivateKey); + instance.doPhase(constructX509PublicKey, true); + final byte[] generateSecret = instance.generateSecret(); + return PlatformUtilsAndroidKt.md5(generateSecret); + } catch (ExceptionInInitializerError | Exception exceptionInInitializerError) { + exceptionInInitializerError.printStackTrace(); + return null; + } + } + + private byte[] calShareKeyByOpenSSL(final String s, final String str, final String s2) { + //if (this.GenECDHKeyEx(s2, str, s) == 0) { + return EcdhCrypt._g_share_key; + //} + // return null; + } + + private PublicKey constructX509PublicKey(final String str) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException { + return KeyFactory.getInstance("EC", "BC").generatePublic(new X509EncodedKeySpec(string_to_buf(str))); + } + + public static byte[] string_to_buf(final String s) { + int i = 0; + if (s == null) { + return new byte[0]; + } + final byte[] array = new byte[s.length() / 2]; + while (i < s.length() / 2) { + array[i] = (byte) ((get_char((byte) s.charAt(i * 2)) << 4) + get_char((byte) s.charAt(i * 2 + 1))); + ++i; + } + return array; + } + + public static byte get_char(final byte b) { + if (b >= 48 && b <= 57) { + return (byte) (b - 48); + } + if (b >= 97 && b <= 102) { + return (byte) (b - 97 + 10); + } + if (b >= 65 && b <= 70) { + return (byte) (b - 65 + 10); + } + return 0; + } + + private int initShareKeyByBouncycastle() { + try { + final KeyPairGenerator instance = KeyPairGenerator.getInstance("EC", "BC"); + instance.initialize(new ECGenParameterSpec("secp192k1")); + final KeyPair genKeyPair = instance.genKeyPair(); + final PublicKey public1 = genKeyPair.getPublic(); + final byte[] encoded = public1.getEncoded(); + final PrivateKey private1 = genKeyPair.getPrivate(); + private1.getEncoded(); + final PublicKey constructX509PublicKey = this.constructX509PublicKey("3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8"); + final KeyAgreement instance2 = KeyAgreement.getInstance("ECDH", "BC"); + instance2.init(private1); + instance2.doPhase(constructX509PublicKey, true); + EcdhCrypt._g_share_key = PlatformUtilsAndroidKt.md5(instance2.generateSecret()); + System.arraycopy(encoded, 23, EcdhCrypt._c_pub_key = new byte[49], 0, 49); + EcdhCrypt.x509PublicKey = public1; + EcdhCrypt.pkcs8PrivateKey = private1; + return 0; + } catch (ExceptionInInitializerError exceptionInInitializerError) { + exceptionInInitializerError.printStackTrace(); + return -1; + } catch (Exception ex) { + ex.printStackTrace(); + return -2; + } + } + + private int initShareKeyByOpenSSL() { + // if (Build$VERSION.SDK_INT >= 23 || this.GenereateKey() != 0) { + // return -1; + // } + if (EcdhCrypt._c_pub_key == null || EcdhCrypt._c_pub_key.length == 0 || EcdhCrypt._c_pri_key == null || EcdhCrypt._c_pri_key.length == 0 || EcdhCrypt._g_share_key == null || EcdhCrypt._g_share_key.length == 0) { + return -2; + } + return 0; + } + + public native int GenECDHKeyEx(final String p0, final String p1, final String p2); + + public int GenereateKey() { + try { + synchronized (EcdhCrypt.class) { + return this.GenECDHKeyEx("04928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8", "", ""); + } + } catch (UnsatisfiedLinkError unsatisfiedLinkError) { + unsatisfiedLinkError.printStackTrace(); + return -1; + } catch (RuntimeException ex) { + return -2; + } catch (Exception ex2) { + return -3; + } catch (Error error) { + return -4; + } + } + + public byte[] calShareKeyMd5ByPeerPublicKey(final byte[] array) { + if (EcdhCrypt.userOpenSSLLib) { + return this.calShareKeyByOpenSSL(buf_to_string(EcdhCrypt._c_pri_key), buf_to_string(EcdhCrypt._c_pub_key), buf_to_string(array)); + } + return this.calShareKeyByBouncycastle(array); + } + + public byte[] get_c_pub_key() { + return EcdhCrypt._c_pub_key.clone(); + } + + public byte[] get_g_share_key() { + return EcdhCrypt._g_share_key.clone(); + } + + public int initShareKey() { + if (EcdhCrypt.initFlg) { + return 0; + } + EcdhCrypt.initFlg = true; + if (this.initShareKeyByOpenSSL() == 0) { + EcdhCrypt.userOpenSSLLib = true; + return 0; + } + if (this.initShareKeyByBouncycastle() == 0) { + EcdhCrypt.userOpenSSLLib = false; + return 0; + } + return this.initShareKeyByDefault(); + } + + public int initShareKeyByDefault() { + // EcdhCrypt._c_pub_key = util.string_to_buf("020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128"); + // EcdhCrypt._g_share_key = util.string_to_buf("4da0f614fc9f29c2054c77048a6566d7"); + return 0; + } + + public void set_c_pri_key(final byte[] array) { + if (array != null) { + EcdhCrypt._c_pri_key = array.clone(); + return; + } + EcdhCrypt._c_pri_key = new byte[0]; + } + + public void set_c_pub_key(final byte[] array) { + if (array != null) { + EcdhCrypt._c_pub_key = array.clone(); + return; + } + EcdhCrypt._c_pub_key = new byte[0]; + } + + public void set_g_share_key(final byte[] array) { + if (array != null) { + EcdhCrypt._g_share_key = array.clone(); + return; + } + EcdhCrypt._g_share_key = new byte[0]; + } +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 63d2ba652..8cd51b69b 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -1,20 +1,23 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName") package net.mamoe.mirai +import io.ktor.util.KtorExperimentalAPI import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Job +import kotlinx.io.OutputStream +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.use import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult +import net.mamoe.mirai.network.data.AddFriendResult +import net.mamoe.mirai.network.data.ImageLink import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.GroupNotFoundException +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.io.transferTo import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.coroutineContext -import kotlin.jvm.JvmSynthetic - /** * Mirai 的机器人. 一个机器人实例登录一个 QQ 账号. @@ -22,77 +25,60 @@ import kotlin.jvm.JvmSynthetic * * @see Contact */ -interface Bot : CoroutineScope { +abstract class Bot : CoroutineScope { + @UseExperimental(MiraiInternalAPI::class) 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) - - 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) - - operator fun invoke(account: BotAccount, context: CoroutineContext): Bot = BotImpl(account, context = context) - - inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) - fun instanceWhose(qq: UInt): Bot = BotImpl.instanceWhose(qq = qq) + fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq) } /** * 账号信息 */ - val account: BotAccount + abstract val account: BotAccount /** * 日志记录器 */ - val logger: MiraiLogger + abstract val logger: MiraiLogger - override val coroutineContext: CoroutineContext + abstract override val coroutineContext: CoroutineContext // region contacts /** * 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友 */ - val qqs: ContactList + abstract val qqs: ContactList /** * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. */ - fun getQQ(id: UInt): QQ - - /** - * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. - */ - fun getQQ(id: Long): QQ + abstract fun getQQ(id: Long): QQ /** * 与这个机器人相关的群列表. 机器人不一定是群成员. */ - val groups: ContactList + abstract val groups: ContactList /** * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. * 若 [id] 无效, 将会抛出 [GroupNotFoundException] */ - suspend fun getGroup(id: GroupId): Group + abstract suspend fun getGroup(id: GroupId): Group /** * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. * 若 [internalId] 无效, 将会抛出 [GroupNotFoundException] */ - suspend fun getGroup(internalId: GroupInternalId): Group + abstract suspend fun getGroup(internalId: GroupInternalId): Group /** * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. * 若 [id] 无效, 将会抛出 [GroupNotFoundException] */ - suspend fun getGroup(id: Long): Group + abstract suspend fun getGroup(id: Long): Group // endregion @@ -101,36 +87,64 @@ interface Bot : CoroutineScope { /** * 网络模块 */ - val network: BotNetworkHandler<*> + abstract val network: BotNetworkHandler /** - * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. - * 然后重新启动并尝试登录 + * 使用在默认配置基础上修改的配置进行登录 */ - fun tryReinitializeNetworkHandler( - configuration: BotConfiguration, - cause: Throwable? = null - ): Job + suspend inline fun login(configuration: BotConfiguration.() -> Unit) { + return this.login(BotConfiguration().apply(configuration)) + } /** - * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. - * 然后重新启动并尝试登录 + * 使用特定配置进行登录 */ - suspend fun reinitializeNetworkHandler( - configuration: BotConfiguration, - cause: Throwable? = null - ): LoginResult + abstract suspend fun login(configuration: BotConfiguration = BotConfiguration.Default) + // endregion + + + // region actions + + abstract suspend fun Image.getLink(): ImageLink + + suspend fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray() + + suspend fun Image.download(): ByteReadPacket = getLink().download() /** - * [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. - * 然后重新启动并尝试登录 + * 添加一个好友 + * + * @param message 若需要验证请求时的验证消息. + * @param remark 好友备注 */ - fun reinitializeNetworkHandlerAsync( - configuration: BotConfiguration, - cause: Throwable? = null - ): Deferred + abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult + + /** + * 同意来自陌生人的加好友请求 + */ + abstract suspend fun approveFriendAddRequest(id: Long, remark: String?) // endregion - fun close() + abstract fun close(throwable: Throwable?) + + // region extensions + + fun Int.qq(): QQ = getQQ(this.toLong()) + fun Long.qq(): QQ = getQQ(this) + + suspend inline fun Int.group(): Group = getGroup(this.toLong()) + suspend inline fun Long.group(): Group = getGroup(this) + suspend inline fun GroupInternalId.group(): Group = getGroup(this) + suspend inline fun GroupId.group(): Group = getGroup(this) + + + /** + * 需要调用者自行 close [output] + */ + @UseExperimental(KtorExperimentalAPI::class) + suspend inline fun Image.downloadTo(output: OutputStream) = + download().use { input -> input.transferTo(output) } + + // endregion } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt index 5a9427630..66fe0b055 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt @@ -3,8 +3,6 @@ package net.mamoe.mirai data class BotAccount( - val id: UInt, + val id: Long, val password: String -) { - constructor(id: Long, password: String) : this(id.toUInt(), password) -} \ No newline at end of file +) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt deleted file mode 100644 index e6e000303..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAddFriend.kt +++ /dev/null @@ -1,149 +0,0 @@ -@file:JvmMultifileClass -@file:JvmName("BotHelperKt") -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai - -import net.mamoe.mirai.network.protocol.timpc.packet.action.AddFriendPacket -import net.mamoe.mirai.network.protocol.timpc.packet.action.CanAddFriendPacket -import net.mamoe.mirai.network.protocol.timpc.packet.action.CanAddFriendResponse -import net.mamoe.mirai.network.protocol.timpc.packet.action.RequestFriendAdditionKeyPacket -import net.mamoe.mirai.network.sessionKey -import kotlin.contracts.ExperimentalContracts -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - - -@Suppress("ClassName") -sealed class AddFriendResult { - - abstract class DONE internal constructor() : AddFriendResult() { - override fun toString(): String = "AddFriendResult(Done)" - } - - /** - * 对方拒绝添加好友 - */ - object REJECTED : AddFriendResult() { - override fun toString(): String = "AddFriendResult(Rejected)" - } - - /** - * 这个人已经是好友 - */ - object ALREADY_ADDED : DONE() { - override fun toString(): String = "AddFriendResult(AlreadyAdded)" - } - - /** - * 等待对方同意 - */ - object WAITING_FOR_APPROVE : DONE() { - override fun toString(): String = "AddFriendResult(WaitingForApprove)" - } - - /** - * 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果) - */ - object ADDED : DONE() { - override fun toString(): String = "AddFriendResult(Added)" - } -} - - -/* - -// TODO: 2019/11/11 其中一个是对方已同意添加好友的包 - -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 BC, identity=(2092749761->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 01 00 00 0F 00 00 00 00 00 00 00 00 01 03 EB 00 02 0A 00 -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749761->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 00 BC 01 00 00 00 00 02 00 00 - */ - -/** - * 添加一个好友 - * - * @param message 若需要验证请求时的验证消息. - * @param remark 好友备注 - */ -@UseExperimental(ExperimentalContracts::class) -suspend fun Bot.addFriend(id: UInt, message: String? = null, remark: String? = null): AddFriendResult = withSession { - return when (CanAddFriendPacket(qqAccount, id, sessionKey).sendAndExpect()) { - is CanAddFriendResponse.AlreadyAdded -> AddFriendResult.ALREADY_ADDED - is CanAddFriendResponse.Rejected -> AddFriendResult.REJECTED - - is CanAddFriendResponse.ReadyToAdd, - is CanAddFriendResponse.RequireVerification -> { - val key = RequestFriendAdditionKeyPacket(qqAccount, id, sessionKey).sendAndExpect().key - AddFriendPacket.RequestAdd(qqAccount, id, sessionKey, message, remark, key).sendAndExpect() - AddFriendResult.WAITING_FOR_APPROVE - } //这个做的是需要验证消息的情况, 不确定 ReadyToAdd 的是啥 - - // 似乎 RequireVerification 和 ReadyToAdd 判断错了. 需要重新检查一下 - - // TODO: 2019/11/11 需要验证问题的情况 - - /*is CanAddFriendResponse.ReadyToAdd -> { - // TODO: 2019/11/11 这不需要验证信息的情况 - - //AddFriendPacket(qqAccount, id, sessionKey, ).sendAndExpectAsync().await() - TODO() - }*/ - } -} - -/* -1494695429 同意好友请求后收到以下包: - -Mirai 22:11:14 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 83 D8 A5 EE 05 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 02, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 02 00 00 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=00 D6, identity=(1494695429->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 00 BC 01 00 00 00 00 02 00 00 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=00 BC, identity=(1494695429->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 01 00 00 0F 00 00 00 00 00 00 00 00 01 03 EB 00 02 0A 00 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) -//�9����同意你的加好友请求"him188的小dick( - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 3B 08 02 1A 03 08 E2 01 0A 39 08 01 10 85 FC DC C8 05 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 10 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B 28 01 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 B7 D8 A5 EE 05 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 04 08 02 1A 02 08 23 1A 02 08 00 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 0C 08 02 1A 03 08 E2 01 0A 0A 08 00 10 DD F1 92 B7 07 1A 00 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 18 08 02 1A 02 08 44 1A 0E 08 DD F1 92 B7 07 10 B7 D8 A5 EE 05 18 01 2A 06 08 B7 D8 A5 EE 05 -Mirai 22:12:06 : Packet received: UnknownEventPacket(id=02 10, identity=(1494695429->1994701021)) -//来自QQ号查找:BJR - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 31 08 02 1A 02 08 23 12 2F 08 DD F1 92 B7 07 10 85 FC DC C8 05 18 03 20 82 D8 A5 EE 05 2A 11 E6 9D A5 E8 87 AA 51 51 E5 8F B7 E6 9F A5 E6 89 BE 3A 00 42 00 4A 00 52 00 -Mirai 22:12:07 : Packet received: UnknownPacket(01 2D) -body=01 01 00 00 00 01 59 17 3E 05 00 00 00 -Mirai 22:12:07 : UnknownPacket(01 2D) = 71 B9 C2 FD 11 79 A3 CC FB 30 BF C6 5E 3B 18 87 A7 1C 52 BF 7F B4 61 42 BF C6 5E 3B 18 87 A7 1C 52 BF 7F B4 61 42 8A CB 49 F4 72 98 29 55 F8 04 FB 38 F5 87 65 98 9D D0 F0 F4 3A EC 12 02 43 F5 39 BF 40 2E 4F 0B 37 29 C6 DB A7 3B FC C7 FB 90 CF 6F 15 D3 34 75 EE 2A 4C 36 E4 39 F2 D1 4B 87 82 37 16 A3 84 E8 D8 2D 19 F3 A8 20 6E 66 C7 22 16 A3 84 E8 D8 2D 19 F3 A8 20 6E 66 C7 22 6D 2A F0 DA A1 E2 C1 54 29 E2 C5 A1 26 11 CA FC 4A E9 EA 32 95 78 41 31 9E 78 04 A8 B6 7C 0E E7 2B 32 87 E3 0A 84 67 F4 83 3F 53 C8 B1 BE EE 07 02 10 97 67 AF 0C DF 2B 20 AC E0 7E 42 7B 98 01 CB CE B8 13 52 8B 34 9A 4D A0 14 BA 6E 88 0E 2F F9 06 B5 1E 4A 00 D7 0E 0A 58 75 7D 39 2E B1 38 A0 4A 13 1C 3E 71 8C 78 CA F7 39 2E B1 38 A0 4A 13 1C 3E 71 8C 78 CA F7 46 06 FA F4 99 D8 52 A1 D7 70 12 40 1B 61 82 3D 8D F6 96 F1 C5 DB 1C E3 F8 9D DD 8A 2C 2C F5 62 EC BF FD C1 F0 77 58 0B FD 29 DE 23 D0 AF CD 46 90 A2 A1 D4 50 6D B2 52 D4 4A 2A EF 7D 4E 6E F8 63 41 BE D8 5F A1 A9 43 BF BC E1 54 C0 A0 33 CD 1B C6 84 2E 72 31 F7 E2 A7 91 3C DB 2D FD A7 84 CA 87 A2 3C 64 A4 04 82 4B 88 74 74 43 45 E1 48 FA BB 15 A6 D5 82 3F FF 2A BA C8 AF F8 E1 77 15 0D 5C 84 EB 40 C7 1E 52 16 CB EB 75 04 54 17 95 09 BF FD CA E8 C7 D1 93 F8 83 6B 50 26 A8 E6 23 00 AA EB 75 56 2D 24 62 CC 79 4E AA 92 B6 F6 CA BA 57 05 57 B3 53 32 60 4B 3B 20 D0 F6 57 31 52 49 EC B0 0B C0 97 D6 39 AC 16 F6 57 31 52 49 EC B0 0B C0 97 D6 39 AC 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - -2092749761 同意后收到以下几个包 - -Mirai 22:04:40 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 F9 D4 A5 EE 05 -Mirai 22:04:45 : Packet received: UnknownEventPacket(id=02 10, identity=(1040400290->1994701021)) -//5�������%�ԥ� (2对方正在输入...( - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 35 08 02 1A 03 08 95 02 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 25 08 00 10 05 18 FE D4 A5 EE 05 20 01 28 08 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E 28 01 -Mirai 22:04:45 : Packet received: FriendConversationInitialize(qq=1040400290) -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 BC, identity=(2092749761->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 01 00 00 0F 00 00 00 00 00 00 00 00 01 03 EB 00 02 0A 00 -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=00 D6, identity=(2092749761->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 00 BC 01 00 00 00 00 02 00 00 -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=02 02, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 02 00 00 -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) -//:�8����同意你的加好友请求"him188的老公( - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00 3A 08 02 1A 03 08 E2 01 0A 38 08 01 10 C1 A7 F3 E5 07 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0F 68 69 6D 31 38 38 E7 9A 84 E8 80 81 E5 85 AC 28 01 -Mirai 22:04:48 : Packet received: UnknownEventPacket(id=02 10, identity=(1994701021->1994701021)) - = 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 08 08 02 1A 02 08 44 2A 06 08 81 D5 A5 EE 05 -Mirai 22:04:48 : Packet received: UnknownPacket(01 2D) -body=01 01 00 00 00 01 7C BC D3 C1 00 00 00 -Mirai 22:04:48 : UnknownPacket(01 2D) = 66 B9 2C A3 EA 31 AD 5B E4 52 37 25 3B 21 A9 FF EE 2E 80 AF 6F 9B C0 A7 37 25 3B 21 A9 FF EE 2E 80 AF 6F 9B C0 A7 6D FF BF 73 37 8A 34 4C D1 DF D3 ED 93 DA B3 32 AC 4D 11 D7 68 F0 93 A3 F5 BE 93 DA B3 32 AC 4D 11 D7 68 F0 93 A3 F5 BE CC 5F 8A DC 52 D7 2E 39 F6 D0 A6 FE E2 FE A9 1D C1 5E AC 2A 02 46 A0 90 23 6E 10 A6 48 B0 04 BC 06 F8 AF 42 87 DA 69 42 55 AA 48 B0 04 BC 06 F8 AF 42 87 DA 69 42 55 AA 29 3B C5 0C 5B 43 EE FA 30 EA 81 86 F3 8D FB 41 EA A3 23 59 27 49 03 B6 34 5C 4A CA DE 8E 3C 02 36 5F 48 C7 14 73 E4 D4 D8 C3 81 80 FC 49 9B 3C CC 4D D8 E9 07 84 56 40 F9 7E 80 D8 11 60 B1 FF F0 0E 5D 6E 4B 45 41 B4 81 54 EB B9 EE 98 D2 29 F3 05 BD 96 D3 E4 A6 42 98 CD C4 D1 5F 10 DE 62 EB E5 D3 E4 A6 42 98 CD C4 D1 5F 10 DE 62 EB E5 25 61 AA 54 A1 BE 14 78 F9 AC 2B F1 43 0B B5 51 2D 15 AA DE 97 B8 CC A3 2A 9B 8B AB 37 7C 45 57 D6 B9 BF 6C 4B 7B 66 AD 89 EB 90 42 0F 5F 63 A7 CC 06 4D 08 E0 5C 5D E3 9A AF 0D 19 C7 78 B5 30 6C 9D E2 A4 CA 3A DD 64 FC 78 A8 E1 59 1F 67 97 C6 B2 0B 73 EB 9A 2D 75 07 7E CE 82 3B EC CF 3A 9F 98 4F C0 BA 98 69 D7 65 87 EA 53 90 18 01 BD 8B AB EB 40 74 9C 03 C4 92 3B 9A F5 3A DD 51 84 EF 72 48 71 DC B4 AA D5 95 AB BC 4B 97 70 4D FD EE DE 37 BD 33 0C DF 64 C5 55 2E ED A6 98 6A 88 28 8B F3 24 8D 73 00 DE 9E FC 78 15 4A AC E2 3F AD 93 4C 2F 88 48 34 DA F3 F7 FC B7 E7 39 F6 33 3E 5C 88 48 34 DA F3 F7 FC B7 E7 39 F6 33 3E 5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - - - */ \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt new file mode 100644 index 000000000..9428393b3 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt @@ -0,0 +1,70 @@ +package net.mamoe.mirai + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.utils.MiraiLogger + +/** + * 构造 [Bot] 的工厂. + * + * 在协议模块中有各自的实现. + * - `mirai-core-timpc`: `TIMPC` + * - `mirai-core-qqandroid`: `QQAndroid` + */ +@Suppress("FunctionName") +interface BotFactory { + /** + * 在当前 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * suspend fun myProcess(){ + * TIMPC.Bot(account, logger) + * } + * ``` + */ + suspend fun Bot(account: BotAccount, logger: MiraiLogger? = null): Bot + + /** + * 在当前 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * suspend fun myProcess(){ + * TIMPC.Bot(account, logger) + * } + * ``` + */ + suspend fun Bot(qq: Long, password: String, logger: MiraiLogger? = null): Bot + + /** + * 在特定的 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * fun myProcess(){ + * TIMPC.run { + * GlobalScope.Bot(account, logger) + * } + * } + * ``` + */ + fun CoroutineScope.Bot(qq: Long, password: String, logger: MiraiLogger? = null): Bot + + /** + * 在特定的 CoroutineScope 下构造 Bot 实例 + * 该 Bot 实例的生命周期将会跟随这个 CoroutineScope. + * 这个 CoroutineScope 也会等待 Bot 的结束才会结束. + * + * ```kotlin + * fun myProcess(){ + * TIMPC.run { + * GlobalScope.Bot(account, logger) + * } + * } + * ``` + */ + fun CoroutineScope.Bot(account: BotAccount, logger: MiraiLogger? = null): Bot +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt index a85977c90..76385c747 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt @@ -1,73 +1,26 @@ @file:JvmMultifileClass @file:JvmName("BotHelperKt") -@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") +@file:Suppress("unused", "EXPERIMENTAL_API_USAGE") package net.mamoe.mirai -import net.mamoe.mirai.contact.* -import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult -import net.mamoe.mirai.network.protocol.timpc.packet.login.requireSuccess import net.mamoe.mirai.utils.BotConfiguration -import net.mamoe.mirai.utils.internal.PositiveNumbers -import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName -import kotlin.jvm.JvmOverloads /* * 在 [Bot] 中的方法的捷径 */ //Contacts -suspend inline fun Bot.getGroup(id: UInt): Group = this.getGroup(GroupId(id)) - - -/** - * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. - * 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync] - */ -@UseExperimental(ExperimentalContracts::class) -inline fun Bot.withSession(block: BotSession.() -> R): R { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - return with(this.network.session) { block() } -} - -/** - * 发送数据包 - * @throws IllegalStateException 当 [BotNetworkHandler.socket] 未开启时 - */ -internal suspend inline fun Bot.sendPacket(packet: OutgoingPacket) = - (this.network as TIMBotNetworkHandler).socket.sendPacket(packet) - -/** - * 使用在默认配置基础上修改的配置进行登录 - */ -@UseExperimental(ExperimentalContracts::class) -suspend inline fun Bot.login(configuration: BotConfiguration.() -> Unit): LoginResult { - contract { - callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) - } - return this.reinitializeNetworkHandler(BotConfiguration().apply(configuration)) -} - -/** - * 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回登录结果 - */ -suspend inline fun Bot.login(): LoginResult = this.reinitializeNetworkHandler(BotConfiguration.Default) - /** * 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回 [this] */ -suspend inline fun Bot.alsoLogin(): Bot = apply { login().requireSuccess() } +suspend inline fun Bot.alsoLogin(configuration: BotConfiguration = BotConfiguration.Default): Bot = + apply { login(configuration) } /** * 使用在默认配置基础上修改的配置进行登录, 返回 [this] @@ -77,20 +30,11 @@ suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bo contract { callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) } - this.reinitializeNetworkHandler(BotConfiguration().apply(configuration)).requireSuccess() + this.login(configuration) return this } -/** - * 使用默认的配置 ([BotConfiguration.Default]) 登录, 返回 [this] - */ -suspend inline fun Bot.alsoLogin(message: String): Bot { - return this.apply { - login().requireSuccess { message } // requireSuccess is inline, so no performance waste - } -} - /** * 取得机器人的 QQ 号 */ -inline val Bot.qqAccount: UInt get() = this.account.id \ No newline at end of file +inline val Bot.qqAccount: Long get() = this.account.id \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 51e8af17d..e28576748 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -2,54 +2,43 @@ 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 kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId -import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupNotFound -import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupPacket -import net.mamoe.mirai.network.protocol.timpc.packet.action.RawGroupInfo -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult -import net.mamoe.mirai.network.protocol.timpc.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.DefaultLogger +import net.mamoe.mirai.utils.LockFreeLinkedList +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.io.logStacktrace import kotlin.coroutines.CoroutineContext -import kotlin.jvm.JvmSynthetic -@PublishedApi -internal class BotImpl @PublishedApi internal constructor( +/* + * 泛型 N 不需要向外(接口)暴露. + */ +@MiraiInternalAPI +abstract class BotImpl constructor( override val account: BotAccount, override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"), context: CoroutineContext -) : Bot, CoroutineScope { +) : 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) - } + @Suppress("LeakingThis") + instances.addLast(this) } companion object { - init { - KnownPacketId.values() /* load id classes */ - } - @PublishedApi internal val instances: LockFreeLinkedList = LockFreeLinkedList() inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block) - fun instanceWhose(qq: UInt): Bot { + fun instanceWhose(qq: Long): Bot { instances.forEach { if (it.qqAccount == qq) { return it @@ -63,101 +52,21 @@ internal class BotImpl @PublishedApi internal constructor( // region network - override val network: BotNetworkHandler<*> get() = _network - private lateinit var _network: BotNetworkHandler<*> - - override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close - configuration: BotConfiguration, - cause: Throwable? - ): Job = launch { - repeat(configuration.reconnectionRetryTimes) { - if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) { - logger.info("Reconnected successfully") - return@launch - } else { - delay(configuration.reconnectPeriodMillis) - } - } - } - - override suspend fun reinitializeNetworkHandler( - configuration: BotConfiguration, - cause: Throwable? - ): LoginResult { - logger.info("BotAccount: ${qqAccount.toLong()}") - logger.info("Initializing BotNetworkHandler") - try { - if (::_network.isInitialized) { - _network.close(cause) - } - } catch (e: Exception) { - logger.error("Cannot close network handler", e) - } - _network = TIMBotNetworkHandler(this.coroutineContext + configuration, this) - - return _network.login() - } - - override fun reinitializeNetworkHandlerAsync( - configuration: BotConfiguration, - cause: Throwable? - ): Deferred = async { reinitializeNetworkHandler(configuration, cause) } - - // endregion - - // region contacts - override val groups: ContactList = ContactList(LockFreeLinkedList()) - - override val qqs: ContactList = ContactList(LockFreeLinkedList()) - - /** - * 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个. - */ - @UseExperimental(MiraiInternalAPI::class) - @JvmSynthetic - override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) } - - @UseExperimental(MiraiInternalAPI::class) - override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt()) - - override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId()) - - @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class) - override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline { - val info: RawGroupInfo = try { - when (val response = - withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect() }) { - is RawGroupInfo -> response - is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}") - else -> assertUnreachable() - } - } catch (e: Exception) { - throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e) - } - - return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) } - } - - @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) } - } - } + abstract override val network: N // endregion @UseExperimental(MiraiInternalAPI::class) - override fun close() { - _network.close() - this.supervisorJob.complete() - groups.delegate.clear() - qqs.delegate.clear() + override fun close(throwable: Throwable?) { + if (throwable == null) { + network.close() + this.supervisorJob.complete() + groups.delegate.clear() + qqs.delegate.clear() + } else { + network.close(throwable) + this.supervisorJob.completeExceptionally(throwable) + groups.delegate.clear() + qqs.delegate.clear() + } } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index b3e3b420e..c8b1de35c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -3,15 +3,9 @@ package net.mamoe.mirai.contact import net.mamoe.mirai.Bot -import net.mamoe.mirai.message.Message -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 net.mamoe.mirai.message.data.* +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.WeakRefProperty import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -26,12 +20,13 @@ interface Contact { /** * 这个联系人所属 [Bot] */ - val bot: Bot + @WeakRefProperty + val bot: Bot // weak ref /** * 可以是 QQ 号码或者群号码 [GroupId]. */ - val id: UInt + val id: Long /** * 向这个对象发送消息. @@ -39,6 +34,8 @@ interface Contact { * 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右. */ suspend fun sendMessage(message: MessageChain) + + suspend fun uploadImage(image: ExternalImage): ImageId } suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.chain()) @@ -46,13 +43,13 @@ suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.c suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain()) /** - * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. - * 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync] + * 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. + * 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法 */ @UseExperimental(ExperimentalContracts::class) -inline fun Contact.withSession(block: BotSession.() -> R): R { +inline fun Contact.withBot(block: Bot.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return bot.withSession(block) + return bot.run(block) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt index 46ac648d5..f725d1d56 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt @@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.joinToString */ @UseExperimental(MiraiInternalAPI::class) @Suppress("unused") -class ContactList(@PublishedApi internal val delegate: LockFreeLinkedList) { +class ContactList(@MiraiInternalAPI val delegate: LockFreeLinkedList) { /** * ID 列表的字符串表示. * 如: @@ -22,9 +22,9 @@ class ContactList(@PublishedApi internal val delegate: LockFreeLink */ 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 + operator fun get(id: Long): C = delegate[id] + fun getOrNull(id: Long): C? = delegate.getOrNull(id) + fun containsId(id: Long): Boolean = delegate.getOrNull(id) != null val size: Int get() = delegate.size operator fun contains(element: C): Boolean = delegate.contains(element) @@ -35,14 +35,14 @@ class ContactList(@PublishedApi internal val delegate: LockFreeLink override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") } -operator fun LockFreeLinkedList.get(id: UInt): C { +operator fun LockFreeLinkedList.get(id: Long): C { forEach { if (it.id == id) return it } throw NoSuchElementException() } -fun LockFreeLinkedList.getOrNull(id: UInt): C? { +fun LockFreeLinkedList.getOrNull(id: Long): C? { forEach { if (it.id == id) return it } return null } -fun LockFreeLinkedList.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier) +fun LockFreeLinkedList.getOrAdd(id: Long, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index 3ba389c03..7e6d753cc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -3,10 +3,7 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope -import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupInfo -import net.mamoe.mirai.network.protocol.timpc.packet.action.QuitGroupResponse -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.internal.PositiveNumbers +import net.mamoe.mirai.network.data.GroupInfo import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail @@ -53,7 +50,7 @@ interface Group : Contact, CoroutineScope/*, Map*/ { // TODO: 2019 /** * 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException] */ - fun getMember(id: UInt): Member + fun getMember(id: Long): Member /** * 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新. @@ -67,7 +64,7 @@ interface Group : Contact, CoroutineScope/*, Map*/ { // TODO: 2019 * * @see QuitGroupResponse.isSuccess 判断是否成功 */ - suspend fun quit(): QuitGroupResponse + suspend fun quit(): Boolean fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" } @@ -81,27 +78,19 @@ interface Group : Contact, CoroutineScope/*, Map*/ { // TODO: 2019 * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] */ -inline class GroupId(inline val value: UInt) - -/** - * 将 [this] 转为 [GroupId]. - */ -@Suppress("NOTHING_TO_INLINE") -inline fun UInt.groupId(): GroupId = GroupId(this) +inline class GroupId(inline val value: Long) /** * 将 [this] 转为 [GroupInternalId]. */ -@Suppress("NOTHING_TO_INLINE") -inline fun UInt.groupInternalId(): GroupInternalId = GroupInternalId(this) +fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this) /** * 将无符号整数格式的 [Long] 转为 [GroupId]. * * 注: 在 Java 中常用 [Long] 来表示 [UInt] */ -fun @receiver:PositiveNumbers Long.groupId(): GroupId = - GroupId(this.coerceAtLeastOrFail(0).toUInt()) +fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0)) /** * 一些群 API 使用的 ID. 在使用时会特别注明 @@ -111,4 +100,4 @@ fun @receiver:PositiveNumbers Long.groupId(): GroupId = * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] */ -inline class GroupInternalId(inline val value: UInt) +inline class GroupInternalId(inline val value: Long) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt index 632823183..45b0b4905 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/GroupIdConvertions.kt @@ -6,7 +6,7 @@ import kotlin.math.pow @Suppress("ObjectPropertyName") -private val `10EXP6` = 10.0.pow(6).toUInt() +private val `10EXP6` = 10.0.pow(6) fun GroupId.toInternalId(): GroupInternalId { @@ -23,12 +23,12 @@ fun GroupId.toInternalId(): GroupInternalId { in 1..10 -> plusLeft(202, 6) in 11..19 -> plusLeft(469, 6) in 20..66 -> plusLeft(208, 7) - in 67..156 -> plusLeft(1943, 6) + in 67..156 -> plusLeft(1943, 6) in 157..209 -> plusLeft(199, 7) in 210..309 -> plusLeft(389, 7) in 310..499 -> plusLeft(349, 7) else -> null - }?.toUInt() ?: this.value + }?.toLong() ?: this.value ) } @@ -36,17 +36,17 @@ fun GroupInternalId.toId(): GroupId = with(value.toString()) { if (value < `10EXP6`) { return GroupId(value) } - val left: UInt = this.dropLast(6).toUInt() + val left = this.dropLast(6).toLong() 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() + in 203..212 -> ((left - 202).toString() + this.takeLast(6).toInt().toString()).toLong() + in 480..488 -> ((left - 469).toString() + this.takeLast(6).toInt().toString()).toLong() + in 2100..2146 -> ((left.toString().take(3).toLong() - 208).toString() + this.takeLast(7).toInt().toString()).toLong() + in 2010..2099 -> ((left - 1943).toString() + this.takeLast(6).toInt().toString()).toLong() + in 2147..2199 -> ((left.toString().take(3).toLong() - 199).toString() + this.takeLast(7).toInt().toString()).toLong() + in 4100..4199 -> ((left.toString().take(3).toLong() - 389).toString() + this.takeLast(7).toInt().toString()).toLong() + in 3800..3989 -> ((left.toString().take(3).toLong() - 349).toString() + this.takeLast(7).toInt().toString()).toLong() else -> value } ) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt index 6c88bee68..711de3f99 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt @@ -4,12 +4,9 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.data.Profile -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.packet.action.AvatarLink -import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendNameRemark -import net.mamoe.mirai.network.protocol.timpc.packet.action.PreviousNameList -import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.network.data.FriendNameRemark +import net.mamoe.mirai.network.data.PreviousNameList +import net.mamoe.mirai.network.data.Profile /** * QQ 对象. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index a580a658b..b9a05d180 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -6,11 +6,10 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.isAdministrator import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.contact.isOwner -import net.mamoe.mirai.message.Message -import net.mamoe.mirai.message.any -import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage -import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage -import net.mamoe.mirai.network.protocol.timpc.packet.event.MessagePacket +import net.mamoe.mirai.message.FriendMessage +import net.mamoe.mirai.message.GroupMessage +import net.mamoe.mirai.message.MessagePacket +import net.mamoe.mirai.message.data.Message import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -188,15 +187,9 @@ class MessageSubscribersBuilder>( * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息 */ @MessageDsl - suspend inline fun sentBy(qqId: UInt, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = content({ sender.id == qqId }, onEvent) - /** - * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息 - */ - @MessageDsl - suspend inline fun sentBy(qqId: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = sentBy(qqId.toUInt(), onEvent) - /** * 如果是管理员或群主发的消息, 就执行 [onEvent] */ @@ -222,21 +215,15 @@ class MessageSubscribersBuilder>( * 如果是来自这个群的消息, 就执行 [onEvent] */ @MessageDsl - suspend inline fun sentFrom(id: UInt, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = + suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = content({ if (this is GroupMessage) group.id == id else false }, onEvent) - /** - * 如果是来自这个群的消息, 就执行 [onEvent] - */ - @MessageDsl - suspend inline fun sentFrom(id: Long, noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = sentFrom(id.toUInt(), onEvent) - /** * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent] */ @MessageDsl suspend inline fun has(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = - subscriber { if (message.any()) onEvent(this) } + subscriber { if (message.any { it::class == M::class }) onEvent(this) } /** * 如果 [filter] 返回 `true` 就执行 `onEvent` @@ -283,7 +270,7 @@ class MessageSubscribersBuilder>( */ @MessageDsl suspend inline fun Regex.matchingReply(noinline replier: AnyReplier) { - content({ this@matchingReply.matchEntire(it) != null }){ + content({ this@matchingReply.matchEntire(it) != null }) { @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning executeAndReply(replier) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt index ecc42de3c..6db2c9aa1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/SubscribersWithBot.kt @@ -7,7 +7,6 @@ import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.internal.HandlerWithSession import net.mamoe.mirai.event.internal.Listener import net.mamoe.mirai.event.internal.subscribeInternal -import net.mamoe.mirai.network.BotSession import kotlin.reflect.KClass /** @@ -19,88 +18,87 @@ import kotlin.reflect.KClass // region 顶层方法 -suspend inline fun Bot.subscribe(noinline handler: suspend BotSession.(E) -> ListeningStatus): Listener = +suspend inline fun Bot.subscribe(noinline handler: suspend Bot.(E) -> ListeningStatus): Listener = E::class.subscribe(this, handler) -suspend inline fun Bot.subscribeAlways(noinline listener: suspend BotSession.(E) -> Unit): Listener = +suspend inline fun Bot.subscribeAlways(noinline listener: suspend Bot.(E) -> Unit): Listener = E::class.subscribeAlways(this, listener) -suspend inline fun Bot.subscribeOnce(noinline listener: suspend BotSession.(E) -> Unit): Listener = +suspend inline fun Bot.subscribeOnce(noinline listener: suspend Bot.(E) -> Unit): Listener = E::class.subscribeOnce(this, listener) -suspend inline fun Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend BotSession.(E) -> T): Listener = +suspend inline fun Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend Bot.(E) -> T): Listener = E::class.subscribeUntil(this, valueIfStop, listener) -suspend inline fun Bot.subscribeUntilFalse(noinline listener: suspend BotSession.(E) -> Boolean): Listener = +suspend inline fun Bot.subscribeUntilFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener = E::class.subscribeUntilFalse(this, listener) -suspend inline fun Bot.subscribeUntilTrue(noinline listener: suspend BotSession.(E) -> Boolean): Listener = +suspend inline fun Bot.subscribeUntilTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener = E::class.subscribeUntilTrue(this, listener) -suspend inline fun Bot.subscribeUntilNull(noinline listener: suspend BotSession.(E) -> Any?): Listener = +suspend inline fun Bot.subscribeUntilNull(noinline listener: suspend Bot.(E) -> Any?): Listener = E::class.subscribeUntilNull(this, listener) -suspend inline fun Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend BotSession.(E) -> T): Listener = +suspend inline fun Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend Bot.(E) -> T): Listener = E::class.subscribeWhile(this, valueIfContinue, listener) -suspend inline fun Bot.subscribeWhileFalse(noinline listener: suspend BotSession.(E) -> Boolean): Listener = +suspend inline fun Bot.subscribeWhileFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener = E::class.subscribeWhileFalse(this, listener) -suspend inline fun Bot.subscribeWhileTrue(noinline listener: suspend BotSession.(E) -> Boolean): Listener = +suspend inline fun Bot.subscribeWhileTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener = E::class.subscribeWhileTrue(this, listener) -suspend inline fun Bot.subscribeWhileNull(noinline listener: suspend BotSession.(E) -> Any?): Listener = +suspend inline fun Bot.subscribeWhileNull(noinline listener: suspend Bot.(E) -> Any?): Listener = E::class.subscribeWhileNull(this, listener) // endregion - // region KClass 的扩展方法 (仅内部使用) @PublishedApi -internal suspend fun KClass.subscribe(bot: Bot, handler: suspend BotSession.(E) -> ListeningStatus) = +internal suspend fun KClass.subscribe(bot: Bot, handler: suspend Bot.(E) -> ListeningStatus) = this.subscribeInternal(HandlerWithSession(bot, handler)) @PublishedApi -internal suspend fun KClass.subscribeAlways(bot: Bot, listener: suspend BotSession.(E) -> Unit) = +internal suspend fun KClass.subscribeAlways(bot: Bot, listener: suspend Bot.(E) -> Unit) = this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.LISTENING }) @PublishedApi -internal suspend fun KClass.subscribeOnce(bot: Bot, listener: suspend BotSession.(E) -> Unit) = +internal suspend fun KClass.subscribeOnce(bot: Bot, listener: suspend Bot.(E) -> Unit) = this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.STOPPED }) @PublishedApi -internal suspend fun KClass.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend BotSession.(E) -> T) = +internal suspend fun KClass.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend Bot.(E) -> T) = subscribeInternal(HandlerWithSession(bot) { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) @PublishedApi -internal suspend inline fun KClass.subscribeUntilFalse(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = +internal suspend inline fun KClass.subscribeUntilFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = subscribeUntil(bot, false, listener) @PublishedApi -internal suspend inline fun KClass.subscribeUntilTrue(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = +internal suspend inline fun KClass.subscribeUntilTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = subscribeUntil(bot, true, listener) @PublishedApi -internal suspend inline fun KClass.subscribeUntilNull(bot: Bot, noinline listener: suspend BotSession.(E) -> Any?) = +internal suspend inline fun KClass.subscribeUntilNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) = subscribeUntil(bot, null, listener) @PublishedApi -internal suspend fun KClass.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend BotSession.(E) -> T) = +internal suspend fun KClass.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend Bot.(E) -> T) = subscribeInternal(HandlerWithSession(bot) { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) @PublishedApi -internal suspend inline fun KClass.subscribeWhileFalse(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = +internal suspend inline fun KClass.subscribeWhileFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = subscribeWhile(bot, false, listener) @PublishedApi -internal suspend inline fun KClass.subscribeWhileTrue(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = +internal suspend inline fun KClass.subscribeWhileTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) = subscribeWhile(bot, true, listener) @PublishedApi -internal suspend inline fun KClass.subscribeWhileNull(bot: Bot, noinline listener: suspend BotSession.(E) -> Any?) = +internal suspend inline fun KClass.subscribeWhileNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) = subscribeWhile(bot, null, listener) // endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ConnectionOccupiedEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ConnectionOccupiedEvent.kt new file mode 100644 index 000000000..73a4a0c16 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ConnectionOccupiedEvent.kt @@ -0,0 +1,10 @@ +package net.mamoe.mirai.event.events + +import net.mamoe.mirai.network.data.EventPacket + +/** + * 被挤下线. 只能获取到中文的消息 + */ +inline class ConnectionOccupiedEvent(val message: String) : EventPacket { + override fun toString(): String = "ConnectionOccupiedEvent(${message.replace("\n", "")})" +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendStatusChanged.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendStatusChanged.kt new file mode 100644 index 000000000..22de35d54 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendStatusChanged.kt @@ -0,0 +1,10 @@ +package net.mamoe.mirai.event.events + +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.utils.OnlineStatus + +data class FriendStatusChanged( + val qq: QQ, + val status: OnlineStatus +) : EventPacket diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/MuteEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/MuteEvent.kt new file mode 100644 index 000000000..bba468b87 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/MuteEvent.kt @@ -0,0 +1,75 @@ +package net.mamoe.mirai.event.events + +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.network.data.EventPacket + + +// region mute +/** + * 某群成员被禁言事件 + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +class MemberMuteEvent( + val member: Member, + override val durationSeconds: Int, + override val operator: Member +) : MuteEvent() { + override val group: Group get() = operator.group + override fun toString(): String = "MemberMuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s" +} + +/** + * 机器人被禁言事件 + */ +class BeingMutedEvent( + override val durationSeconds: Int, + override val operator: Member +) : MuteEvent() { + override val group: Group get() = operator.group + override fun toString(): String = "BeingMutedEvent(group=${group.id}, operator=${operator.id}, duration=${durationSeconds}s" +} + +sealed class MuteEvent : EventOfMute() { + abstract override val operator: Member + abstract override val group: Group + abstract val durationSeconds: Int +} +// endregion + +// region unmute +/** + * 某群成员被解除禁言事件 + */ +@Suppress("unused") +class MemberUnmuteEvent( + val member: Member, + override val operator: Member +) : UnmuteEvent() { + override val group: Group get() = operator.group + override fun toString(): String = "MemberUnmuteEvent(member=${member.id}, group=${group.id}, operator=${operator.id}" +} + +/** + * 机器人被解除禁言事件 + */ +@Suppress("SpellCheckingInspection") +class BeingUnmutedEvent( + override val operator: Member +) : UnmuteEvent() { + override val group: Group get() = operator.group + override fun toString(): String = "BeingUnmutedEvent(group=${group.id}, operator=${operator.id}" +} + +sealed class UnmuteEvent : EventOfMute() { + abstract override val operator: Member + abstract override val group: Group +} + +// endregion + +abstract class EventOfMute : EventPacket { + abstract val operator: Member + abstract val group: Group +} + diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt deleted file mode 100644 index 7805b9704..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/PacketEvents.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.mamoe.mirai.event.events - -import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.Cancellable -import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.timpc.packet.Packet - -/* Abstract */ - -/** - * 数据包相关事件 - */ -internal sealed class PacketEvent

(bot: Bot, open val packet: P) : BotEvent(bot) - - -/* Client to Server */ - -/** - * 发送给服务器的数据包的相关事件 - */ -internal sealed class OutgoingPacketEvent(bot: Bot, packet: OutgoingPacket) : PacketEvent(bot, packet) - -/** - * 包已发送, 此时包数据已完全发送至服务器, 且包已被关闭. - * - * 不可被取消 - */ -internal class PacketSentEvent(bot: Bot, packet: OutgoingPacket) : OutgoingPacketEvent(bot, packet) - -/** - * 包发送前, 此时包数据已经编码完成. - * - * 可被取消 - */ -internal class BeforePacketSendEvent(bot: Bot, packet: OutgoingPacket) : OutgoingPacketEvent(bot, packet), Cancellable - - -/* Server to Client */ - -/** - * 来自服务器的数据包的相关事件 - */ -internal sealed class ServerPacketEvent

(bot: Bot, packet: P) : PacketEvent

(bot, packet) - -/** - * 服务器数据包接收事件. 此时包已经解密完成. - */ -internal class ServerPacketReceivedEvent

(bot: Bot, packet: P) : ServerPacketEvent

(bot, packet), Cancellable \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ReceiveFriendAddRequestEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ReceiveFriendAddRequestEvent.kt new file mode 100644 index 000000000..20f1ae813 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/ReceiveFriendAddRequestEvent.kt @@ -0,0 +1,27 @@ +package net.mamoe.mirai.event.events + +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.utils.unsafeWeakRef +import kotlin.jvm.JvmOverloads + +/** + * 陌生人请求添加机器人账号为好友 + */ +class ReceiveFriendAddRequestEvent( + _qq: QQ, + /** + * 验证消息 + */ + val message: String +) : EventPacket { + val qq: QQ by _qq.unsafeWeakRef() + + /** + * 同意这个请求 + * + * @param remark 备注名, 不设置则需为 `null` + */ + @JvmOverloads // TODO: 2019/12/17 协议抽象 + suspend fun approve(remark: String? = null): Unit = qq.bot.approveFriendAddRequest(qq.id, remark) +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index 4f21b7baf..c34648d73 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -1,17 +1,17 @@ package net.mamoe.mirai.event.internal -import kotlinx.coroutines.* +import kotlinx.coroutines.CompletableJob +import kotlinx.coroutines.Job import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.EventDebugLogger import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.events.BotEvent -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.session -import net.mamoe.mirai.utils.internal.inlinedRemoveIf +import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.io.logStacktrace +import net.mamoe.mirai.utils.unsafeWeakRef import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.jvm.JvmField @@ -24,8 +24,6 @@ import kotlin.reflect.KFunction */ var EventDisabled = false -// TODO: 2019/11/29 修改监听为 lock-free 模式 - /** * 监听和广播实现. * 它会首先检查这个事件是否正在被广播 @@ -34,38 +32,9 @@ var EventDisabled = false * * @author Him188moe */ // inline to avoid a Continuation creation -internal suspend inline fun , E : Subscribable> KClass.subscribeInternal(listener: L): L = with(this.listeners()) { - if (mainMutex.tryLock(listener)) {//能锁则代表这个事件目前没有正在广播. - try { - add(listener)//直接修改主监听者列表 - EventDebugLogger.debug("Added a listener to ${this@subscribeInternal.simpleName}") - } finally { - mainMutex.unlock(listener) - } - return listener - } - - //不能锁住, 则这个事件正在广播, 那么要将新的监听者放入缓存 - cacheMutex.withLock { - cache.add(listener) - EventDebugLogger.debug("Added a listener to cache of ${this@subscribeInternal.simpleName}") - } - - GlobalScope.launch { - //启动协程并等待正在进行的广播结束, 然后将缓存转移到主监听者列表 - //启动后的协程马上就会因为锁而被挂起 - mainMutex.withLock(listener) { - cacheMutex.withLock { - if (cache.size != 0) { - addAll(cache) - cache.clear() - EventDebugLogger.debug("Cache of ${this@subscribeInternal.simpleName} is now transferred to main") - } - } - } - } - - return@with listener +internal suspend inline fun , E : Subscribable> KClass.subscribeInternal(listener: L): L { + this.listeners().addLast(listener) + return listener } /** @@ -77,8 +46,6 @@ internal suspend inline fun , E : Subscribable> KClass.subscr * @author Him188moe */ sealed class Listener : CompletableJob { - @JvmField - internal val lock = Mutex() abstract suspend fun onEvent(event: E): ListeningStatus } @@ -113,7 +80,7 @@ internal class Handler @Suppress("FunctionName") internal suspend inline fun HandlerWithSession( bot: Bot, - noinline handler: suspend BotSession.(E) -> ListeningStatus + noinline handler: suspend Bot.(E) -> ListeningStatus ): HandlerWithSession { return HandlerWithSession(bot, coroutineContext[Job], coroutineContext, handler) } @@ -125,10 +92,10 @@ internal suspend inline fun HandlerWithSession( */ @PublishedApi internal class HandlerWithSession @PublishedApi internal constructor( - @JvmField val bot: Bot, - parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend BotSession.(E) -> ListeningStatus -) : - Listener(), CompletableJob by Job(parentJob) { + bot: Bot, + parentJob: Job?, private val context: CoroutineContext, @JvmField val handler: suspend Bot.(E) -> ListeningStatus +) : Listener(), CompletableJob by Job(parentJob) { + val bot: Bot by bot.unsafeWeakRef() override suspend fun onEvent(event: E): ListeningStatus { if (isCompleted || isCancelled) return ListeningStatus.STOPPED @@ -137,10 +104,11 @@ internal class HandlerWithSession @PublishedApi internal const if (event !is BotEvent || event.bot !== bot) return ListeningStatus.LISTENING return try { - withContext(context) { bot.session.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() } + withContext(context) { bot.handler(event) }.also { if (it == ListeningStatus.STOPPED) complete() } } catch (e: Throwable) { e.logStacktrace() //completeExceptionally(e) + complete() ListeningStatus.STOPPED } } @@ -151,24 +119,7 @@ internal class HandlerWithSession @PublishedApi internal const */ internal suspend fun KClass.listeners(): EventListeners = EventListenerManger.get(this) -internal class EventListeners : MutableList> by mutableListOf() { - /** - * 主监听者列表. - * 广播事件时使用这个锁. - */ - @JvmField - val mainMutex = Mutex() - /** - * 缓存(监听)事件时使用的锁 - */ - @JvmField - val cacheMutex = Mutex() - /** - * 等待加入到主 list 的监听者. 务必使用 [cacheMutex] - */ - @JvmField - val cache: MutableList> = mutableListOf() - +internal class EventListeners : LockFreeLinkedList>() { init { this::class.members.filterIsInstance>().forEach { if (it.name == "add") { @@ -198,51 +149,26 @@ internal object EventListenerManger { } -internal suspend fun E.broadcastInternal(): E { +// inline: NO extra Continuation +internal suspend inline fun E.broadcastInternal(): E { if (EventDisabled) return this - callListeners(this::class.listeners()) + callAndRemoveIfRequired(this::class.listeners()) - applySuperListeners(this::class) { callListeners(it) } + this::class.supertypes.forEach { superType -> + if (Subscribable::class.isInstance(superType)) { + // the super type is a child of Subscribable, then we can cast. + @Suppress("UNCHECKED_CAST") + callAndRemoveIfRequired((superType.classifier as KClass).listeners()) + } + } return this } -private suspend inline fun E.callListeners(listeners: EventListeners) { - //自己持有, 则是在一个事件中 - if (listeners.mainMutex.holdsLock(listeners)) { - callAndRemoveIfRequired(listeners) - } else { - while (!listeners.mainMutex.tryLock(listeners)) { - delay(10) - } - try { - callAndRemoveIfRequired(listeners) - } finally { - listeners.mainMutex.unlock(listeners) +private suspend inline fun E.callAndRemoveIfRequired(listeners: EventListeners) { + listeners.forEach { + if (it.onEvent(this) == ListeningStatus.STOPPED) { + listeners.remove(it) // atomic remove } } -} - -private suspend inline fun E.callAndRemoveIfRequired(listeners: EventListeners) = listeners.inlinedRemoveIf { - if (it.lock.tryLock()) { - try { - it.onEvent(this) == ListeningStatus.STOPPED - } finally { - it.lock.unlock() - } - } else false -} - -/** - * apply [block] to all the [EventListeners] in [clazz]'s superclasses - */ -private tailrec suspend fun applySuperListeners( - clazz: KClass, - block: suspend (EventListeners) -> Unit -) { - val superEventClass = - clazz.supertypes.map { it.classifier }.filterIsInstance>().firstOrNull() ?: return - @Suppress("UNCHECKED_CAST") - block(superEventClass.listeners() as EventListeners) - applySuperListeners(superEventClass, block) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Face.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Face.kt deleted file mode 100644 index e570518e9..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Face.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.mamoe.mirai.message - -/** - * QQ 自带表情 - */ -inline class Face(val id: FaceId) : Message { - override val stringValue: String get() = "[face${id.value}]" - override fun toString(): String = stringValue - - companion object Key : Message.Key -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt new file mode 100644 index 000000000..43717e526 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt @@ -0,0 +1,26 @@ +package net.mamoe.mirai.message + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.event.BroadcastControllable +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.utils.MiraiInternalAPI + +class FriendMessage( + bot: Bot, + /** + * 是否是在这次登录之前的消息, 即消息记录 + */ + val previous: Boolean, + override val sender: QQ, + override val message: MessageChain +) : MessagePacket(bot), BroadcastControllable { + /** + * 是否应被自动广播. 此为内部 API + */ + @MiraiInternalAPI + override val shouldBroadcast: Boolean + get() = !previous + + override val subject: QQ get() = sender +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt new file mode 100644 index 000000000..193f39a7a --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt @@ -0,0 +1,35 @@ +package net.mamoe.mirai.message + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.message.data.At +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.utils.unsafeWeakRef + +@Suppress("unused", "NOTHING_TO_INLINE") +class GroupMessage( + bot: Bot, + group: Group, + val senderName: String, + /** + * 发送方权限. + */ + val permission: MemberPermission, + sender: Member, + override val message: MessageChain +) : MessagePacket(bot) { + val group: Group by group.unsafeWeakRef() + override val sender: Member by sender.unsafeWeakRef() + + /* + 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) + inline fun Long.member(): Member = group.getMember(this) + override fun toString(): String = + "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt new file mode 100644 index 000000000..e4ec5c9a2 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt @@ -0,0 +1,91 @@ +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.message + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.event.events.BotEvent +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.network.data.EventPacket +import net.mamoe.mirai.network.data.ImageLink +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail +import kotlin.jvm.JvmName + +/** + * 平台相关扩展 + */ +@UseExperimental(MiraiInternalAPI::class) +expect abstract class MessagePacket(bot: Bot) : MessagePacketBase + +@MiraiInternalAPI +abstract class MessagePacketBase(_bot: Bot) : EventPacket, BotEvent() { + override val bot: Bot by _bot.unsafeWeakRef() + + /** + * 消息事件主体. + * + * 对于好友消息, 这个属性为 [QQ] 的实例; + * 对于群消息, 这个属性为 [Group] 的实例 + * + * 在回复消息时, 可通过 [subject] 作为回复对象 + */ + abstract val subject: TSubject + + /** + * 发送人 + */ + abstract val sender: TSender + + abstract val message: MessageChain + + + // region Send to subject + + /** + * 给这个消息事件的主体发送消息 + * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 + * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 + */ + suspend inline fun reply(message: MessageChain) = subject.sendMessage(message) + + suspend inline fun reply(message: Message) = subject.sendMessage(message.chain()) + suspend inline fun reply(plain: String) = subject.sendMessage(plain.toMessage()) + + @JvmName("reply1") + suspend inline fun String.reply() = reply(this) + + @JvmName("reply1") + suspend inline fun Message.reply() = reply(this) + + @JvmName("reply1") + suspend inline fun MessageChain.reply() = reply(this) + + suspend inline fun ExternalImage.send() = this.sendTo(subject) + + suspend inline fun ExternalImage.upload(): Image = this.upload(subject) + suspend inline fun Image.send() = this.sendTo(subject) + suspend inline fun ImageId.send() = this.sendTo(subject) + suspend inline fun Message.send() = this.sendTo(subject) + suspend inline fun String.send() = this.toMessage().sendTo(subject) + + // endregion + + // region Image download + suspend inline fun Image.getLink(): ImageLink = with(bot) { getLink() } + + suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray() + suspend inline fun Image.download(): ByteReadPacket = getLink().download() + // endregion + + fun At.qq(): QQ = bot.getQQ(this.target) + + fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toLong()) + fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) + + suspend inline fun Int.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0).toLong()) + suspend inline fun Long.group(): Group = bot.getGroup(this.coerceAtLeastOrFail(0)) + suspend inline fun GroupId.group(): Group = bot.getGroup(this) + suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/At.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt similarity index 85% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/At.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt index f554cefd4..c0492697e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/At.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt @@ -1,6 +1,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.QQ @@ -8,7 +8,7 @@ import net.mamoe.mirai.contact.QQ /** * At 一个人. 只能发送给一个群. */ -inline class At(val target: UInt) : Message { +inline class At(val target: Long) : Message { constructor(target: QQ) : this(target.id) override val stringValue: String get() = "[@$target]" // TODO: 2019/11/25 使用群名称进行 at. 因为手机端只会显示这个文字 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FaceID.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt similarity index 95% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FaceID.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt index b9da172da..1096494d5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FaceID.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt @@ -1,14 +1,23 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data import kotlin.jvm.JvmStatic +/** + * QQ 自带表情 + */ +inline class Face(val id: FaceId) : Message { + override val stringValue: String get() = "[face${id.value}]" + override fun toString(): String = stringValue + + companion object Key : Message.Key +} + /** * @author LamGC */ @Suppress("SpellCheckingInspection", "unused") -inline class FaceId(inline val value: UByte) { +@UseExperimental(ExperimentalUnsignedTypes::class) +inline class FaceId constructor(inline val value: UByte) { companion object { @JvmStatic val unknown: FaceId = FaceId(0xffu) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Image.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index 9301db3c0..dc1756267 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -1,10 +1,9 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.sendMessage -import net.mamoe.mirai.network.protocol.timpc.packet.action.FriendImagePacket import net.mamoe.mirai.utils.ExternalImage @@ -30,7 +29,8 @@ inline class ImageId0x06(override inline val value: String) : ImageId { /** * 一般是群的图片的 id. */ -class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : ImageId { +class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : + ImageId { override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)" val md5: ByteArray @@ -46,7 +46,8 @@ class ImageId0x03 constructor(override inline val value: String, inline val uniq inline fun ImageId(value: String): ImageId = ImageId0x06(value) @Suppress("FunctionName", "NOTHING_TO_INLINE") -inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId = ImageId0x03(value, uniqueId, height, width) +inline fun ImageId(value: String, uniqueId: UInt, height: Int, width: Int): ImageId = + ImageId0x03(value, uniqueId, height, width) /** @@ -65,6 +66,7 @@ fun ImageId.checkLength() = check(value.length == 37 || value.length == 42) { "I fun ImageId.requireLength() = require(value.length == 37 || value.length == 42) { "Illegal ImageId length" } @Suppress("NOTHING_TO_INLINE") -inline fun ImageId.image(): Image = Image(this) +inline fun ImageId.image(): Image = + Image(this) suspend inline fun ImageId.sendTo(contact: Contact) = contact.sendMessage(this.image()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt similarity index 98% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt index e66d8e50a..39a508534 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt @@ -1,6 +1,6 @@ @file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.sendMessage diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt similarity index 88% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageChain.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 4402f1dc8..87f20e6c9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -1,5 +1,6 @@ -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data +import net.mamoe.mirai.message.data.NullMessageChain.toString import net.mamoe.mirai.utils.MiraiExperimentalAPI import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -75,7 +76,8 @@ fun MessageChain(vararg messages: Message): MessageChain = * 构造 [MessageChain] */ @Suppress("FunctionName") -fun MessageChain(messages: Iterable): MessageChain = MessageChainImpl(messages.toMutableList()) +fun MessageChain(messages: Iterable): MessageChain = + MessageChainImpl(messages.toMutableList()) /** * 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain] @@ -106,13 +108,16 @@ fun SingleMessageChain(delegate: Message): MessageChain { * 否则将调用 [MessageChain] 构造一个 [MessageChainImpl] */ @Suppress("NOTHING_TO_INLINE") -inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain(this) +inline fun Message.chain(): MessageChain = if (this is MessageChain) this else MessageChain( + this +) /** * 构造 [MessageChain] */ @Suppress("unused", "NOTHING_TO_INLINE") -inline fun List.messageChain(): MessageChain = MessageChain(this) +inline fun List.messageChain(): MessageChain = + MessageChain(this) /** @@ -230,51 +235,45 @@ class EmptyMessageChain : MessageChain { * Null 的 [MessageChain]. * 它不包含任何元素, 也没有创建任何 list. * - * - 所有 get 方法均抛出 [IndexOutOfBoundsException] - * - 所有 add 方法均抛出 [UnsupportedOperationException] - * - 其他判断类方法均 false 或 -1 + * 除 [toString] 外, 其他方法均 [error] */ object NullMessageChain : MessageChain { - override fun subList(fromIndex: Int, toIndex: Int): MutableList = unsupported() + override fun subList(fromIndex: Int, toIndex: Int): MutableList = error("accessing NullMessageChain") - override val stringValue: String - get() = "" + override val stringValue: String get() = "null" - override fun toString(): String = stringValue + override fun toString(): String = "null" - override fun contains(sub: String): Boolean = false - override fun contains(element: Message): Boolean = false - override fun followedBy(tail: Message): MessageChain = - MessageChainImpl(tail) + override fun contains(sub: String): Boolean = error("accessing NullMessageChain") + override fun contains(element: Message): Boolean = error("accessing NullMessageChain") + override fun followedBy(tail: Message): MessageChain = error("accessing NullMessageChain") - override val size: Int = 0 - override fun containsAll(elements: Collection): Boolean = false - override fun get(index: Int): Message = throw IndexOutOfBoundsException() - override fun indexOf(element: Message): Int = -1 - override fun isEmpty(): Boolean = true - override fun iterator(): MutableIterator = - EmptyMutableIterator() + override val size: Int get() = error("accessing NullMessageChain") + override fun containsAll(elements: Collection): Boolean = error("accessing NullMessageChain") + override fun get(index: Int): Message = error("accessing NullMessageChain") + override fun indexOf(element: Message): Int = error("accessing NullMessageChain") + override fun isEmpty(): Boolean = error("accessing NullMessageChain") + override fun iterator(): MutableIterator = error("accessing NullMessageChain") - override fun lastIndexOf(element: Message): Int = -1 - override fun add(element: Message): Boolean = unsupported() - override fun add(index: Int, element: Message) = throw IndexOutOfBoundsException(index.toString()) - override fun addAll(index: Int, elements: Collection): Boolean = - throw IndexOutOfBoundsException(index.toString()) + override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain") + override fun add(element: Message): Boolean = error("accessing NullMessageChain") + override fun add(index: Int, element: Message) = error("accessing NullMessageChain") + override fun addAll(index: Int, elements: Collection): Boolean = error("accessing NullMessageChain") - override fun addAll(elements: Collection): Boolean = unsupported() - override fun clear() {} - override fun listIterator(): MutableListIterator = - EmptyMutableListIterator() + override fun addAll(elements: Collection): Boolean = error("accessing NullMessageChain") + override fun clear() { + error("accessing NullMessageChain") + } - override fun listIterator(index: Int): MutableListIterator = - throw IndexOutOfBoundsException(index.toString()) + override fun listIterator(): MutableListIterator = error("accessing NullMessageChain") - override fun remove(element: Message): Boolean = false - override fun removeAll(elements: Collection): Boolean = false - override fun removeAt(index: Int): Message = throw IndexOutOfBoundsException(index.toString()) - override fun retainAll(elements: Collection): Boolean = false - override fun set(index: Int, element: Message): Message = throw IndexOutOfBoundsException(index.toString()) - private fun unsupported(): Nothing = throw UnsupportedOperationException() + override fun listIterator(index: Int): MutableListIterator = error("accessing NullMessageChain") + + override fun remove(element: Message): Boolean = error("accessing NullMessageChain") + override fun removeAll(elements: Collection): Boolean = error("accessing NullMessageChain") + override fun removeAt(index: Int): Message = error("accessing NullMessageChain") + override fun retainAll(elements: Collection): Boolean = error("accessing NullMessageChain") + override fun set(index: Int, element: Message): Message = error("accessing NullMessageChain") } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt similarity index 94% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/PlainText.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index 5e41b47d0..f591994a4 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -1,4 +1,4 @@ -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data inline class PlainText(override val stringValue: String) : Message { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/XML.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt similarity index 94% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/XML.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt index 6a7c6ffea..e86afdd81 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/XML.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt @@ -1,13 +1,14 @@ @file:Suppress("MemberVisibilityCanBePrivate") -package net.mamoe.mirai.message +package net.mamoe.mirai.message.data /** * XML 消息, 如分享, 卡片等. * * @see buildXMLMessage */ -inline class XMLMessage(override val stringValue: String) : Message, SingleOnly { +inline class XMLMessage(override val stringValue: String) : Message, + SingleOnly { override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed") override fun toString(): String = stringValue } @@ -16,7 +17,8 @@ inline class XMLMessage(override val stringValue: String) : Message, SingleOnly * 构造一条 XML 消息 */ @XMLDsl -inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = XMLMessage(XMLMessageBuilder().apply(block).text) +inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = + XMLMessage(XMLMessageBuilder().apply(block).text) @Suppress("NOTHING_TO_INLINE") @XMLDsl diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt index ca094f434..8ec4f5104 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/internal/MessageDataInternal.kt @@ -3,7 +3,8 @@ package net.mamoe.mirai.message.internal import kotlinx.io.core.* -import net.mamoe.mirai.message.* +import net.mamoe.mirai.message.MessageType +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.unzip @@ -27,7 +28,7 @@ internal fun IoBuffer.parsePlainTextOrAt(): Message { PlainText(msg) } else { discardExact(10) - At(readUInt()) + At(readQQ()) } } @@ -38,13 +39,13 @@ internal fun IoBuffer.parseLongText0x19(): PlainText { //AA 02 33 50 00 60 00 68 00 9A 01 2A 08 09 20 CB 50 78 80 80 04 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 D3 02 A0 03 10 B0 03 00 C0 03 AF 9C 01 D0 03 00 E8 03 00 //AA 02 30 50 00 60 00 68 00 9A 01 27 08 0A 78 A7 C0 04 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00 // 应该是手机发送时的字体或气泡之类的 - // println("parseLongText0x19.raw=${raw.toUHexString()}") + // println("parseLongText0x19.raw=${raw.toHexString()}") return PlainText("") } internal fun IoBuffer.parseMessageImage0x06(): Image { discardExact(1) - //MiraiLogger.debug(this.toUHexString()) + //MiraiLogger.debug(this.toHexString()) val filenameLength = readShort() discardExact(filenameLength.toInt()) @@ -199,7 +200,7 @@ internal fun ByteReadPacket.readMessage(): Message? { } } -internal fun ByteReadPacket.readMessageChain(): MessageChain { +fun ByteReadPacket.readMessageChain(): MessageChain { val chain = MessageChain() do { if (this.remaining == 0L) { @@ -209,7 +210,7 @@ internal fun ByteReadPacket.readMessageChain(): MessageChain { return chain } -internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket { +fun MessageChain.toPacket(): ByteReadPacket = buildPacket { this@toPacket.forEach { message -> writePacket(with(message) { when (this) { @@ -244,7 +245,7 @@ internal fun MessageChain.toPacket(): ByteReadPacket = buildPacket { writeShortLVString(stringValue) // 这个应该是 "@群名", 手机上面会显示这个消息, 电脑会显示下面那个 // 06 00 0D 00 01 00 00 00 08 00 76 E4 B8 DD 00 00 writeHex("06 00 0D 00 01 00 00 00 08 00") - writeUInt(target) + writeQQ(target) writeZero(2) } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt index cd2eb851d..0eece10df 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt @@ -1,17 +1,11 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package net.mamoe.mirai.network import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter -import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler -import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.timpc.packet.Packet -import net.mamoe.mirai.network.protocol.timpc.packet.login.HeartbeatPacket -import net.mamoe.mirai.network.protocol.timpc.packet.login.RequestSKeyPacket -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult -import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.PlatformDatagramChannel /** @@ -33,29 +27,41 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel * A BotNetworkHandler is used to connect with Tencent servers. */ @Suppress("PropertyName") -interface BotNetworkHandler : CoroutineScope { - val socket: Socket - val bot: Bot +abstract class BotNetworkHandler : CoroutineScope { + abstract val bot: Bot - val supervisor: CompletableJob - - val session: BotSession + abstract val supervisor: CompletableJob /** * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果 * 本函数将挂起直到登录成功. */ - suspend fun login(): LoginResult + abstract suspend fun login() /** * 等待直到与服务器断开连接. 若未连接则立即返回 */ - suspend fun awaitDisconnection() + abstract suspend fun awaitDisconnection() /** * 关闭网络接口, 停止所有有关协程和任务 */ - fun close(cause: Throwable? = null) { + open fun close(cause: Throwable? = null) { supervisor.cancel(CancellationException("handler closed", cause)) } + +/* + @PublishedApi + internal abstract fun CoroutineScope.QQ(bot: Bot, id: Long, coroutineContext: CoroutineContext): QQ + + @PublishedApi + internal abstract fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group + + @PublishedApi + internal abstract fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member + + + */ + + } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt deleted file mode 100644 index 0a23d3c92..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotSession.kt +++ /dev/null @@ -1,200 +0,0 @@ -@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "NOTHING_TO_INLINE") - -package net.mamoe.mirai.network - -import kotlinx.coroutines.* -import kotlinx.io.core.ByteReadPacket -import net.mamoe.mirai.* -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.GroupId -import net.mamoe.mirai.contact.GroupInternalId -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.message.Image -import net.mamoe.mirai.message.ImageId0x03 -import net.mamoe.mirai.message.ImageId0x06 -import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler -import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter -import net.mamoe.mirai.network.protocol.timpc.handler.TemporaryPacketHandler -import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.timpc.packet.Packet -import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey -import net.mamoe.mirai.network.protocol.timpc.packet.action.* -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.assertUnreachable -import net.mamoe.mirai.utils.getGTK -import net.mamoe.mirai.utils.internal.PositiveNumbers -import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail -import net.mamoe.mirai.utils.secondsToMillis -import kotlin.coroutines.coroutineContext - -/** - * 构造 [BotSession] 的捷径 - */ -@Suppress("FunctionName", "NOTHING_TO_INLINE") -internal inline fun TIMBotNetworkHandler.BotSession(sessionKey: SessionKey): BotSession = BotSession(bot, sessionKey) - -/** - * 登录会话. 当登录完成后, 客户端会拿到 sessionKey. - * 此时建立 session, 然后开始处理事务. - * - * 本类中含有各平台相关扩展函数等. - * - * @author Him188moe - */ -@UseExperimental(MiraiInternalAPI::class) -expect class BotSession internal constructor( - bot: Bot, - sessionKey: SessionKey -) : BotSessionBase - -/** - * [BotSession] 平台通用基础 - */ -@MiraiInternalAPI -// cannot be internal because of `public BotSession` -abstract class BotSessionBase internal constructor( - val bot: Bot, - internal val sessionKey: SessionKey -) { - val socket: DataPacketSocketAdapter get() = bot.network.socket - val NetworkScope: CoroutineScope get() = bot.network - - /** - * Web api 使用 - */ - val cookies: String get() = _cookies - - /** - * Web api 使用 - */ - val sKey: String get() = _sKey - - /** - * Web api 使用 - */ - val gtk: Int get() = _gtk - - 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)) - suspend inline fun UInt.group(): Group = bot.getGroup(GroupId(this)) - suspend inline fun GroupId.group(): Group = bot.getGroup(this) - suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this) - - suspend fun Image.getLink(): ImageLink = when (this.id) { - is ImageId0x06 -> FriendImagePacket.RequestImageLink(bot.qqAccount, bot.sessionKey, id).sendAndExpect() - is ImageId0x03 -> GroupImagePacket.RequestImageLink(bot.qqAccount, bot.sessionKey, id).sendAndExpect().requireSuccess() - else -> assertUnreachable() - } - - suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray() - suspend inline fun Image.download(): ByteReadPacket = getLink().download() - - - - - // region internal - - @Suppress("PropertyName") - internal var _sKey: String = "" - set(value) { - field = value - _gtk = getGTK(value) - } - @Suppress("PropertyName") - internal lateinit var _cookies: String - private var _gtk: Int = 0 - - /** - * 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P]. - * 这个方法会立即发出这个数据包然后返回一个 [CompletableDeferred]. - * - * 实现方法: - * ```kotlin - * with(session){ - * ClientPacketXXX(...).sendAndExpectAsync { - * //it: ServerPacketXXX - * } - * } - * ``` - * @sample net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage - * - * @param checkSequence 是否筛选 `sequenceId`, 即是否筛选发出的包对应的返回包. - * @param P 期待的包 - * @param handler 处理期待的包. **将会在调用本函数的 [coroutineContext] 下执行.** - * - * @see Bot.withSession 转换 receiver, 即 `this` 的指向, 为 [BotSession] - */ - internal suspend inline fun OutgoingPacket.sendAndExpectAsync( - checkSequence: Boolean = true, - noinline handler: suspend (P) -> R - ): Deferred { - val deferred: CompletableDeferred = CompletableDeferred(coroutineContext[Job]) - (bot.network as TIMBotNetworkHandler) - .addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSessionBase as BotSession, checkSequence, coroutineContext + deferred).also { - it.toSend(this) - it.onExpect(handler) - }) - return deferred - } - - internal suspend inline fun OutgoingPacket.sendAndExpectAsync(checkSequence: Boolean = true): Deferred

= - sendAndExpectAsync(checkSequence) { it } - - internal suspend inline fun OutgoingPacket.sendAndExpect( - checkSequence: Boolean = true, - timeoutMillis: Long = 5.secondsToMillis, - crossinline mapper: (P) -> R - ): R = withTimeout(timeoutMillis) { sendAndExpectAsync(checkSequence) { mapper(it) }.await() } - - internal suspend inline fun OutgoingPacket.sendAndExpect( - checkSequence: Boolean = true, - timeoutMillist: Long = 5.secondsToMillis - ): P = withTimeout(timeoutMillist) { sendAndExpectAsync(checkSequence) { it }.await() } - - internal suspend inline fun OutgoingPacket.send() = - (socket as TIMBotNetworkHandler.BotSocketAdapter).sendPacket(this) - - // endregion -} - - -inline val BotSession.isOpen: Boolean get() = socket.isOpen -inline val BotSession.qqAccount: UInt get() = bot.account.id // 为了与群和好友的 id 区别. - -/** - * 取得 [BotNetworkHandler] 的 sessionKey. - * 实际上是一个捷径. - */ -internal inline val BotNetworkHandler<*>.sessionKey: SessionKey get() = this.session.sessionKey - -/** - * 取得 [Bot] 的 [BotSession]. - * 实际上是一个捷径. - */ -inline val Bot.session: BotSession get() = this.network.session - -/** - * 取得 [Bot] 的 `sessionKey`. - * 实际上是一个捷径. - */ -internal inline val Bot.sessionKey: SessionKey get() = this.session.sessionKey - - -/** - * 发送数据包 - * @throws IllegalStateException 当 [BotNetworkHandler.socket] 未开启时 - */ -internal suspend inline fun BotSession.sendPacket(packet: OutgoingPacket) = this.bot.sendPacket(packet) - - -inline fun BotSession.getQQ(@PositiveNumbers number: Long): QQ = this.bot.getQQ(number) -inline fun BotSession.getQQ(number: UInt): QQ = this.bot.getQQ(number) - -suspend inline fun BotSession.getGroup(id: UInt): Group = this.bot.getGroup(id) -suspend inline fun BotSession.getGroup(@PositiveNumbers id: Long): Group = this.bot.getGroup(id) -suspend inline fun BotSession.getGroup(id: GroupId): Group = this.bot.getGroup(id) -suspend inline fun BotSession.getGroup(internalId: GroupInternalId): Group = this.bot.getGroup(internalId) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/AddFriendResult.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/AddFriendResult.kt new file mode 100644 index 000000000..302b3390e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/AddFriendResult.kt @@ -0,0 +1,44 @@ +@file:JvmMultifileClass +@file:JvmName("BotHelperKt") +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.network.data + +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + + +@Suppress("ClassName") +sealed class AddFriendResult { + abstract class DONE internal constructor() : AddFriendResult() { + override fun toString(): String = "AddFriendResult(Done)" + } + + /** + * 对方拒绝添加好友 + */ + object REJECTED : AddFriendResult() { + override fun toString(): String = "AddFriendResult(Rejected)" + } + + /** + * 这个人已经是好友 + */ + object ALREADY_ADDED : DONE() { + override fun toString(): String = "AddFriendResult(AlreadyAdded)" + } + + /** + * 等待对方同意 + */ + object WAITING_FOR_APPROVE : DONE() { + override fun toString(): String = "AddFriendResult(WaitingForApprove)" + } + + /** + * 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果) + */ + object ADDED : DONE() { + override fun toString(): String = "AddFriendResult(Added)" + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/EventPacket.kt similarity index 55% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacket.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/EventPacket.kt index 3fdbd8c66..de92630c8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/EventPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/EventPacket.kt @@ -1,7 +1,6 @@ -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.network.data import net.mamoe.mirai.event.Subscribable -import net.mamoe.mirai.network.protocol.timpc.packet.Packet /** * 事件包. 可被监听. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/FriendNameRemark.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/FriendNameRemark.kt new file mode 100644 index 000000000..64bc4f9f7 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/FriendNameRemark.kt @@ -0,0 +1,6 @@ +package net.mamoe.mirai.network.data + +/** + * 给好友设置的备注 + */ +inline class FriendNameRemark(val value: String) : Packet diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/GroupInfo.kt new file mode 100644 index 000000000..da45a8152 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/GroupInfo.kt @@ -0,0 +1,27 @@ +package net.mamoe.mirai.network.data + +import net.mamoe.mirai.contact.ContactList +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import kotlin.jvm.JvmField + +/** + * 群资料 + */ +@Suppress("MemberVisibilityCanBePrivate") // 将来使用 +class GroupInfo( + @JvmField internal var _group: Group, + @JvmField internal var _owner: Member, + @JvmField internal var _name: String, + @JvmField internal var _announcement: String, + @JvmField internal var _members: ContactList +) { + val group: Group get() = _group + val owner: Member get() = _owner + val name: String get() = _name + val announcement: String get() = _announcement + val members: ContactList get() = _members + + override fun toString(): String = + "GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}" +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/ImageLink.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/ImageLink.kt new file mode 100644 index 000000000..55e24564a --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/ImageLink.kt @@ -0,0 +1,19 @@ +package net.mamoe.mirai.network.data + +import io.ktor.client.request.get +import io.ktor.util.KtorExperimentalAPI +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.readBytes +import net.mamoe.mirai.utils.Http + +interface ImageLink { + /** + * 原图 + */ + val original: String + + suspend fun downloadAsByteArray(): ByteArray = download().readBytes() + + @UseExperimental(KtorExperimentalAPI::class) + suspend fun download(): ByteReadPacket = Http.get(original) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/LoginResult.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/LoginResult.kt new file mode 100644 index 000000000..88e30b4d4 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/LoginResult.kt @@ -0,0 +1,65 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.network.data + +import net.mamoe.mirai.utils.BotConfiguration + +/** + * 登录失败的原因 + */ +enum class LoginResult(val id: Byte) { + /** + * 密码错误 + */ + WRONG_PASSWORD(1), + + /** + * 被冻结 + */ + BLOCKED(2), + + /** + * QQ 号码输入有误 + */ + UNKNOWN_QQ_NUMBER(3), + + /** + * 账号开启了设备锁. 暂不支持设备锁登录 + */ + DEVICE_LOCK(4), + + /** + * 账号被回收 + */ + TAKEN_BACK(5), + + /** + * 未知. 更换服务器或等几分钟再登录可能解决. + */ + UNKNOWN(6), + + /** + * 包数据错误 + */ + INTERNAL_ERROR(7), + + /** + * 超时 + */ + TIMEOUT(8), + + /** + * 网络不可用 + */ + NETWORK_UNAVAILABLE(9), + + /** + * 需要验证码且 [BotConfiguration.failOnCaptcha] 为 `true` + */ + CAPTCHA(10), + + /** + * 该号码长期未登录, 为了保证账号安全, 已被系统设置成保护状态, 请用手机 TIM 最新版本登录, 登录成功后即可自动解除保护模式 + */ // TIM的错误代码为 00020 + PROTECTED(11), +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/Packet.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/Packet.kt new file mode 100644 index 000000000..9d7d08b6d --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/Packet.kt @@ -0,0 +1,6 @@ +package net.mamoe.mirai.network.data + +/** + * 一个包的数据 (body) + */ +interface Packet diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/PreviousNameList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/PreviousNameList.kt new file mode 100644 index 000000000..637f93924 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/PreviousNameList.kt @@ -0,0 +1,14 @@ +package net.mamoe.mirai.network.data + +/** + * 曾用名列表 + * + * 曾用名可能是: + * - 昵称 + * - 共同群内的群名片 + */ +class PreviousNameList( + list: List +) : Packet, List by list { + override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ") +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/Profile.kt similarity index 96% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/Profile.kt index 7bbdb76d9..80d8d9303 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/data/Profile.kt @@ -1,6 +1,6 @@ @file:Suppress("EXPERIMENTAL_API_USAGE") -package net.mamoe.mirai.contact.data +package net.mamoe.mirai.network.data import io.ktor.util.date.GMTDate @@ -9,7 +9,7 @@ import io.ktor.util.date.GMTDate */ @Suppress("PropertyName") data class Profile( - val qq: UInt, + val qq: Long, val nickname: String, val englishName: String?, val chineseName: String?, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/TemporaryPacketHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/TemporaryPacketHandler.kt deleted file mode 100644 index 979bc0f33..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/handler/TemporaryPacketHandler.kt +++ /dev/null @@ -1,75 +0,0 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.network.protocol.timpc.handler - -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.withContext -import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.timpc.packet.OutgoingPacket -import net.mamoe.mirai.network.protocol.timpc.packet.Packet -import net.mamoe.mirai.network.sendPacket -import kotlin.coroutines.CoroutineContext -import kotlin.reflect.KClass - -/** - * 临时数据包处理器 - * ```kotlin - * session.addHandler{ - * toSend { TouchPacket() } - * onExpect {//it: ClientTouchResponsePacket - * //do sth. - * } - * } - * ``` - * - * @see BotSession.sendAndExpectAsync - */ -internal class TemporaryPacketHandler

( - private val expectationClass: KClass

, - private val deferred: CompletableDeferred, - private val fromSession: BotSession, - private val checkSequence: Boolean, - /** - * 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行 - */ - private val callerContext: CoroutineContext -) { - private lateinit var toSend: OutgoingPacket - - private lateinit var handler: suspend (P) -> R - - lateinit var session: BotSession//无需覆盖 - - - @Suppress("NOTHING_TO_INLINE") - inline fun toSend(packet: OutgoingPacket) { - this.toSend = packet - } - - @Suppress("NOTHING_TO_INLINE") - inline fun onExpect(noinline handler: suspend (P) -> R) { - this.handler = handler - } - - internal suspend inline fun send(session: BotSession) { - this.session = session - session.sendPacket(toSend) - } - - @Suppress("NOTHING_TO_INLINE") - 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 doReceiveCatchingExceptions(packet: Packet) { - @Suppress("UNCHECKED_CAST") - val ret = try { - withContext(callerContext) { - handler(packet as P) - } - } catch (e: Throwable) { - deferred.completeExceptionally(e) - return - } - deferred.complete(ret) - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MessageEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MessageEvent.kt deleted file mode 100644 index 2af8900bd..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MessageEvent.kt +++ /dev/null @@ -1,230 +0,0 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.network.protocol.timpc.packet.event - -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.String -import kotlinx.io.core.discardExact -import kotlinx.io.core.readUInt -import net.mamoe.mirai.Bot -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.message.* -import net.mamoe.mirai.message.internal.readMessageChain -import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion -import net.mamoe.mirai.network.protocol.timpc.packet.action.ImageLink -import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail -import net.mamoe.mirai.utils.io.printTLVMap -import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.readTLVMap -import net.mamoe.mirai.utils.io.readUShortLVByteArray -import net.mamoe.mirai.withSession -import kotlin.jvm.JvmName - -/** - * 平台相关扩展 - */ -@UseExperimental(MiraiInternalAPI::class) -expect abstract class MessagePacket() : MessagePacketBase - -@MiraiInternalAPI -abstract class MessagePacketBase : EventPacket, BotEvent() { - internal lateinit var botVar: Bot - - override val bot: Bot get() = botVar - - /** - * 消息事件主体. - * - * 对于好友消息, 这个属性为 [QQ] 的实例; - * 对于群消息, 这个属性为 [Group] 的实例 - * - * 在回复消息时, 可通过 [subject] 作为回复对象 - */ - abstract val subject: TSubject - - /** - * 发送人 - */ - abstract val sender: TSender - - abstract val message: MessageChain - - - // region Send to subject - - /** - * 给这个消息事件的主体发送消息 - * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 - * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 - */ - suspend inline fun reply(message: MessageChain) = subject.sendMessage(message) - - suspend inline fun reply(message: Message) = subject.sendMessage(message.chain()) - suspend inline fun reply(plain: String) = subject.sendMessage(plain.toMessage()) - - @JvmName("reply1") - suspend inline fun String.reply() = reply(this) - - @JvmName("reply1") - suspend inline fun Message.reply() = reply(this) - - @JvmName("reply1") - suspend inline fun MessageChain.reply() = reply(this) - - suspend inline fun ExternalImage.send() = this.sendTo(subject) - - suspend inline fun ExternalImage.upload(): Image = this.upload(subject) - suspend inline fun Image.send() = this.sendTo(subject) - suspend inline fun ImageId.send() = this.sendTo(subject) - suspend inline fun Message.send() = this.sendTo(subject) - suspend inline fun String.send() = this.toMessage().sendTo(subject) - - // endregion - - // region Image download - suspend inline fun Image.getLink(): ImageLink = bot.withSession { getLink() } - - suspend inline fun Image.downloadAsByteArray(): ByteArray = getLink().downloadAsByteArray() - suspend inline fun Image.download(): ByteReadPacket = getLink().download() - // endregion - - fun At.qq(): QQ = bot.getQQ(this.target) - - fun Int.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0).toUInt()) - fun Long.qq(): QQ = bot.getQQ(this.coerceAtLeastOrFail(0)) - 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)) - suspend inline fun UInt.group(): Group = bot.getGroup(GroupId(this)) - suspend inline fun GroupId.group(): Group = bot.getGroup(this) - suspend inline fun GroupInternalId.group(): Group = bot.getGroup(this) -} - -// region group message - -@Suppress("unused", "NOTHING_TO_INLINE") -data class GroupMessage( - val group: Group, - val senderName: String, - /** - * 发送方权限. - */ - val permission: MemberPermission, - override val sender: Member, - override val message: MessageChain -) : MessagePacket() { - - /* - 01 00 09 01 00 06 66 61 69 6C 65 64 19 00 45 01 00 42 AA 02 3F 08 06 50 02 60 00 68 00 88 01 00 9A 01 31 08 0A 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 02 B0 03 00 C0 03 00 D0 03 00 E8 03 02 8A 04 04 08 02 08 01 90 04 80 C8 10 0E 00 0E 01 00 04 00 00 08 E4 07 00 04 00 00 00 01 12 00 1E 02 00 09 E9 85 B1 E9 87 8E E6 98 9F 03 00 01 02 05 00 04 00 00 00 03 08 00 04 00 00 00 04 - */ - override val subject: Group get() = group - - inline fun At.member(): Member = group.getMember(this.target) - inline fun UInt.member(): Member = group.getMember(this) - inline fun Long.member(): Member = group.getMember(this.toUInt()) - override fun toString(): String = - "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" -} - -@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") -internal object GroupMessageEventParserAndHandler : KnownEventParserAndHandler(0x0052u) { - override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): GroupMessage { - discardExact(31) - val groupNumber = readUInt() - discardExact(1) - val qq = readUInt() - - discardExact(48) - readUShortLVByteArray() - discardExact(2)//2个0x00 - - //debugPrintIfFail { - val message = readMessageChain() - - var senderPermission: MemberPermission = MemberPermission.MEMBER - var senderName = "" - val map = readTLVMap(true) - if (map.containsKey(18u)) { - map.getValue(18u).read { - val tlv = readTLVMap(true) - senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) { - null -> MemberPermission.MEMBER - 0x08u -> MemberPermission.OWNER - 0x10u -> MemberPermission.ADMINISTRATOR - else -> { - tlv.printTLVMap("TLV(tag=18) Map") - MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used") - MemberPermission.MEMBER - } - } - - senderName = when { - tlv.containsKey(0x01u) -> String(tlv.getValue(0x01u))//这个人的qq昵称 - tlv.containsKey(0x02u) -> String(tlv.getValue(0x02u))//这个人的群名片 - else -> { - tlv.printTLVMap("TLV(tag=18) Map") - MiraiLogger.warning("Could not determine senderName") - "null" - } - } - } - } - - val group = bot.getGroup(groupNumber) - return GroupMessage(group, senderName, senderPermission, group.getMember(qq), message).apply { this.botVar = bot } - // } - - } -} - -// endregion - -// region friend message - -data class FriendMessage( - /** - * 是否是在这次登录之前的消息, 即消息记录 - */ - val previous: Boolean, - override val sender: QQ, - override val message: MessageChain -) : MessagePacket(), BroadcastControllable { - /** - * 是否应被自动广播. 此为内部 API - */ - @MiraiInternalAPI - override val shouldBroadcast: Boolean - get() = !previous - - override val subject: QQ get() = sender -} - - -@Suppress("unused") -@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") -internal object FriendMessageEventParserAndHandler : KnownEventParserAndHandler(0x00A6u) { - override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendMessage { - discardExact(2) - val l1 = readShort() - discardExact(1)//0x00 - val previous = readByte().toInt() == 0x08 - discardExact(l1.toInt() - 2) - //java.io.EOFException: Only 49 bytes were discarded of 69 requested - //抖动窗口消息 - discardExact(69) - readUShortLVByteArray()//font - discardExact(2)//2个0x00 - val message = readMessageChain() - return FriendMessage( - previous = previous, - sender = bot.getQQ(identity.from), - message = message - ).apply { this.botVar = bot } - } -} -// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MuteEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MuteEvent.kt deleted file mode 100644 index 8bca6579d..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/MuteEvent.kt +++ /dev/null @@ -1,4 +0,0 @@ -package net.mamoe.mirai.network.protocol.timpc.packet.event - - - diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/PhoneMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/PhoneMessage.kt deleted file mode 100644 index 524bfd1ef..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/PhoneMessage.kt +++ /dev/null @@ -1,4 +0,0 @@ -package net.mamoe.mirai.network.protocol.timpc.packet.event - -//来自 Android 给我的电脑发消息, ID 0211, 内容 "你好" -//00 00 00 20 00 05 00 02 00 01 00 06 00 04 00 01 01 01 00 09 00 06 03 E9 20 02 EB 94 00 0A 00 04 01 00 00 00 00 00 00 54 00 00 00 3E 08 12 1A 16 08 07 10 91 04 18 DD F1 92 B7 07 20 83 76 38 DD F1 92 B7 07 50 04 42 21 08 DD F1 92 B7 07 10 DD F1 92 B7 07 18 01 20 07 40 DC 85 96 EE 05 48 DC 85 96 EE 05 50 FA AF FD 18 52 15 0A 06 08 01 10 00 50 01 1A 0B 08 E9 07 10 94 D7 8B 80 02 50 02 08 04 12 1D 08 E9 07 10 94 D7 8B 80 02 18 01 20 01 28 DD F1 92 B7 07 30 DD F1 92 B7 07 48 02 50 01 32 1B 08 80 80 B8 AE B5 02 10 01 18 00 20 01 2A 0C 0A 0A 08 01 12 06 E4 BD A0 E5 A5 BD \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/QuitGroup.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/QuitGroup.kt deleted file mode 100644 index a71f7bbd8..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/event/QuitGroup.kt +++ /dev/null @@ -1,4 +0,0 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.network.protocol.timpc.packet.event - diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/LoginResult.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/LoginResult.kt deleted file mode 100644 index 6250fa283..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/timpc/packet/login/LoginResult.kt +++ /dev/null @@ -1,124 +0,0 @@ -@file:Suppress("unused") - -package net.mamoe.mirai.network.protocol.timpc.packet.login - -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult.SUCCESS -import net.mamoe.mirai.utils.BotConfiguration -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -/** - * 登录结果. 除 [SUCCESS] 外均为失败. - * @see LoginResult.requireSuccess 要求成功 - */ -enum class LoginResult(val id: Byte) { - /** - * 登录成功 - */ - SUCCESS(0), - - /** - * 密码错误 - */ - WRONG_PASSWORD(1), - - /** - * 被冻结 - */ - BLOCKED(2), - - /** - * QQ 号码输入有误 - */ - UNKNOWN_QQ_NUMBER(3), - - /** - * 账号开启了设备锁. 暂不支持设备锁登录 - */ - DEVICE_LOCK(4), - - /** - * 账号被回收 - */ - TAKEN_BACK(5), - - /** - * 未知. 更换服务器或等几分钟再登录可能解决. - */ - UNKNOWN(6), - - /** - * 包数据错误 - */ - INTERNAL_ERROR(7), - - /** - * 超时 - */ - TIMEOUT(8), - - /** - * 网络不可用 - */ - NETWORK_UNAVAILABLE(9), - - /** - * 需要验证码且 [BotConfiguration.failOnCaptcha] 为 `true` - */ - CAPTCHA(10), - - /** - * 该号码长期未登录, 为了保证账号安全, 已被系统设置成保护状态, 请用手机 TIM 最新版本登录, 登录成功后即可自动解除保护模式 - */ // TIM的错误代码为 00020 - PROTECTED(11), -} - -/** - * 如果 [this] 不为 [LoginResult.SUCCESS] 就抛出消息为 [lazyMessage] 的 [IllegalStateException] - */ -@UseExperimental(ExperimentalContracts::class) -inline fun LoginResult.requireSuccess(lazyMessage: (LoginResult) -> String) { - contract { - callsInPlace(lazyMessage, InvocationKind.AT_MOST_ONCE) - } - if (this != SUCCESS) error(lazyMessage(this)) -} - - -/** - * 检查 [this] 为 [LoginResult.SUCCESS]. - * 失败则 [error] - */ -@Suppress("NOTHING_TO_INLINE") -inline fun LoginResult.requireSuccess() = requireSuccess { "Login failed: $this" } - -/** - * 检查 [this] 为 [LoginResult.SUCCESS]. - * 失败则返回 `null` - * - * @return 成功时 [Unit], 失败时 `null` - */ -@Suppress("NOTHING_TO_INLINE") -inline fun LoginResult.requireSuccessOrNull(): Unit? = if (this == SUCCESS) Unit else null - -/** - * 返回 [this] 是否为 [LoginResult.SUCCESS]. - */ -@Suppress("NOTHING_TO_INLINE") -@UseExperimental(ExperimentalContracts::class) -inline fun LoginResult.isSuccess(): Boolean = this == SUCCESS - -/** - * 检查 [this] 为 [LoginResult.SUCCESS]. - * 失败则返回 `null` - * - * @return 成功时 [Unit], 失败时 `null` - */ -@UseExperimental(ExperimentalContracts::class) -inline fun LoginResult.ifFail(block: (LoginResult) -> R): R? { - contract { - callsInPlace(block, InvocationKind.AT_MOST_ONCE) - } - return if (this != SUCCESS) block(this) else null -} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt index 941b3e467..cc5473379 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt @@ -9,7 +9,12 @@ import kotlin.annotation.AnnotationTarget.* * 非常不建议在发行版本中使用这些 API. */ @Experimental(level = Experimental.Level.ERROR) -@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) +@Target( + CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, + CLASS, + FUNCTION, + PROPERTY +) annotation class MiraiInternalAPI /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index bf5c032ae..7fa19b109 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -2,7 +2,6 @@ package net.mamoe.mirai.utils import kotlinx.io.core.IoBuffer import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchPacket.TouchResponse import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.jvm.JvmStatic @@ -24,9 +23,9 @@ expect var DefaultCaptchaSolver: CaptchaSolver */ class BotConfiguration : CoroutineContext.Element { /** - * 等待 [TouchResponse] 的时间 + * 连接每个服务器的时间 */ - var touchTimeoutMillis: Long = 2.secondsToMillis + var touchTimeoutMillis: Long = 1.secondsToMillis /** * 是否使用随机的设备名. * 使用随机可以降低被封禁的风险, 但可能导致每次登录都需要输入验证码 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt index 00070c957..e56c96a57 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Calculation.kt @@ -7,7 +7,7 @@ import net.mamoe.mirai.utils.io.getRandomByteArray private const val GTK_BASE_VALUE: Int = 5381 -internal fun getGTK(sKey: String): Int { +fun getGTK(sKey: String): Int { var value = GTK_BASE_VALUE for (c in sKey.toByteArray()) { value += (value shl 5) + c.toInt() @@ -18,14 +18,11 @@ internal fun getGTK(sKey: String): Int { } @Tested -@PublishedApi -internal fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16)) +fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16)) -@PublishedApi -internal fun BytePacketBuilder.writeCRC32(key: ByteArray) { +fun BytePacketBuilder.writeCRC32(key: ByteArray) { writeFully(key)//key writeInt(crc32(key)) } -@PublishedApi -internal fun md5(str: String): ByteArray = md5(str.toByteArray()) \ No newline at end of file +fun md5(str: String): ByteArray = md5(str.toByteArray()) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index ab1bfbfe1..8a6f59f42 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -7,8 +7,7 @@ import kotlinx.io.core.Input import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.message.* -import net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.io.toUHexString @Suppress("FunctionName") @@ -50,7 +49,14 @@ class ExternalImage( * 用于发送消息的 [ImageId] */ @Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") - val groupImageId: ImageId by lazy { ImageId0x03("{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format", 0u, height, width) } + val groupImageId: ImageId by lazy { + ImageId0x03( + "{${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}}.$format", + 0u, + height, + width + ) + } override fun toString(): String = "[ExternalImage(${width}x$height $format)]" } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt index fb41bda83..6561fc879 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt @@ -1,4 +1,4 @@ -@file:Suppress("NOTHING_TO_INLINE") +@file:Suppress("NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.utils diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LoginFailedException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LoginFailedException.kt new file mode 100644 index 000000000..5274825e0 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LoginFailedException.kt @@ -0,0 +1,8 @@ +package net.mamoe.mirai.utils + +import net.mamoe.mirai.network.data.LoginResult + +class LoginFailedException( + val result: LoginResult, + message: String = "Login failed with reason $result" +) : Exception(message) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt index 7730cfe94..65e421819 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OnlineStatus.kt @@ -8,7 +8,7 @@ import kotlin.jvm.JvmStatic * QQ 在线状态 * * @author Him188moe - * @see net.mamoe.mirai.network.protocol.timpc.packet.login.ChangeOnlineStatusPacket + * @see net.mamoe.mirai.timpc.network.packet.login.ChangeOnlineStatusPacket */ inline class OnlineStatus( inline val id: UByte diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt new file mode 100644 index 000000000..b6e33e620 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt @@ -0,0 +1,6 @@ +package net.mamoe.mirai.utils + +/** + * 图片文件过大 + */ +class OverFileSizeMaxException : IllegalStateException() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt index 2f529d4ad..04f576ec3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt @@ -4,7 +4,6 @@ package net.mamoe.mirai.utils import io.ktor.client.HttpClient import io.ktor.util.date.GMTDate -import kotlinx.io.core.IoBuffer /** * 时间戳 @@ -44,5 +43,4 @@ expect fun localIpAddress(): String /** * Ktor HttpClient. 不同平台使用不同引擎. */ -@PublishedApi -internal expect val Http: HttpClient +expect val Http: HttpClient diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt index 8fab59f2f..d2287fb19 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SuspendLazy.kt @@ -10,9 +10,13 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** - * 创建一个在当前 [CoroutineScope] 下执行初始化的 [SuspendLazy] + * 创建一个在当前 [CoroutineScope] 下执行初始化的 [suspendLazy] + * + * ```kotlin + * val image: Deferred by suspendLazy{ /* intializer */ } + * ``` */ -fun CoroutineScope.SuspendLazy(context: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R): Lazy> = +fun CoroutineScope.suspendLazy(context: CoroutineContext = EmptyCoroutineContext, initializer: suspend () -> R): Lazy> = SuspendLazy(this, context, initializer) /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt index 369c2914e..54b808afd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TEA.kt @@ -3,8 +3,6 @@ package net.mamoe.mirai.utils import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.IoBuffer import kotlinx.io.pool.useInstance -import net.mamoe.mirai.network.protocol.timpc.packet.Decrypter -import net.mamoe.mirai.network.protocol.timpc.packet.DecrypterByteArray import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toUHexString @@ -17,7 +15,7 @@ import kotlin.random.Random /** * 解密错误 */ -internal class DecryptionFailedException : Exception { +class DecryptionFailedException : Exception { constructor() : super() constructor(message: String?) : super(message) } @@ -31,9 +29,7 @@ internal class DecryptionFailedException : Exception { * @param key 长度至少为 16 * @throws DecryptionFailedException 解密错误时 */ -internal fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length) - -internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length) +fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length) /** * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密. @@ -42,7 +38,7 @@ internal fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.siz * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程 * @throws DecryptionFailedException 解密错误时 */ -internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) { +inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) { ByteArrayPool.useInstance { this.readFully(it, offset, length) consumer(it.encryptBy(key, length = length)) @@ -60,7 +56,7 @@ internal inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, le * @param key 固定长度 16 * @throws DecryptionFailedException 解密错误时 */ -internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = +fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length) /** @@ -71,7 +67,7 @@ internal fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteA * @param key 长度至少为 16 * @throws DecryptionFailedException 解密错误时 */ -internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray { +fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray { checkDataLengthAndReturnSelf(length) return ByteArrayPool.useInstance { keyBuffer -> key.readFully(keyBuffer, 0, key.readRemaining) @@ -85,7 +81,7 @@ internal fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteAr * @param key 长度至少为 16 * @throws DecryptionFailedException 解密错误时 */ -internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray { +fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray { return ByteArrayPool.useInstance { this.readFully(it, offset, length) it.checkDataLengthAndReturnSelf(length) @@ -97,20 +93,18 @@ internal fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = r // region ByteReadPacket extension -internal fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } +fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } -internal fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } +fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) } -internal fun ByteReadPacket.decryptBy(key: Decrypter): ByteReadPacket = key.decrypt(this) - -internal inline fun ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R = +inline fun ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R = ByteArrayPool.useInstance { val length = remaining.toInt() readFully(it, 0, length) consumer(it.decryptBy(key, length)) }.also { close() } -internal inline fun ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R = +inline fun ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R = ByteArrayPool.useInstance { val length = remaining.toInt() readFully(it, 0, length) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt index 55dc76b4b..52ba7ec94 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/WeakRef.kt @@ -39,12 +39,26 @@ expect class WeakRef(referent: T) { fun clear() } +/** + * Indicates that the property is delegated by a [WeakRef] + * + * @see weakRef + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.SOURCE) +annotation class WeakRefProperty + /** * Provides a weak reference to [this] * The `getValue` for delegation returns [this] when [this] is not released by GC */ fun T.weakRef(): WeakRef = WeakRef(this) +/** + * Constructs an unsafe inline delegate for [this] + */ +fun WeakRef.unsafe(): UnsafeWeakRef = UnsafeWeakRef(this) + /** * Provides a weak reference to [this]. * The `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Unreachable.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/assertUnreachable.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Unreachable.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/assertUnreachable.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/HexComparator.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/HexComparator.kt deleted file mode 100644 index 72de070c2..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/HexComparator.kt +++ /dev/null @@ -1,327 +0,0 @@ -@file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection", "unused", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package net.mamoe.mirai.utils.internal - -import kotlinx.io.core.toByteArray -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.hexToBytes -import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.readUVarInt -import net.mamoe.mirai.utils.io.toUHexString -import kotlin.math.max -import kotlin.reflect.KProperty0 - -/** - * 匹配已知 hex 常量并格式化后打印到控制台. - * - * 低效率, 仅调试使用. - */ -internal fun String.printColorize(ignoreUntilFirstConst: Boolean): String = with(HexComparator) { colorize(ignoreUntilFirstConst) } - -/** - * 比较两个 hex 并格式化后打印到控制台. - * - * 低效率, 仅调试使用. - */ -@MiraiInternalAPI -fun printCompareHex(hex1s: String, hex2s: String): String = with(HexComparator) { compare(hex1s.toUpperCase(), hex2s.toUpperCase()) } - -data class NamedHexElement( - val name: String, - val value: String -) - -/** - * 初始化用于匹配的 Hex 列表 - */ -private fun LinkedHashSet.initConstFileds() { - listOf( - TestConsts, - TIMProtocol, - PacketIds - ).forEach { obj -> - obj::class.members.filterIsInstance>().forEach { property -> - property.get()?.let { add(NamedHexElement(property.name, it.toString())) } - } - } -} - -private object TestConsts { - val NIU_BI = "牛逼".toByteArray().toUHexString() - val _1994701021 = 1994701021u.toUHexString(" ") - val _1040400290 = 1040400290u.toUHexString(" ") - val _580266363 = 580266363u.toUHexString(" ") - val _761025446 = 761025446u.toUHexString() - - val _1040400290_ = "3E 03 3F A2" - val _1994701021_ = "76 E4 B8 DD" - val _jiahua_ = "B1 89 BE 09" - val _Him188moe_ = "Him188moe".toByteArray().toUHexString() - val 发图片2 = "发图片2".toByteArray().toUHexString() - val 发图片群 = "发图片群".toByteArray().toUHexString() - val 发图片 = "发图片".toByteArray().toUHexString() - val 群 = "群".toByteArray().toUHexString() - val 你好 = "你好".toByteArray().toUHexString() - - val MESSAGE_TAIL_10404 = - "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00" - .replace(" ", " ") - - val FONT_10404 = "E5 BE AE E8 BD AF E9 9B 85 E9 BB 91" - - val varint580266363 = "FB D2 D8 94 02" - val varint1040400290 = "A2 FF 8C F0 03" - val varint1994701021 = "DD F1 92 B7 07" - val varint761025446 = 761025446u.toUHexString().hexToBytes().read { readUVarInt() }.toUHexString() -} - -@Suppress("SpellCheckingInspection") -private object PacketIds { - val heartbeat = "00 58" - val friendmsgsend = "00 CD" - val friendmsgevent = "00 CE" - val groupmsg = "00 02" -} - -/** - * Hex 比较器, 并着色已知常量 - * - * This could be used to check packet encoding.. - * but better to run under UNIX - * - * @author NaturalHG - * @author Him188moe - */ -private object HexComparator { - - private val RED = "\u001b[31m" - private val GREEN = "\u001b[33m" - private val UNKNOWN_COLOR = "\u001b[30m" - private val BLUE = "\u001b[34m" - - @Suppress("unused") - private class ConstMatcher constructor(hex: String) { - private val matches = linkedSetOf() - - fun getMatchedConstName(hexNumber: Int): String? { - for (match in this.matches) { - if (match.range.contains(hexNumber)) { - return match.constName - } - } - return null - } - - private class Match internal constructor(val range: IntRange, val constName: String) - - init { - CONST_FIELDS.forEach { (name, value) -> - for (match in match(hex, value)) { - matches.add(Match(match, name)) - } - } - TIMProtocol::class.members.filterIsInstance>().mapNotNull { it()?.toString() }.forEach { - for (match in match(hex, it)) { - matches.add(Match(match, it)) - } - } - } - - companion object { - val CONST_FIELDS: Set = linkedSetOf().apply { initConstFileds() } - } - - private fun match(hex: String, value: String): Set { - val constValue: String - try { - constValue = value.trim { it <= ' ' } - if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes - return linkedSetOf() - } - } catch (ignored: ClassCastException) { - return linkedSetOf() - } - - return mutableSetOf().apply { - var index = -1 - index = hex.indexOf(constValue, index + 1) - while (index != -1) { - add(IntRange(index / 3, (index + constValue.length) / 3)) - - index = hex.indexOf(constValue, index + 1) - } - } - } - } - - private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) { - //System.out.println(constMatcher.matches); - var i = 0 - while (i < length) { - constNameBuilder.append(" ") - val match = constMatcher.getMatchedConstName(i / 4) - if (match != null) { - var appendedNameLength = match.length - constNameBuilder.append(match) - while (match == constMatcher.getMatchedConstName(i++ / 4)) { - if (appendedNameLength-- < 0) { - constNameBuilder.append(" ") - } - } - - constNameBuilder.append(" ".repeat(match.length % 4)) - } - i++ - } - } - - fun compare(hex1s: String, hex2s: String): String { - val builder = StringBuilder() - - val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val constMatcher1 = ConstMatcher(hex1s) - val constMatcher2 = ConstMatcher(hex2s) - - if (hex1.size == hex2.size) { - builder.append(GREEN).append("长度一致:").append(hex1.size) - } else { - builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size) - } - - - val numberLine = StringBuilder() - val hex1ConstName = StringBuilder() - val hex1b = StringBuilder() - val hex2b = StringBuilder() - val hex2ConstName = StringBuilder() - var dif = 0 - - val length = max(hex1.size, hex2.size) * 4 - buildConstNameChain(length, constMatcher1, hex1ConstName) - buildConstNameChain(length, constMatcher2, hex2ConstName) - - - for (i in 0 until max(hex1.size, hex2.size)) { - var h1: String? = null - var h2: String? = null - var isDif = false - if (hex1.size <= i) { - h1 = RED + "__" - isDif = true - } else { - val matchedConstName = constMatcher1.getMatchedConstName(i) - if (matchedConstName != null) { - h1 = BLUE + hex1[i] - } - } - if (hex2.size <= i) { - h2 = RED + "__" - isDif = true - } else { - val matchedConstName = constMatcher2.getMatchedConstName(i) - if (matchedConstName != null) { - h2 = BLUE + hex2[i] - } - } - - if (h1 == null && h2 == null) { - h1 = hex1[i] - h2 = hex2[i] - if (h1 == h2) { - h1 = GREEN + h1 - h2 = GREEN + h2 - } else { - h1 = RED + h1 - h2 = RED + h2 - isDif = true - } - } else { - if (h1 == null) { - h1 = RED + hex1[i] - } - if (h2 == null) { - h2 = RED + hex2[i] - } - } - - numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ") - hex1b.append(" ").append(h1).append(" ") - hex2b.append(" ").append(h2).append(" ") - if (isDif) { - ++dif - } - - //doConstReplacement(hex1b); - //doConstReplacement(hex2b); - } - - return builder.append(" ").append(dif).append(" 个不同").append("\n") - .append(numberLine).append("\n") - .append(hex1ConstName).append("\n") - .append(hex1b).append("\n") - .append(hex2b).append("\n") - .append(hex2ConstName).append("\n") - .toString() - - - } - - fun String.colorize(ignoreUntilFirstConst: Boolean = false): String { - val builder = StringBuilder() - - val hex = trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray() - val constMatcher1 = ConstMatcher(this) - - val numberLine = StringBuilder() - val hex1ConstName = StringBuilder() - val hex1b = StringBuilder() - - buildConstNameChain(length, constMatcher1, hex1ConstName) - - var firstConst: String? = null - var constNameOffset = 0//已经因为还没到第一个const跳过了几个char - for (i in hex.indices) { - var h1: String? = null - - val matchedConstName = constMatcher1.getMatchedConstName(i) - if (matchedConstName != null) { - firstConst = firstConst ?: matchedConstName - h1 = BLUE + hex[i] - } - - if (!ignoreUntilFirstConst || firstConst != null) {//有过任意一个 Const - if (h1 == null) { - h1 = GREEN + hex[i] - } - numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ") - hex1b.append(" ").append(h1).append(" ") - } else { - constNameOffset++ - } - } - - return builder.append("\n") - .append(numberLine).append("\n") - .append(if (firstConst == null) hex1ConstName else { - with(hex1ConstName) { - val index = indexOf(firstConst) - if (index == -1) toString() else " " + substring(index, length) - } - }).append("\n") - .append(hex1b).append("\n") - .toString() - } - - - private fun getFixedNumber(number: Int): String { - if (number < 10) { - return "00$number" - } - return if (number < 100) { - "0$number" - } else number.toString() - } - -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt index 71e41b1de..e75b91998 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/NumberUtils.kt @@ -4,8 +4,7 @@ package net.mamoe.mirai.utils.internal * 要求 [this] 最小为 [min]. */ @Suppress("NOTHING_TO_INLINE") -@PublishedApi -internal inline fun Int.coerceAtLeastOrFail(min: Int): Int { +inline fun Int.coerceAtLeastOrFail(min: Int): Int { require(this >= min) return this } @@ -14,8 +13,7 @@ internal inline fun Int.coerceAtLeastOrFail(min: Int): Int { * 要求 [this] 最小为 [min]. */ @Suppress("NOTHING_TO_INLINE") -@PublishedApi -internal inline fun Long.coerceAtLeastOrFail(min: Long): Long { +inline fun Long.coerceAtLeastOrFail(min: Long): Long { require(this >= min) return this } @@ -24,20 +22,11 @@ internal inline fun Long.coerceAtLeastOrFail(min: Long): Long { * 要求 [this] 最大为 [max]. */ @Suppress("NOTHING_TO_INLINE") -@PublishedApi -internal inline fun Int.coerceAtMostOrFail(max: Int): Int = +inline fun Int.coerceAtMostOrFail(max: Int): Int = if (this >= max) error("value is greater than its expected maximum value $max") else this @Suppress("NOTHING_TO_INLINE") -@PublishedApi -internal inline fun Long.coerceAtMostOrFail(max: Long): Long = +inline fun Long.coerceAtMostOrFail(max: Long): Long = if (this >= max) error("value is greater than its expected maximum value $max") - else this - -/** - * 表示这个参数必须为正数. 仅用于警示 - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE) -internal annotation class PositiveNumbers \ No newline at end of file + else this \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt index 6d2f97436..f3b23df58 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt @@ -6,7 +6,7 @@ import kotlinx.io.pool.ObjectPool internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256 internal const val DEFAULT_BYTE_ARRAY_SIZE = 4096 -internal val ByteArrayPool: ObjectPool = ByteArrayPoolImpl +val ByteArrayPool: ObjectPool = ByteArrayPoolImpl private object ByteArrayPoolImpl : DefaultPool(DEFAULT_BYTE_ARRAY_POOL_SIZE) { override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt index 12dd8341b..65a4b16f8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt @@ -1,4 +1,4 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") package net.mamoe.mirai.utils.io @@ -7,33 +7,41 @@ import kotlinx.io.charsets.Charsets import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.String import kotlinx.io.core.use -import kotlin.jvm.JvmOverloads -import kotlin.jvm.JvmSynthetic -@JvmOverloads -fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) { - var ret = it.toString(16).toUpperCase() - if (ret.length == 1) { - ret = "0$ret" +@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray +@UseExperimental(ExperimentalUnsignedTypes::class) +fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { + val lastIndex = offset + length + return buildString(length * 2) { + this@toUHexString.forEachIndexed { index, it -> + if (index in offset until lastIndex) { + var ret = it.toByte().toUByte().toString(16).toUpperCase() + if (ret.length == 1) ret = "0$ret" + append(ret) + if (index != lastIndex) append(separator) + } + } } - return@joinToString ret } -@JvmOverloads -fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator) +@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray +@ExperimentalUnsignedTypes +fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { + val lastIndex = offset + length + return buildString(length * 2) { + this@toUHexString.forEachIndexed { index, it -> + if (index in offset until lastIndex) { + var ret = it.toByte().toUByte().toString(16).toUpperCase() + if (ret.length == 1) ret = "0$ret" + append(ret) + if (index != lastIndex) append(separator) + } + } + } +} fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset) -@JvmSynthetic -@JvmOverloads -fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString(separator) { - var ret = it.toString(16).toUpperCase() - if (ret.length == 1) { - ret = "0$ret" - } - return@joinToString ret -} - fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size) = ByteReadPacket(this, offset = offset, length = length) inline fun ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().use(t) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt index 1f1e3770f..e6ecb2920 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt @@ -1,109 +1,64 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.* +import kotlinx.io.pool.useInstance import net.mamoe.mirai.utils.DefaultLogger -import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.internal.printColorize -import net.mamoe.mirai.utils.internal.printCompareHex import net.mamoe.mirai.utils.withSwitch -internal object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch() +object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch() -internal fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) +fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) -@PublishedApi -internal fun debugPrintln(any: Any?) = DebugLogger.debug(any) +fun debugPrintln(any: Any?) = DebugLogger.debug(any) -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal fun String.debugPrint(name: String): String { +fun String.debugPrint(name: String): String { DebugLogger.debug("$name=$this") return this } -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal fun ByteArray.debugPrint(name: String): ByteArray { +fun ByteArray.debugPrint(name: String): ByteArray { DebugLogger.debug(name + "=" + this.toUHexString()) return this } -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal fun IoBuffer.debugPrint(name: String): IoBuffer { - val readBytes = this.readBytes() - DebugLogger.debug(name + "=" + readBytes.toUHexString()) - return readBytes.toIoBuffer() +fun IoBuffer.debugPrint(name: String): IoBuffer { + ByteArrayPool.useInstance { + val count = this.readAvailable(it) + DebugLogger.debug(name + "=" + it.toUHexString(offset = 0, length = count)) + return it.toIoBuffer(0, count) + } } -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer { - val readBytes = this.readBytes() - block(readBytes.toIoBuffer()) - return readBytes.toIoBuffer() +inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer { + ByteArrayPool.useInstance { + val count = this.readAvailable(it) + block(it.toIoBuffer(0, count)) + return it.toIoBuffer(0, count) + } } -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("discardExact(n)")) -internal fun Input.debugDiscardExact(n: Number, name: String = "") { +fun Input.debugDiscardExact(n: Number, name: String = "") { DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString()) } -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket { - val bytes = this.readBytes() - DebugLogger.debug("ByteReadPacket $name=" + bytes.toUHexString()) - return bytes.toReadPacket() -} - -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal inline fun ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R { - val bytes = this.readBytes() - try { - return block(bytes.toReadPacket()) - } catch (e: Throwable) { - DebugLogger.debug("Error in ByteReadPacket $name=" + bytes.toUHexString()) - throw e +fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket { + ByteArrayPool.useInstance { + val count = this.readAvailable(it) + DebugLogger.debug("ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count)) + return it.toReadPacket(0, count) } } -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith("this")) -internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket { - val bytes = this.readBytes() - bytes.printColorizedHex(name, ignoreUntilFirstConst) - return bytes.toReadPacket() -} - -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" ")) -internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "") { - val data = this.build().readBytes() - data.printColorizedHex(name) - this.writeFully(data) -} - -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" ")) -internal fun BytePacketBuilder.debugColorizedPrintThis(name: String = "", compareTo: String? = null) { - val data = this.build().readBytes() - data.printColorizedHex(name, compareTo = compareTo) - this.writeFully(data) -} - -@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(" ")) -internal fun BytePacketBuilder.debugPrintThis(name: String = "") { - val data = this.build().readBytes() - data.printColorizedHex(name) - this.writeFully(data) -} - -internal fun String.printStringFromHex() { - println(this.hexToBytes().encodeToString()) -} - -@UseExperimental(MiraiInternalAPI::class) -internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstConst: Boolean = false, compareTo: String? = null) { - println("Hex比较 `$name`") - if (compareTo != null) { - println(printCompareHex(toUHexString(), compareTo)) - } else { - println(toUHexString().printColorize(ignoreUntilFirstConst)) +inline fun ByteReadPacket.debugPrintIfFail(name: String = "", block: ByteReadPacket.() -> R): R { + ByteArrayPool.useInstance { + val count = this.readAvailable(it) + try { + return block(it.toReadPacket(0, count)) + } catch (e: Throwable) { + DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count)) + throw e + } } - println() } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt index 70e8e41db..d74dea273 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt @@ -2,10 +2,13 @@ package net.mamoe.mirai.utils.io -import kotlinx.io.InputStream import kotlinx.io.OutputStream import kotlinx.io.core.* import kotlinx.io.pool.useInstance +import net.mamoe.mirai.contact.GroupId +import net.mamoe.mirai.contact.GroupInternalId +import net.mamoe.mirai.contact.groupId +import net.mamoe.mirai.contact.groupInternalId import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -23,6 +26,14 @@ inline fun Input.discardExact(n: UByte) = this.discardExact(n.toInt()) @Suppress("NOTHING_TO_INLINE") inline fun Input.discardExact(n: Byte) = this.discardExact(n.toInt()) +fun ByteReadPacket.transferTo(outputStream: OutputStream) { + ByteArrayPool.useInstance { + while (this.isNotEmpty) { + outputStream.write(it, 0, this.readAvailable(it)) + } + } +} + fun ByteReadPacket.readRemainingBytes( n: Int = remaining.toInt()//not that safe but adequate ): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) } @@ -43,6 +54,11 @@ fun Input.readIP(): String = buildString(4 + 3) { fun Input.readPacket(length: Int): ByteReadPacket = this.readBytes(length).toReadPacket() +fun Input.readQQ(): Long = this.readUInt().toLong() +fun Input.readGroup(): Long = this.readUInt().toLong() +fun Input.readGroupId(): GroupId = this.readUInt().toLong().groupId() +fun Input.readGroupInternalId(): GroupInternalId = this.readUInt().toLong().groupInternalId() + fun Input.readUVarIntLVString(): String = String(this.readUVarIntByteArray()) fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray()) @@ -55,7 +71,7 @@ fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().to fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) -internal inline fun inline(block: () -> R): R = block() +private inline fun inline(block: () -> R): R = block() @Suppress("DuplicatedCode") fun Input.readTLVMap(expectingEOF: Boolean = false, tagSize: Int = 1): MutableMap { @@ -144,9 +160,6 @@ fun Map.printTLVMap(name: String = "", keyLength: Int = 1) = internal inline fun unsupported(message: String? = null): Nothing = error(message ?: "Unsupported") -internal inline fun ByteReadPacket.unsupportedFlag(name: String, flag: String): Nothing = error("Unsupported flag of $name. flag=$flag, remaining=${readBytes().toUHexString()}") -internal inline fun ByteReadPacket.unsupportedType(name: String, type: String): Nothing = error("Unsupported type of $name. type=$type, remaining=${readBytes().toUHexString()}") - internal inline fun illegalArgument(message: String? = null): Nothing = error(message ?: "Illegal argument passed") @JvmName("printTLVStringMap") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt index dbcf2239b..bc176c2c3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt @@ -8,54 +8,52 @@ import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupInternalId -import net.mamoe.mirai.network.protocol.timpc.TIMProtocol -import net.mamoe.mirai.network.protocol.timpc.packet.DecrypterByteArray -import net.mamoe.mirai.network.protocol.timpc.packet.login.PrivateKey -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.Tested +import net.mamoe.mirai.utils.currentTime +import net.mamoe.mirai.utils.deviceName +import net.mamoe.mirai.utils.encryptBy import net.mamoe.mirai.utils.internal.coerceAtMostOrFail import kotlin.random.Random import kotlin.random.nextInt -internal fun BytePacketBuilder.writeZero(count: Int) { +fun BytePacketBuilder.writeZero(count: Int) { require(count != 0) { "Trying to write zero with count 0, you made a mistake?" } require(count > 0) { "writeZero: count must > 0" } repeat(count) { this.writeByte(0) } } -internal fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) } +fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) } -internal fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) -internal fun BytePacketBuilder.writeQQ(qq: UInt) = this.writeUInt(qq) -internal fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value) -internal fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value) -internal fun BytePacketBuilder.writeFully(value: DecrypterByteArray) = this.writeFully(value.value) +fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) // same bit rep. +fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value.toUInt()) +fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value.toUInt()) -internal fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) { +fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) { this.writeShort(byteArray.size.toShort()) this.writeFully(byteArray) } -internal fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = +fun BytePacketBuilder.writeShortLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = BytePacketBuilder().apply(builder).build().use { if (tag != null) writeUByte(tag) writeUShort((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL).toUShort()) writePacket(it) } -internal fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = +fun BytePacketBuilder.writeUVarIntLVPacket(tag: UByte? = null, lengthOffset: ((Long) -> Long)? = null, builder: BytePacketBuilder.() -> Unit) = BytePacketBuilder().apply(builder).build().use { if (tag != null) writeUByte(tag) writeUVarInt((lengthOffset?.invoke(it.remaining) ?: it.remaining).coerceAtMostOrFail(0xFFFFL)) writePacket(it) } -internal fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray()) +fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray()) -internal fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray()) +fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray()) -internal fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt()) +fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt()) -internal fun BytePacketBuilder.writeHex(uHex: String) { +fun BytePacketBuilder.writeHex(uHex: String) { uHex.split(" ").forEach { if (it.isNotBlank()) { writeUByte(it.toUByte(16)) @@ -63,49 +61,49 @@ internal fun BytePacketBuilder.writeHex(uHex: String) { } } -internal fun BytePacketBuilder.writeProto(serializer: SerializationStrategy, obj: T) = writeFully(ProtoBuf.dump(serializer, obj)) +fun BytePacketBuilder.writeProto(serializer: SerializationStrategy, obj: T) = writeFully(ProtoBuf.dump(serializer, obj)) -internal fun BytePacketBuilder.writeTLV(tag: UByte, values: UByteArray) { +fun BytePacketBuilder.writeTLV(tag: UByte, values: UByteArray) { writeUByte(tag) writeUVarInt(values.size.toUInt()) writeFully(values) } -internal fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) { +fun BytePacketBuilder.writeTLV(tag: UByte, values: ByteArray) { writeUByte(tag) writeUVarInt(values.size.toUInt()) writeFully(values) } -internal fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) { +fun BytePacketBuilder.writeTHex(tag: UByte, uHex: String) { this.writeUByte(tag) this.writeFully(uHex.hexToUBytes()) } -internal fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue) +fun BytePacketBuilder.writeTV(tagValue: UShort) = writeUShort(tagValue) -internal fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) { +fun BytePacketBuilder.writeTV(tag: UByte, value: UByte) { writeUByte(tag) writeUByte(value) } -internal fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) { +fun BytePacketBuilder.writeTUbyte(tag: UByte, value: UByte) { this.writeUByte(tag) this.writeUByte(value) } -internal fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) { +fun BytePacketBuilder.writeTUVarint(tag: UByte, value: UInt) { this.writeUByte(tag) this.writeUVarInt(value) } -internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) { +fun BytePacketBuilder.writeTByteArray(tag: UByte, value: ByteArray) { this.writeUByte(tag) this.writeFully(value) } -internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) { +fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) { this.writeUByte(tag) this.writeFully(value) } @@ -113,43 +111,18 @@ internal fun BytePacketBuilder.writeTByteArray(tag: UByte, value: UByteArray) { /** * 会使用 [ByteArrayPool] 缓存 */ -internal inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = +inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) } -internal inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance { +inline fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = ByteArrayPool.useInstance { key.readFully(it, 0, key.readRemaining) encryptAndWrite(it, encoder) } -internal inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.value, encoder) - -internal inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder) - -internal fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) { - val firstMD5 = md5(password) - val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray()) - - this.encryptAndWrite(secondMD5) { - writeRandom(4) - writeHex("00 02") - writeQQ(qq) - writeFully(TIMProtocol.constantData2) - writeHex("00 00 01") - - writeFully(firstMD5) - writeInt(loginTime) - writeByte(0) - writeZero(4 * 3) - writeIP(loginIP) - writeZero(8) - writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾 - writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16 - writeFully(privateKey.value) - } -} +inline fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder) @Tested -internal fun BytePacketBuilder.writeDeviceName(random: Boolean) { +fun BytePacketBuilder.writeDeviceName(random: Boolean) { val deviceName: String = if (random) { "DESKTOP-" + String(ByteArray(7) { (if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt index 2876729ee..30e4d7745 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformSocket.kt @@ -23,9 +23,9 @@ expect class ClosedChannelException : IOException /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ -internal class SendPacketInternalException(cause: Throwable?) : Exception(cause) +class SendPacketInternalException(cause: Throwable?) : Exception(cause) /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ -internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause) \ No newline at end of file +class ReadPacketInternalException(cause: Throwable?) : Exception(cause) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt index 43d5bf5c1..659e326cf 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt @@ -3,7 +3,6 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.IoBuffer -import kotlinx.io.core.writeFully import kotlinx.io.pool.ObjectPool import kotlin.random.Random import kotlin.random.nextInt @@ -105,8 +104,7 @@ fun String.hexToUBytes(): UByteArray = /** * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] */ -@PublishedApi -internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } +fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() } /** * 随机生成长度为 [length] 的 [String]. @@ -142,4 +140,4 @@ fun ByteArray.toUShort(): UShort = * 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入. * 注意回收 ([ObjectPool.recycle]) */ -fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it } \ No newline at end of file +fun ByteArray.toIoBuffer(offset: Int = 0, length: Int = this.size - offset): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this, offset, length); it } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt index fbda79f87..929c5885c 100644 --- a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt +++ b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils.io/TypeConversionTest.kt @@ -2,7 +2,6 @@ package net.mamoe.mirai.utils.io import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.asserter class TypeConversionTest { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt similarity index 94% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt index 4a4e174d1..26bc03188 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/timpc/packet/event/MessagePacket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt @@ -1,15 +1,16 @@ @file:Suppress("unused") -package net.mamoe.mirai.network.protocol.timpc.packet.event +package net.mamoe.mirai.message import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.io.core.Input import kotlinx.io.core.use import kotlinx.io.streams.inputStream +import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.message.* +import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.toExternalImage @@ -24,7 +25,7 @@ import javax.imageio.ImageIO * JVM 平台相关扩展 */ @UseExperimental(MiraiInternalAPI::class) -actual abstract class MessagePacket : MessagePacketBase() { +actual abstract class MessagePacket actual constructor(bot: Bot) : MessagePacketBase(bot) { suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image) suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt index ec4ff96c3..b6a88fc46 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt @@ -6,7 +6,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.io.core.Input import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.network.protocol.timpc.packet.action.OverFileSizeMaxException +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.utils.OverFileSizeMaxException import net.mamoe.mirai.utils.sendTo import net.mamoe.mirai.utils.toExternalImage import net.mamoe.mirai.utils.upload diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/BotSessionJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/BotSessionJvm.kt deleted file mode 100644 index 78eee8187..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/BotSessionJvm.kt +++ /dev/null @@ -1,45 +0,0 @@ -package net.mamoe.mirai.network - -import io.ktor.util.KtorExperimentalAPI -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.withContext -import kotlinx.io.core.copyTo -import kotlinx.io.core.use -import kotlinx.io.streams.asInput -import kotlinx.io.streams.asOutput -import kotlinx.io.streams.inputStream -import net.mamoe.mirai.Bot -import net.mamoe.mirai.message.Image -import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey -import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.toExternalImage -import java.awt.image.BufferedImage -import java.io.File -import java.io.InputStream -import java.io.OutputStream -import javax.imageio.ImageIO - -/** - * JVM 平台相关扩展. 详情查看 [BotSessionBase] - */ -@UseExperimental(MiraiInternalAPI::class) -@Suppress("unused") -actual class BotSession internal actual constructor( - bot: Bot, - sessionKey: SessionKey -) : BotSessionBase(bot, sessionKey) { - - suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() - suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(IO) { downloadAsStream().use { ImageIO.read(it) } } - suspend inline fun Image.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() } - - suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) } - - /** - * 需要调用者自行 close [output] - */ - @UseExperimental(KtorExperimentalAPI::class) - suspend inline fun Image.downloadTo(output: OutputStream) = - download().inputStream().asInput().use { input -> withContext(IO) { input.copyTo(output.asOutput()) } } -} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherJvm.kt deleted file mode 100644 index 3b4d250ad..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/network/protocol/timpc/NetworkDispatcherJvm.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.mamoe.mirai.network.protocol.timpc - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.asCoroutineDispatcher -import java.util.concurrent.Executors - -/** - * 独立的 4 thread 调度器 - */ -internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index c6a5d48bc..ddeccb0d1 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -1,10 +1,9 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.utils import io.ktor.util.asStream import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.io.jvm.javaio.copyTo import kotlinx.coroutines.withContext import kotlinx.io.core.Input import kotlinx.io.core.IoBuffer @@ -48,8 +47,7 @@ fun BufferedImage.toExternalImage(formatName: String = "gif"): ExternalImage { return ExternalImage(width, height, digest.digest(), formatName, buffer) } -@Suppress("unused") -suspend fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** * 读取文件头识别图片属性, 然后构造 [ExternalImage] @@ -75,8 +73,7 @@ fun File.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [File.toExternalImage] */ -@Suppress("unused") -suspend fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** * 下载文件到临时目录然后调用 [File.toExternalImage] @@ -95,8 +92,7 @@ fun URL.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [URL.toExternalImage] */ -@Suppress("unused") -suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** * 保存为临时文件然后调用 [File.toExternalImage] @@ -114,11 +110,12 @@ fun InputStream.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [InputStream.toExternalImage] */ -@Suppress("unused") -suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** - * 保存为临时文件然后调用 [File.toExternalImage] + * 保存为临时文件然后调用 [File.toExternalImage]. + * + * 需要函数调用者 close [this] */ @Throws(IOException::class) fun Input.toExternalImage(): ExternalImage { @@ -132,5 +129,4 @@ fun Input.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [Input.toExternalImage] */ -@Suppress("unused") -suspend fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRef.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRefJvm.kt similarity index 100% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRef.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WeakRefJvm.kt diff --git a/mirai-core/src/jvmTest/kotlin/mirai/test/ecdh/ecdh.c b/mirai-core/src/jvmTest/kotlin/mirai/test/ecdh/ecdh.c new file mode 100644 index 000000000..f9ee8bc63 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/mirai/test/ecdh/ecdh.c @@ -0,0 +1,357 @@ + +int __fastcall sub_F74(int a1, int a2, int a3, signed int a4, int a5, const char *a6) +{ + int v6; // r4 + int v7; // r5 + int v8; // r7 + int v9; // r0 + char *v10; // r6 + int v11; // r0 + int v12; // r0 + signed int v13; // r7 + int v14; // r0 + int v15; // r5 + signed int v16; // r5 + int v17; // r0 + int v18; // r5 + int v19; // r7 + int v20; // r5 + int v21; // r0 + signed int v22; // r5 + signed int v23; // r7 + int v24; // r0 + int v25; // r0 + int v26; // r0 + int v27; // r0 + int v28; // r3 + signed int v29; // r0 + int v30; // r2 + int result; // r0 + int v32; // r2 + int v33; // r2 + int v34; // [sp+0h] [bp-10D0h] + int v35; // [sp+4h] [bp-10CCh] + signed int v36; // [sp+8h] [bp-10C8h] + int v37; // [sp+Ch] [bp-10C4h] + const char *v38; // [sp+10h] [bp-10C0h] + int v39; // [sp+14h] [bp-10BCh] + int v40; // [sp+18h] [bp-10B8h] + int v41; // [sp+1Ch] [bp-10B4h] + int v42; // [sp+20h] [bp-10B0h] + _DWORD *v43; // [sp+24h] [bp-10ACh] + int v44; // [sp+28h] [bp-10A8h] + int v45; // [sp+2Ch] [bp-10A4h] + int v46; // [sp+30h] [bp-10A0h] + char v47; // [sp+34h] [bp-109Ch] + char v48; // [sp+B4h] [bp-101Ch] + char v49; // [sp+2B4h] [bp-E1Ch] + int v50; // [sp+4B4h] [bp-C1Ch] + char v51; // [sp+8B4h] [bp-81Ch] + unsigned __int8 v52; // [sp+CB4h] [bp-41Ch] + int v53; // [sp+10B4h] [bp-1Ch] + + v6 = a2; + v36 = a4; + v40 = a3; + v37 = a5; + v7 = (int)&_stack_chk_guard; + v8 = 676; + v38 = a6; + v41 = a1; + v9 = (*(int (__fastcall **)(int, signed int, _DWORD))(*(_DWORD *)a2 + 676))(a2, a4, 0); + v43 = &_stack_chk_guard; + v10 = (char *)v9; + v39 = 676; + if ( v9 ) + { + j_memset(&v50, 0, 1024); + v8 = (int)"%s"; + j_snprintf(&v50, 1024, "%s", v10); + v7 = 680; + (*(void (__fastcall **)(int, signed int, char *))(*(_DWORD *)v6 + 680))(v6, v36, v10); + v10 = &aJavaOicqWlogin_1[(_DWORD)&v42 + 27]; + j_memset(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27], 0, 1024); + v36 = 680; + if ( v41 ) + { + v11 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)v6 + 676))(v6, a5, 0); + v7 = v11; + if ( !v11 ) + { + v29 = 3; + goto LABEL_59; + } + j_snprintf(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27], 1024, "%s", v11); + (*(void (__fastcall **)(int, int, int))(*(_DWORD *)v6 + 680))(v6, a5, v7); + } + v10 = (char *)&_stack_chk_fail + (_DWORD)&v42; + j_memset((char *)&_stack_chk_fail + (_DWORD)&v42, 0, 1024); + if ( v41 ) + { + v12 = (*(int (__fastcall **)(int, const char *, _DWORD))(*(_DWORD *)v6 + 676))(v6, a6, 0); + v7 = v12; + if ( !v12 ) + { + v29 = 2; + goto LABEL_59; + } + j_snprintf((char *)&_stack_chk_fail + (_DWORD)&v42, 1024, "%s", v12); + (*(void (__fastcall **)(int, const char *, int))(*(_DWORD *)v6 + 680))(v6, a6, v7); + } + j_memset(&v47, 0, 128); + v44 = 512; + v37 = j_EC_KEY_new_by_curve_name(711); + if ( !v37 ) + { + j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_new_by_curve_name failed."); + v8 = -1; +LABEL_56: + j___android_log_print(4, "wlogin_sdk", "GenerateKey failed peerBase16PublicKey %s ret %d", &v50, v8, v35); + v29 = 4; + goto LABEL_59; + } + v7 = v52; + if ( !v52 ) + { + if ( j_EC_KEY_generate_key(v37) != 1 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_KEY_generate_key failed ret %d"); + v10 = (_BYTE *)(&stru_3F8 + 8); + v36 = 128; + v38 = (_BYTE *)(&stru_3F8 + 8); + v13 = 2; +LABEL_51: + v8 = -v13; + goto LABEL_53; + } +LABEL_35: + v39 = j_EC_KEY_get0_group(v37); + if ( v39 ) + { + v24 = j_EC_KEY_get0_public_key(v37); + v7 = v24; + if ( !v24 ) + { + j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_get0_public_key failed"); + v10 = (_BYTE *)(&stru_3F8 + 8); + v36 = 128; + v38 = (_BYTE *)(&stru_3F8 + 8); + v13 = 5; + goto LABEL_51; + } + v38 = (const char *)j_EC_POINT_point2oct(v39, v24, 2, &aJavaOicqWlogin_1[(_DWORD)&v42 + 27], 67, 0); + if ( (signed int)v38 > 0 ) + { + v25 = j_EC_KEY_get0_private_key(v37); + v7 = v25; + if ( v25 ) + { + v26 = j_BN_bn2mpi(v25, 0); + v10 = (char *)v26; + if ( v26 > 1024 ) + { + j___android_log_print(4, "wlogin_sdk", "ERROR:privateKeyLen %d larger than buff len %d", v26, 1024); + v10 = (_BYTE *)(&stru_3F8 + 8); + v23 = 5; + v36 = 128; + goto LABEL_45; + } + j_BN_bn2mpi(v7, (char *)&_stack_chk_fail + (_DWORD)&v42); + v27 = j_strlen(&v50); + String2Buffer(&v50, v27, &v48, &v44); + v7 = j_EC_POINT_new(v39); + if ( j_EC_POINT_oct2point(v39, v7, &v48, v44, 0) != 1 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_POINT_oct2point failed %d"); + v13 = 6; + v36 = 128; + goto LABEL_51; + } + v28 = j_ECDH_compute_key(&v49, 512, v7, v37, 0); + if ( v28 > 0 ) + { + j_MD5(&v49, v28, &v47); + v8 = 0; + v36 = 16; +LABEL_53: + j_EC_KEY_free(v37); + if ( v7 ) + j_EC_POINT_free(v7); + if ( !v8 ) + { + v37 = (*(int (__fastcall **)(int, int))(*(_DWORD *)v6 + 124))(v6, v40); + v7 = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)v6 + 704))(v6, v38); + (*(void (__fastcall **)(int, int, _DWORD, const char *, char *, int))(*(_DWORD *)v6 + 832))( + v6, + v7, + 0, + v38, + &aJavaOicqWlogin_1[(_DWORD)&v42 + 27], + v35); + v38 = "([B)V"; + v39 = 704; + v30 = (*(int (__fastcall **)(int, int, const char *, const char *))(*(_DWORD *)v6 + 132))( + v6, + v37, + "set_c_pub_key", + "([B)V"); + v42 = 832; + if ( v30 ) + goto LABEL_61; + v29 = 5; + goto LABEL_59; + } + goto LABEL_56; + } + j___android_log_print(4, "wlogin_sdk", "ERROR:Gene ShareKey failed: %d", v28); + v36 = 128; + } + else + { + j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_get0_private_key failed."); + v10 = (_BYTE *)(&stru_3F8 + 8); + v36 = 128; + } + v13 = 7; + goto LABEL_51; + } + j___android_log_print(4, "wlogin_sdk", "ERROR:EC_POINT_point2oct failed, pubkey len:%d.", v38); + v36 = 128; + v38 = (_BYTE *)(&stru_3F8 + 8); + v10 = (_BYTE *)(&stru_3F8 + 8); + v23 = 6; + } + else + { + j___android_log_print(4, "wlogin_sdk", "ERROR:EC_KEY_get0_group failed"); + v10 = (_BYTE *)(&stru_3F8 + 8); + v36 = 128; + v38 = (_BYTE *)(&stru_3F8 + 8); + v23 = 4; + } +LABEL_45: + v8 = -v23; + v7 = 0; + goto LABEL_53; + } + v45 = 512; + v14 = j_strlen((char *)&_stack_chk_fail + (_DWORD)&v42); + String2Buffer((char *)&_stack_chk_fail + (_DWORD)&v42, v14, &v48, &v45); + v15 = j_BN_mpi2bn(&v48, v45, 0); + if ( !v15 ) + { + j___android_log_print(4, "wlogin_sdk", "BN_mpi2bn failed"); + v16 = 1; +LABEL_24: + v20 = -v16; + goto LABEL_34; + } + if ( j_EC_KEY_set_private_key(v37, v15) != 1 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_KEY_set_private_key failed %d"); + j_BN_free(v15); + v16 = 2; + goto LABEL_24; + } + j_BN_free(v15); + v17 = j_EC_KEY_get0_group(v37); + v18 = v17; + if ( !v17 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_KEY_get0_group failed"); + v16 = 3; + goto LABEL_24; + } + v19 = j_EC_POINT_new(v17); + if ( !v19 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_POINT_new failed"); + v16 = 4; + goto LABEL_24; + } + if ( v51 ) + { + v46 = 512; + v21 = j_strlen(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27]); + String2Buffer(&aJavaOicqWlogin_1[(_DWORD)&v42 + 27], v21, &v49, &v46); + if ( j_EC_POINT_oct2point(v18, v19, &v49, v46, 0) != 1 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_POINT_oct2point failed ret %d"); + v22 = 5; + goto LABEL_32; + } + } + else + { + v34 = 0; + v35 = 0; + if ( j_EC_POINT_mul(v18, v19, 0) != 1 ) + { + j___android_log_print(4, "wlogin_sdk", "EC_POINT_mul failed ret %d"); + v22 = 6; + goto LABEL_32; + } + } + v20 = 0; + if ( j_EC_KEY_set_public_key(v37, v19) == 1 ) + goto LABEL_33; + j___android_log_print(4, "wlogin_sdk", "EC_KEY_set_public_key failed ret %d"); + v22 = 7; +LABEL_32: + v20 = -v22; +LABEL_33: + j_EC_POINT_free(v19); + if ( !v20 ) + goto LABEL_35; +LABEL_34: + j___android_log_print(4, "wlogin_sdk", "setECKey failed ret %d", v20, v34, v35); + v10 = (_BYTE *)(&stru_3F8 + 8); + v36 = 128; + v38 = (_BYTE *)(&stru_3F8 + 8); + v23 = 3; + goto LABEL_45; + } + v29 = 1; +LABEL_59: + result = -v29; + while ( v53 != *v43 ) + { +LABEL_61: + (*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 244))(v6, v40); + (*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 92))(v6, v7); + if ( v41 ) + { + v7 = (*(int (__fastcall **)(int, char *))(*(_DWORD *)v6 + v39))(v6, v10); + (*(void (__fastcall **)(int, int, int, char *, char *))(*(_DWORD *)v6 + v42))( + v6, + v7, + v8, + v10, + (char *)&_stack_chk_fail + (_DWORD)&v42); + v8 = *(_DWORD *)(*(_DWORD *)v6 + 132); + v32 = ((int (__fastcall *)(int, int, const char *, const char *))v8)(v6, v37, "set_c_pri_key", v38); + if ( !v32 ) + { + v29 = 7; + goto LABEL_59; + } + v8 = *(_DWORD *)(*(_DWORD *)v6 + 244); + ((void (__fastcall *)(int, int, int, int))v8)(v6, v40, v32, v7); + (*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 92))(v6, v7); + } + v7 = (*(int (__fastcall **)(int, signed int))(*(_DWORD *)v6 + v39))(v6, v36); + (*(void (__fastcall **)(int, int, _DWORD, signed int, char *))(*(_DWORD *)v6 + v42))(v6, v7, 0, v36, &v47); + v10 = *(char **)(*(_DWORD *)v6 + 132); + v33 = ((int (__fastcall *)(int, int, const char *, const char *))v10)(v6, v37, "set_g_share_key", "([B)V"); + if ( !v33 ) + { + v29 = 8; + goto LABEL_59; + } + v10 = *(char **)(*(_DWORD *)v6 + 244); + ((void (__fastcall *)(int, int, int, int))v10)(v6, v40, v33, v7); + (*(void (__fastcall **)(int, int))(*(_DWORD *)v6 + 92))(v6, v7); + result = 0; + } + return result; +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt index 204d62e61..65c9500ed 100644 --- a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/contact/GroupIdConversionsKtTest.kt @@ -2,13 +2,12 @@ package net.mamoe.mirai.contact import net.mamoe.mirai.test.shouldBeEqualTo import org.junit.Test -import kotlin.random.Random @UseExperimental(ExperimentalUnsignedTypes::class) internal class GroupIdConversionsKtTest { @Test fun checkToInternalId() { - GroupId(221056495u).toInternalId().value shouldBeEqualTo 4111056495u + GroupId(221056495).toInternalId().value shouldBeEqualTo 4111056495 // 61 056495 //4111 056495 } diff --git a/mirai-debug/build.gradle.kts b/mirai-debug/build.gradle.kts index 1d30f6e05..2ccfe8a02 100644 --- a/mirai-debug/build.gradle.kts +++ b/mirai-debug/build.gradle.kts @@ -35,12 +35,14 @@ kotlin { } } -fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" +fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" -fun DependencyHandlerScope.ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" +fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" dependencies { - implementation(project(":mirai-core")) + + implementation(files("../mirai-core-timpc/build/classes/kotlin/jvm/main")) // IDE bug + implementation(project(":mirai-core-timpc")) // runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") diff --git a/mirai-debug/src/main/kotlin/hex/HexComparator.kt b/mirai-debug/src/main/kotlin/hex/HexComparator.kt index a04a3afdb..dd826fe32 100644 --- a/mirai-debug/src/main/kotlin/hex/HexComparator.kt +++ b/mirai-debug/src/main/kotlin/hex/HexComparator.kt @@ -1,40 +1,327 @@ -@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant") +@file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection", "unused", "EXPERIMENTAL_UNSIGNED_LITERALS") package hex +import kotlinx.io.core.toByteArray +import net.mamoe.mirai.timpc.network.TIMProtocol import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.internal.printCompareHex +import net.mamoe.mirai.utils.io.hexToBytes +import net.mamoe.mirai.utils.io.read +import net.mamoe.mirai.utils.io.readUVarInt +import net.mamoe.mirai.utils.io.toUHexString +import kotlin.math.max +import kotlin.reflect.KProperty0 +/** + * 匹配已知 hex 常量并格式化后打印到控制台. + * + * 低效率, 仅调试使用. + */ +internal fun String.printColorize(ignoreUntilFirstConst: Boolean): String = with(HexComparator) { colorize(ignoreUntilFirstConst) } -@UseExperimental(MiraiInternalAPI::class) -fun main() { -// println(HexComparator.printColorize("00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00")) - while (true) { - println("Hex1: ") - val hex1 = readLine()!! - println("Hex2: ") - val hex2 = readLine()!! - println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") - println(printCompareHex(hex1.toUpperCase(), hex2.toUpperCase())) - println() +/** + * 比较两个 hex 并格式化后打印到控制台. + * + * 低效率, 仅调试使用. + */ +@MiraiInternalAPI +fun printCompareHex(hex1s: String, hex2s: String): String = with(HexComparator) { + compare( + hex1s.toUpperCase(), + hex2s.toUpperCase() + ) +} + +data class NamedHexElement( + val name: String, + val value: String +) + +/** + * 初始化用于匹配的 Hex 列表 + */ +private fun LinkedHashSet.initConstFileds() { + listOf( + TestConsts, + TIMProtocol, + PacketIds + ).forEach { obj -> + obj::class.members.filterIsInstance>().forEach { property -> + property.get()?.let { add(NamedHexElement(property.name, it.toString())) } + } } - /* - System.out.println(HexComparator.compare( - //mirai +} - "2A 22 96 29 7B 00 40 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 EC 21 40 06 18 89 54 BC Protocol.messageConst1 00 00 01 00 0A 01 00 07 E7 89 9B E9 80 BC 21\n" - , - //e - "2A 22 96 29 7B 00 3F 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 6B 8E 1A FE 39 0B FC Protocol.messageConst1 00 00 01 00 0A 01 00 07 6D 65 73 73 61 67 65" - )); - */ +private object TestConsts { + val NIU_BI = "牛逼".toByteArray().toUHexString() + val _1994701021 = 1994701021u.toUHexString(" ") + val _1040400290 = 1040400290u.toUHexString(" ") + val _580266363 = 580266363u.toUHexString(" ") + val _761025446 = 761025446u.toUHexString() + + val _1040400290_ = "3E 03 3F A2" + val _1994701021_ = "76 E4 B8 DD" + val _jiahua_ = "B1 89 BE 09" + val _Him188moe_ = "Him188moe".toByteArray().toUHexString() + val 发图片2 = "发图片2".toByteArray().toUHexString() + val 发图片群 = "发图片群".toByteArray().toUHexString() + val 发图片 = "发图片".toByteArray().toUHexString() + val 群 = "群".toByteArray().toUHexString() + val 你好 = "你好".toByteArray().toUHexString() + + val MESSAGE_TAIL_10404 = + "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00" + .replace(" ", " ") + + val FONT_10404 = "E5 BE AE E8 BD AF E9 9B 85 E9 BB 91" + + val varint580266363 = "FB D2 D8 94 02" + val varint1040400290 = "A2 FF 8C F0 03" + val varint1994701021 = "DD F1 92 B7 07" + val varint761025446 = 761025446u.toUHexString().hexToBytes().read { readUVarInt() }.toUHexString() +} + +@Suppress("SpellCheckingInspection") +private object PacketIds { + val heartbeat = "00 58" + val friendmsgsend = "00 CD" + val friendmsgevent = "00 CE" + val groupmsg = "00 02" +} + +/** + * Hex 比较器, 并着色已知常量 + * + * This could be used to check packet encoding.. + * but better to run under UNIX + * + * @author NaturalHG + * @author Him188moe + */ +private object HexComparator { + + private val RED = "\u001b[31m" + private val GREEN = "\u001b[33m" + private val UNKNOWN_COLOR = "\u001b[30m" + private val BLUE = "\u001b[34m" + + @Suppress("unused") + private class ConstMatcher constructor(hex: String) { + private val matches = linkedSetOf() + + fun getMatchedConstName(hexNumber: Int): String? { + for (match in this.matches) { + if (match.range.contains(hexNumber)) { + return match.constName + } + } + return null + } + + private class Match internal constructor(val range: IntRange, val constName: String) + + init { + CONST_FIELDS.forEach { (name, value) -> + for (match in match(hex, value)) { + matches.add(Match(match, name)) + } + } + } + + companion object { + val CONST_FIELDS: MutableSet = linkedSetOf().apply { initConstFileds() } + } + + private fun match(hex: String, value: String): Set { + val constValue: String + try { + constValue = value.trim { it <= ' ' } + if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes + return linkedSetOf() + } + } catch (ignored: ClassCastException) { + return linkedSetOf() + } + + return mutableSetOf().apply { + var index = -1 + index = hex.indexOf(constValue, index + 1) + while (index != -1) { + add(IntRange(index / 3, (index + constValue.length) / 3)) + + index = hex.indexOf(constValue, index + 1) + } + } + } + } + + private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) { + //System.out.println(constMatcher.matches); + var i = 0 + while (i < length) { + constNameBuilder.append(" ") + val match = constMatcher.getMatchedConstName(i / 4) + if (match != null) { + var appendedNameLength = match.length + constNameBuilder.append(match) + while (match == constMatcher.getMatchedConstName(i++ / 4)) { + if (appendedNameLength-- < 0) { + constNameBuilder.append(" ") + } + } + + constNameBuilder.append(" ".repeat(match.length % 4)) + } + i++ + } + } + + fun compare(hex1s: String, hex2s: String): String { + val builder = StringBuilder() + + val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val constMatcher1 = ConstMatcher(hex1s) + val constMatcher2 = ConstMatcher(hex2s) + + if (hex1.size == hex2.size) { + builder.append(GREEN).append("长度一致:").append(hex1.size) + } else { + builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size) + } - /* - System.out.println(HexComparator.compare( - //e - "90 5E 39 DF 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 7B 7B 7B 7B 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC", - //mirai - "6F 0B DF 92 00 02 76 E4 B8 DD 00 00 04 53 00 00 00 01 00 00 15 85 00 00 01 55 35 05 8E C9 BA 16 D0 01 63 5B 59 4B 59 52 31 01 B9 00 00 00 00 00 00 00 00 00 00 00 00 00 E9 E9 E9 E9 00 00 00 00 00 00 00 00 00 10 15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B AA BB CC DD EE FF AA BB CC\n\n\n" - ));*/ + val numberLine = StringBuilder() + val hex1ConstName = StringBuilder() + val hex1b = StringBuilder() + val hex2b = StringBuilder() + val hex2ConstName = StringBuilder() + var dif = 0 + + val length = max(hex1.size, hex2.size) * 4 + buildConstNameChain(length, constMatcher1, hex1ConstName) + buildConstNameChain(length, constMatcher2, hex2ConstName) + + + for (i in 0 until max(hex1.size, hex2.size)) { + var h1: String? = null + var h2: String? = null + var isDif = false + if (hex1.size <= i) { + h1 = RED + "__" + isDif = true + } else { + val matchedConstName = constMatcher1.getMatchedConstName(i) + if (matchedConstName != null) { + h1 = BLUE + hex1[i] + } + } + if (hex2.size <= i) { + h2 = RED + "__" + isDif = true + } else { + val matchedConstName = constMatcher2.getMatchedConstName(i) + if (matchedConstName != null) { + h2 = BLUE + hex2[i] + } + } + + if (h1 == null && h2 == null) { + h1 = hex1[i] + h2 = hex2[i] + if (h1 == h2) { + h1 = GREEN + h1 + h2 = GREEN + h2 + } else { + h1 = RED + h1 + h2 = RED + h2 + isDif = true + } + } else { + if (h1 == null) { + h1 = RED + hex1[i] + } + if (h2 == null) { + h2 = RED + hex2[i] + } + } + + numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ") + hex1b.append(" ").append(h1).append(" ") + hex2b.append(" ").append(h2).append(" ") + if (isDif) { + ++dif + } + + //doConstReplacement(hex1b); + //doConstReplacement(hex2b); + } + + return builder.append(" ").append(dif).append(" 个不同").append("\n") + .append(numberLine).append("\n") + .append(hex1ConstName).append("\n") + .append(hex1b).append("\n") + .append(hex2b).append("\n") + .append(hex2ConstName).append("\n") + .toString() + + + } + + fun String.colorize(ignoreUntilFirstConst: Boolean = false): String { + val builder = StringBuilder() + + val hex = trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray() + val constMatcher1 = ConstMatcher(this) + + val numberLine = StringBuilder() + val hex1ConstName = StringBuilder() + val hex1b = StringBuilder() + + buildConstNameChain(length, constMatcher1, hex1ConstName) + + var firstConst: String? = null + var constNameOffset = 0//已经因为还没到第一个const跳过了几个char + for (i in hex.indices) { + var h1: String? = null + + val matchedConstName = constMatcher1.getMatchedConstName(i) + if (matchedConstName != null) { + firstConst = firstConst ?: matchedConstName + h1 = BLUE + hex[i] + } + + if (!ignoreUntilFirstConst || firstConst != null) {//有过任意一个 Const + if (h1 == null) { + h1 = GREEN + hex[i] + } + numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ") + hex1b.append(" ").append(h1).append(" ") + } else { + constNameOffset++ + } + } + + return builder.append("\n") + .append(numberLine).append("\n") + .append(if (firstConst == null) hex1ConstName else { + with(hex1ConstName) { + val index = indexOf(firstConst) + if (index == -1) toString() else " " + substring(index, length) + } + }).append("\n") + .append(hex1b).append("\n") + .toString() + } + + + private fun getFixedNumber(number: Int): String { + if (number < 10) { + return "00$number" + } + return if (number < 100) { + "0$number" + } else number.toString() + } + } \ No newline at end of file diff --git a/mirai-debug/src/main/kotlin/test/JaptTest.kt b/mirai-debug/src/main/kotlin/test/JaptTest.kt index cbe4f4e08..3366b549c 100644 --- a/mirai-debug/src/main/kotlin/test/JaptTest.kt +++ b/mirai-debug/src/main/kotlin/test/JaptTest.kt @@ -1,6 +1,6 @@ package test -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult +import net.mamoe.mirai.network.data.LoginResult @Suppress("RedundantSuspendModifier") suspend fun suspendPrintln(arg: String) = println(arg) diff --git a/mirai-debug/src/main/kotlin/test/ProfileTest.kt b/mirai-debug/src/main/kotlin/test/ProfileTest.kt index 3d617f98e..c3dbad5b1 100644 --- a/mirai-debug/src/main/kotlin/test/ProfileTest.kt +++ b/mirai-debug/src/main/kotlin/test/ProfileTest.kt @@ -9,7 +9,6 @@ import net.mamoe.mirai.utils.io.toUHexString @ExperimentalStdlibApi @Suppress("EXPERIMENTAL_API_USAGE") fun main() { - val newMap = "4E 22 00 03 E5 AE 89 4E 25 00 06 35 31 31 34 39 35 4E 26 00 01 2D 4E 27 00 01 2D 4E 29 00 01 02 4E 2A 00 06 56 69 76 69 61 6E 4E 2B 00 10 31 35 36 31 34 38 39 31 33 40 71 71 2E 63 6F 6D 4E 2D 00 1D 68 74 74 70 3A 2F 2F 31 35 36 31 34 38 39 31 33 2E 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 4E 2E 00 02 33 00 4E 2F 00 04 33 33 39 00 4E 30 00 01 2D 4E 31 00 01 00 4E 33 00 2D E6 88 91 E7 95 99 E9 95 BF E7 9A 84 E5 A4 B4 E5 8F 91 EF BC 8C E6 98 AF E4 BD A0 E9 94 99 E8 BF 87 E7 9A 84 E5 B9 B4 E5 8D 8E 2E 2E 2E 4E 35 00 18 E5 B9 BF E4 B8 9C E6 8A 80 E6 9C AF E5 B8 88 E8 8C 83 E5 AD A6 E9 99 A2 4E 36 00 01 0A 4E 37 00 01 03 4E 38 00 01 01 4E 3F 00 04 07 C2 0B 02 4E 40 00 0C 00 00 00 31 00 00 34 34 00 00 00 33 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 21 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 13 88 02 02 52 0F 00 14 00 00 00 00 00 00 00 00 12 05 10 58 89 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 31 00 00 34 34 00 00 31 34 5D C8 00 1E E7 B4 A2 E5 B0 BC EF BC 88 E4 B8 AD E5 9B BD EF BC 89 E6 9C 89 E9 99 90 E5 85 AC E5 8F B8 65 97 00 01 11 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 00 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00" .hexToBytes().read { diff --git a/mirai-demos/mirai-demo-1/build.gradle b/mirai-demos/mirai-demo-1/build.gradle index f6ea91580..91f064334 100644 --- a/mirai-demos/mirai-demo-1/build.gradle +++ b/mirai-demos/mirai-demo-1/build.gradle @@ -2,8 +2,10 @@ apply plugin: "kotlin" apply plugin: "java" dependencies { - api project(":mirai-core") - // runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE + + implementation files("../../mirai-core-timpc/build/classes/kotlin/jvm/main") // IDE bug + implementation project(":mirai-core-timpc") + api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion } diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt index 1e71176cb..ac03f7a3f 100644 --- a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt +++ b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt @@ -10,10 +10,14 @@ import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.event.* -import net.mamoe.mirai.message.* -import net.mamoe.mirai.network.protocol.timpc.packet.action.uploadImage -import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage -import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage +import net.mamoe.mirai.message.FriendMessage +import net.mamoe.mirai.message.GroupMessage +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.ImageId +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.firstOrNull +import net.mamoe.mirai.message.sendAsImageTo +import net.mamoe.mirai.timpc.TIMPC import net.mamoe.mirai.utils.suspendToExternalImage import java.io.File @@ -25,7 +29,7 @@ private fun readTestAccount(): BotAccount? { val lines = file.readLines() return try { - BotAccount(lines[0].toUInt(), lines[1]) + BotAccount(lines[0].toLong(), lines[1]) } catch (e: IndexOutOfBoundsException) { null } @@ -33,9 +37,9 @@ private fun readTestAccount(): BotAccount? { @Suppress("UNUSED_VARIABLE") suspend fun main() { - val bot = Bot( + val bot = TIMPC.Bot( readTestAccount() ?: BotAccount(//填写你的账号 - id = 1994701121u, + id = 1994701121, password = "123456" ) ).alsoLogin { @@ -211,37 +215,65 @@ suspend fun directlySubscribe(bot: Bot) { "复读" in it.message -> it.sender.sendMessage(it.message) - "发群消息" in it.message -> 580266363u.group().sendMessage(it.message.toString().substringAfter("发群消息")) + "发群消息" in it.message -> 580266363.group().sendMessage(it.message.toString().substringAfter("发群消息")) "上传群图片" in it.message -> withTimeoutOrNull(5000) { val filename = it.message.toString().substringAfter("上传群图片") val image = File( "C:\\Users\\Him18\\Desktop\\$filename" ).suspendToExternalImage() - 920503456u.group().uploadImage(image) + 920503456.group().uploadImage(image) it.reply(image.groupImageId.value) delay(100) - 920503456u.group().sendMessage(Image(image.groupImageId)) + 920503456.group().sendMessage(Image(image.groupImageId)) } "发群图片" in it.message -> { - 920503456u.group().sendMessage(Image(ImageId(it.message.toString().substringAfter("发群图片")))) + 920503456.group().sendMessage( + Image( + ImageId( + it.message.toString().substringAfter( + "发群图片" + ) + ) + ) + ) } "发好友图片" in it.message -> { - it.reply(Image(ImageId(it.message.toString().substringAfter("发好友图片")))) + it.reply( + Image( + ImageId( + it.message.toString().substringAfter( + "发好友图片" + ) + ) + ) + ) } /*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> image.upload(session, Group(session.bot, 580266363)).of() })*/ - it.message eq "发图片群2" -> 580266363u.group().sendMessage(Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))) + it.message eq "发图片群2" -> 580266363.group().sendMessage( + Image( + ImageId( + "{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg" + ) + ) + ) /* it.event eq "发图片" -> sendFriendMessage(it.sentBy, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image -> image.upload(session, it.sentBy).of() })*/ - it.message eq "发图片2" -> it.reply(PlainText("test") + Image(ImageId("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))) + it.message eq "发图片2" -> it.reply( + PlainText("test") + Image( + ImageId( + "{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg" + ) + ) + ) else -> { } diff --git a/mirai-demos/mirai-demo-android/build.gradle b/mirai-demos/mirai-demo-android/build.gradle index 5d39bd241..439fde6ca 100644 --- a/mirai-demos/mirai-demo-android/build.gradle +++ b/mirai-demos/mirai-demo-android/build.gradle @@ -40,7 +40,7 @@ kotlin { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib" - implementation project(':mirai-core') + implementation project(':mirai-core-timpc') implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt index 40aae2aee..fd4c98c63 100644 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt @@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(),LoginCallback { } binder?.setCallback(this) if (!needCaptcha){ - val qq = et_qq.text.toString().toUInt() + val qq = et_qq.text.toString().toLong() val pwd = et_pwd.text.toString() binder?.startLogin(qq, pwd) }else{ diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt index f3eb34d91..e28643614 100644 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt @@ -12,13 +12,9 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.io.core.readBytes import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.login -import net.mamoe.mirai.message.Image -import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage -import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult +import net.mamoe.mirai.network.data.LoginResult +import net.mamoe.mirai.timpc.TIMPC import java.lang.ref.WeakReference class MiraiService : Service() { @@ -43,9 +39,9 @@ class MiraiService : Service() { } - private fun login(qq: UInt, pwd: String) { + private fun login(qq: Long, pwd: String) { GlobalScope.launch { - mBot = Bot(qq, pwd).apply { + mBot = TIMPC.Bot(qq, pwd).apply { val loginResult = login { captchaSolver = { val bytes = it.readBytes() @@ -64,75 +60,12 @@ class MiraiService : Service() { mBot.subscribeMessages { - content({ true }) { + always { mCallback?.get()?.onMessage("收到来自${sender.id}的消息") } // 当接收到消息 == "你好" 时就回复 "你好!" "你好" reply "你好!" - - // 当消息 == "查看 subject" 时, 执行 lambda - case("查看 subject") { - if (subject is QQ) { - reply("消息主体为 QQ, 你在跟发私聊消息") - } else { - reply("消息主体为 Group, 你在群里发消息") - } - - // 在回复的时候, 一般使用 subject 来作为回复对象. - // 因为当群消息时, subject 为这个群. - // 当好友消息时, subject 为这个好友. - // 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标 - } - - - // 当消息里面包含这个类型的消息时 - has { - // this: MessagePacket - // message: MessageChain - // sender: QQ - // it: String (MessageChain.toString) - - if (this is GroupMessage) { - //如果是群消息 - // group: Group - this.group.sendMessage("你在一个群里") - // 等同于 reply("你在一个群里") - } - - reply("图片, ID= ${message[Image].id}")//获取第一个 Image 类型的消息 - reply(message) - } - - - "123" containsReply "你的消息里面包含 123" - - - // 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String - "我的qq" reply { sender.id } - - - // 当消息前缀为 "我是" 时 - startsWith("我是", removePrefix = true) { - // it: 删除了消息前缀 "我是" 后的消息 - // 如一条消息为 "我是张三", 则此时的 it 为 "张三". - - reply("你是$it") - } - - - // 当消息中包含 "复读" 时 - contains("复读") { - reply(message) - } - - - // 自定义的 filter, filter 中 it 为转为 String 的消息. - // 也可以用任何能在处理时使用的变量, 如 subject, sender, message - content({ it.length == 3 }) { - reply("你发送了长度为 3 的消息") - } - } } @@ -146,7 +79,7 @@ class MiraiService : Service() { inner class MiraiBinder : Binder() { - fun startLogin(qq: UInt, pwd: String) { + fun startLogin(qq: Long, pwd: String) { login(qq, pwd) } diff --git a/mirai-demos/mirai-demo-gentleman/build.gradle b/mirai-demos/mirai-demo-gentleman/build.gradle index 0f18b4236..796968ac7 100644 --- a/mirai-demos/mirai-demo-gentleman/build.gradle +++ b/mirai-demos/mirai-demo-gentleman/build.gradle @@ -3,8 +3,8 @@ apply plugin: "java" apply plugin: "application" dependencies { - api project(":mirai-core") - //runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // classpath is not set correctly by IDE + implementation files("../../mirai-core-timpc/build/classes/kotlin/jvm/main") // IDE bug + implementation project(":mirai-core-timpc") implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt index 710c0b612..53b4aefc2 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt @@ -3,7 +3,7 @@ package demo.gentleman import com.alibaba.fastjson.JSON import kotlinx.coroutines.* import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.message.Image +import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.uploadAsImage import org.jsoup.Jsoup diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt index 37a9055e4..b1fcc6056 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt @@ -17,7 +17,7 @@ private const val IMAGE_BUFFER_CAPACITY: Int = 5 */ @ExperimentalUnsignedTypes @ExperimentalCoroutinesApi -object Gentlemen : MutableMap by mutableMapOf() { +object Gentlemen : MutableMap by mutableMapOf() { fun provide(key: Contact): Gentleman = this.getOrPut(key.id) { Gentleman(key) } } diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt index f901da7da..3fd6a4d3a 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt @@ -7,16 +7,22 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import net.mamoe.mirai.* +import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.Subscribable +import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeGroupMessages import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.message.* -import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage -import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage -import net.mamoe.mirai.network.protocol.timpc.packet.event.ReceiveFriendAddRequestEvent +import net.mamoe.mirai.message.FriendMessage +import net.mamoe.mirai.message.GroupMessage +import net.mamoe.mirai.message.data.At +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.buildXMLMessage +import net.mamoe.mirai.message.data.getValue +import net.mamoe.mirai.message.sendAsImageTo +import net.mamoe.mirai.timpc.TIMPC import java.io.File import java.util.* import javax.swing.filechooser.FileSystemView @@ -30,7 +36,7 @@ private fun readTestAccount(): BotAccount? { val lines = file.readLines() return try { - BotAccount(lines[0].toUInt(), lines[1]) + BotAccount(lines[0].toLong(), lines[1]) } catch (e: Exception) { null } @@ -38,9 +44,9 @@ private fun readTestAccount(): BotAccount? { @Suppress("UNUSED_VARIABLE") suspend fun main() { - val bot = Bot( + val bot = TIMPC.Bot( readTestAccount() ?: BotAccount( - id = 913366033u, + id = 913366033, password = "a18260132383" ) ).alsoLogin() @@ -86,7 +92,7 @@ suspend fun main() { startsWith("profile", removePrefix = true) { val account = it.trim() if (account.isNotEmpty()) { - account.toUInt().qq() + account.toLong().qq() } else { sender }.queryProfile().toString().reply() @@ -168,7 +174,7 @@ suspend fun main() { } startsWith("添加好友", removePrefix = true) { - reply(bot.addFriend(it.toUInt()).toString()) + reply(bot.addFriend(it.toLong()).toString()) } } diff --git a/settings.gradle b/settings.gradle index a8f84dc13..e01862775 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ pluginManagement { rootProject.name = 'mirai' include(':mirai-core') +include(':mirai-core-timpc') include(':mirai-console') //include(':mirai-api')