Commit 2f67f836 authored by Him188's avatar Him188

Rewrite

parent da18aafa
...@@ -31,17 +31,18 @@ repositories{ ...@@ -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/) 您需要将 `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 目前还处于实验性阶段, 建议您时刻保持最新版本.
现在 Mirai 只支持 TIM PC 协议.
**common** **common**
```kotlin ```kotlin
implementation("net.mamoe:mirai-core-common:VERSION") implementation("net.mamoe:mirai-core-timpc-common:VERSION")
``` ```
**jvm** **jvm**
```kotlin ```kotlin
implementation("net.mamoe:mirai-core-jvm:VERSION") implementation("net.mamoe:mirai-core-timpc-jvm:VERSION")
``` ```
**android** **android**
```kotlin ```kotlin
implementation("net.mamoe:mirai-core-android:VERSION") implementation("net.mamoe:mirai-core-timpc-android:VERSION")
``` ```
## Try ## Try
......
...@@ -33,7 +33,7 @@ kotlin { ...@@ -33,7 +33,7 @@ kotlin {
sourceSets["main"].apply { sourceSets["main"].apply {
dependencies { dependencies {
implementation(project(":mirai-core")) implementation(project(":mirai-core-timpc"))
implementation(kotlin("stdlib-jdk8", kotlinVersion)) implementation(kotlin("stdlib-jdk8", kotlinVersion))
implementation(kotlin("stdlib-jdk7", kotlinVersion)) implementation(kotlin("stdlib-jdk7", kotlinVersion))
......
...@@ -17,9 +17,7 @@ import io.ktor.util.pipeline.ContextDsl ...@@ -17,9 +17,7 @@ import io.ktor.util.pipeline.ContextDsl
import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineContext
import io.ktor.util.pipeline.PipelineInterceptor import io.ktor.util.pipeline.PipelineInterceptor
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.addFriend
import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.hexToUBytes import net.mamoe.mirai.utils.io.hexToUBytes
...@@ -40,7 +38,7 @@ fun Application.mirai() { ...@@ -40,7 +38,7 @@ fun Application.mirai() {
} }
mirai("/sendGroupMessage") { mirai("/sendGroupMessage") {
Bot.instanceWhose(qq = param("bot")).getGroup(param<UInt>("group")).sendMessage(param<String>("message")) Bot.instanceWhose(qq = param("bot")).getGroup(param<Long>("group")).sendMessage(param<String>("message"))
call.ok() call.ok()
} }
......
@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
@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
package net.mamoe.mirai.network.protocol.timpc package net.mamoe.mirai.timpc.network
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
...@@ -9,4 +9,5 @@ import java.util.concurrent.Executors ...@@ -9,4 +9,5 @@ import java.util.concurrent.Executors
* *
* JVM: 独立的 4 thread 调度器 * JVM: 独立的 4 thread 调度器
*/ */
internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() internal actual val NetworkDispatcher: CoroutineDispatcher
\ No newline at end of file get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
@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
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<Long, MemberPermission>
) : 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<Member>()
members.forEach { entry: Map.Entry<Long, MemberPermission> ->
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)
)
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.data.Profile
import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.network.protocol.timpc.packet.action.* import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberJoinEventPacket import net.mamoe.mirai.network.data.FriendNameRemark
import net.mamoe.mirai.network.protocol.timpc.packet.event.MemberQuitEvent import net.mamoe.mirai.network.data.GroupInfo
import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.network.data.PreviousNameList
import net.mamoe.mirai.network.sessionKey import net.mamoe.mirai.network.data.Profile
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.sendPacket import net.mamoe.mirai.timpc.TIMPCBot
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.timpc.internal.RawGroupInfo
import net.mamoe.mirai.withSession 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 import kotlin.coroutines.CoroutineContext
internal sealed class ContactImpl : Contact { internal sealed class ContactImpl : Contact {
...@@ -30,22 +35,12 @@ internal sealed class ContactImpl : Contact { ...@@ -30,22 +35,12 @@ internal sealed class ContactImpl : Contact {
internal abstract suspend fun startUpdater() 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") @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 { 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() override val internalId = GroupId(id).toInternalId()
internal lateinit var info: GroupInfo internal lateinit var info: GroupInfo
...@@ -56,19 +51,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr ...@@ -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 announcement: String get() = info.announcement
override val members: ContactList<Member> get() = info.members override val members: ContactList<Member> get() = info.members
override fun getMember(id: UInt): Member = override fun getMember(id: Long): Member =
members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is ${id.toLong()} in group ${groupId.value.toLong()}") members.getOrNull(id) ?: throw NoSuchElementException("No such member whose id is $id in group ${groupId.value}")
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message)) bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
} }
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<GroupImageResponse>()
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<RawGroupInfo>().parseBy(this@GroupImpl).also { info = it } GroupPacket.QueryGroupInfo(qqAccount, internalId, sessionKey).sendAndExpect<RawGroupInfo>().parseBy(this@GroupImpl).also { info = it }
} }
override suspend fun quit(): QuitGroupResponse = bot.withSession { override suspend fun quit(): Boolean = withTIMPCBot {
GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect() GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect<GroupPacket.QuitGroupResponse>().isSuccess
} }
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
...@@ -84,25 +105,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr ...@@ -84,25 +105,45 @@ internal data class GroupImpl internal constructor(override val bot: Bot, val gr
override fun toString(): String = "Group(${this.id})" 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 @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(), ContactImpl(),
QQ, CoroutineScope { QQ, CoroutineScope {
override val bot: TIMPCBot by bot.unsafeWeakRef()
override suspend fun sendMessage(message: MessageChain) = override suspend fun sendMessage(message: MessageChain) =
bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message)) 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<FriendImageResponse>().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<RequestProfileDetailsResponse>().profile RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey).sendAndExpect<RequestProfileDetailsResponse>().profile
} }
override suspend fun queryPreviousNameList(): PreviousNameList = bot.withSession { override suspend fun queryPreviousNameList(): PreviousNameList = withTIMPCBot {
QueryPreviousNamePacket(bot.qqAccount, sessionKey, id).sendAndExpect() 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() QueryFriendRemarkPacket(bot.qqAccount, sessionKey, id).sendAndExpect()
} }
...@@ -114,10 +155,6 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot: ...@@ -114,10 +155,6 @@ internal data class QQImpl @PublishedApi internal constructor(override val bot:
override fun toString(): String = "QQ(${this.id})" override fun toString(): String = "QQ(${this.id})"
} }
@Suppress("FunctionName", "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( ...@@ -130,13 +167,13 @@ internal data class MemberImpl(
) : QQ by delegate, CoroutineScope, Member, ContactImpl() { ) : QQ by delegate, CoroutineScope, Member, ContactImpl() {
override fun toString(): String = "Member(id=${this.id}, group=${group.id}, permission=$permission)" 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 > 0) { "duration must be greater than 0 second" }
require(durationSeconds <= 30 * 24 * 3600) { "duration must be no more than 30 days" } require(durationSeconds <= 30 * 24 * 3600) { "duration must be no more than 30 days" }
if (permission == MemberPermission.OWNER) return false if (permission == MemberPermission.OWNER) return false
val operator = group.getMember(bot.qqAccount) 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) { when (operator.permission) {
MemberPermission.MEMBER -> return false MemberPermission.MEMBER -> return false
MemberPermission.ADMINISTRATOR -> if (permission == MemberPermission.ADMINISTRATOR) return false MemberPermission.ADMINISTRATOR -> if (permission == MemberPermission.ADMINISTRATOR) return false
...@@ -153,7 +190,7 @@ internal data class MemberImpl( ...@@ -153,7 +190,7 @@ internal data class MemberImpl(
// TODO: 2019/12/6 更新群成员信息 // 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<GroupPacket.MuteResponse>() GroupPacket.Mute(qqAccount, group.internalId, sessionKey, id, 0u).sendAndExpect<GroupPacket.MuteResponse>()
} }
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE") @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.io.hexToBytes
import net.mamoe.mirai.utils.solveIpAddress import net.mamoe.mirai.utils.solveIpAddress
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @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 kotlinx.io.core.Closeable
import net.mamoe.mirai.Bot 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 import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/** /**
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.timpc.handler package net.mamoe.mirai.timpc.network.handler
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.data.Packet
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.coroutines.CoroutineContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* 临时数据包处理器
* ```kotlin
* session.addHandler<ClientTouchResponsePacket>{
* toSend { TouchPacket() }
* onExpect {//it: ClientTouchResponsePacket
* //do sth.
* }
* }
* ```
*
* @see BotSession.sendAndExpectAsync
*/
internal class TemporaryPacketHandler<P : Packet, R>( internal class TemporaryPacketHandler<P : Packet, R>(
private val expectationClass: KClass<P>, private val expectationClass: KClass<P>,
private val deferred: CompletableDeferred<R>, private val deferred: CompletableDeferred<R>,
private val fromSession: BotSession, private val checkSequence: UShort? = null,
private val checkSequence: Boolean,
/** /**
* 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行 * 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行
*/ */
private val callerContext: CoroutineContext private val callerContext: CoroutineContext,
private val handler: suspend (P) -> R
) { ) {
private lateinit var toSend: OutgoingPacket internal fun filter(packet: Packet, sequenceId: UShort): Boolean =
expectationClass.isInstance(packet) && if (checkSequence != null) sequenceId == checkSequence else true
private lateinit var handler: suspend (P) -> R internal suspend inline fun doReceivePassingExceptionsToDeferred(packet: Packet) {
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") @Suppress("UNCHECKED_CAST")
val ret = try { val ret = try {
withContext(callerContext) { withContext(callerContext) {
...@@ -72,4 +33,5 @@ internal class TemporaryPacketHandler<P : Packet, R>( ...@@ -72,4 +33,5 @@ internal class TemporaryPacketHandler<P : Packet, R>(
} }
deferred.complete(ret) deferred.complete(ret)
} }
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.network.protocol.timpc.packet package net.mamoe.mirai.timpc.network.packet
/** /**
* 包的最后一次修改时间, 和分析时使用的 TIM 版本 * 包的最后一次修改时间, 和分析时使用的 TIM 版本
......
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.ByteReadPacket
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.decryptBy 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 ...@@ -15,6 +19,7 @@ internal inline class SessionKey(override val value: ByteArray) : DecrypterByteA
/** /**
* [ByteArray] 解密器 * [ByteArray] 解密器
*/ */
@PublishedApi
internal interface DecrypterByteArray : Decrypter { internal interface DecrypterByteArray : Decrypter {
val value: ByteArray val value: ByteArray
override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(value) override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(value)
...@@ -50,4 +55,8 @@ internal interface Decrypter { ...@@ -50,4 +55,8 @@ internal interface Decrypter {
operator fun plus(another: Decrypter): Decrypter = LinkedDecrypter { another.decrypt(this.decrypt(it)) } operator fun plus(another: Decrypter): Decrypter = LinkedDecrypter { another.decrypt(this.decrypt(it)) }
} }
internal interface DecrypterType<D : Decrypter> internal interface DecrypterType<D : Decrypter>
\ No newline at end of file
@PublishedApi
internal inline fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) =
this.encryptAndWrite(key.value, encoder)
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "MemberVisibilityCanBePrivate") @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.io.core.*
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.writeQQ import net.mamoe.mirai.utils.io.writeQQ
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
...@@ -39,13 +39,11 @@ internal abstract class SessionPacketFactory<TPacket : Packet> : PacketFactory<T ...@@ -39,13 +39,11 @@ internal abstract class SessionPacketFactory<TPacket : Packet> : PacketFactory<T
/** /**
* 在 [BotNetworkHandler] 下处理这个包. 广播事件等. * 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
*/ */
open suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {} open suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {}
} }
/** /**
* 构造一个待发送给服务器的数据包. * 构造一个待发送给服务器的数据包.
*
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@JvmOverloads @JvmOverloads
...@@ -76,13 +74,11 @@ internal inline fun PacketFactory<*, *>.buildOutgoingPacket( ...@@ -76,13 +74,11 @@ internal inline fun PacketFactory<*, *>.buildOutgoingPacket(
/** /**
* 构造一个待发送给服务器的会话数据包. * 构造一个待发送给服务器的会话数据包.
*
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@JvmOverloads @JvmOverloads
internal inline fun PacketFactory<*, *>.buildSessionPacket( internal inline fun PacketFactory<*, *>.buildSessionPacket(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
name: String? = null, name: String? = null,
id: PacketId = this.id, id: PacketId = this.id,
...@@ -105,13 +101,11 @@ internal inline fun PacketFactory<*, *>.buildSessionPacket( ...@@ -105,13 +101,11 @@ internal inline fun PacketFactory<*, *>.buildSessionPacket(
/** /**
* 构造一个待发送给服务器的会话数据包. * 构造一个待发送给服务器的会话数据包.
*
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@JvmOverloads @JvmOverloads
internal fun <T> PacketFactory<*, *>.buildSessionProtoPacket( internal fun <T> PacketFactory<*, *>.buildSessionProtoPacket(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
name: String? = null, name: String? = null,
id: PacketId = this.id, id: PacketId = this.id,
......
package net.mamoe.mirai.network.protocol.timpc.packet package net.mamoe.mirai.timpc.network.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
/**
* 一个包的数据 (body)
*/
interface Packet
/** /**
* 被忽略的数据包. * 被忽略的数据包.
*/ */
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
...@@ -10,6 +10,7 @@ import kotlinx.io.pool.useInstance ...@@ -10,6 +10,7 @@ import kotlinx.io.pool.useInstance
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
...@@ -36,7 +37,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt ...@@ -36,7 +37,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
/** /**
* **解码**服务器的回复数据包 * **解码**服务器的回复数据包
*/ */
abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TPacket abstract suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): TPacket
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun <T> ByteReadPacket.decodeProtoPacket( fun <T> ByteReadPacket.decodeProtoPacket(
...@@ -69,7 +70,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt ...@@ -69,7 +70,7 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
} }
internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() { internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
override suspend fun BotNetworkHandler<*>.handlePacket(packet: UnknownPacket) { override suspend fun BotNetworkHandler.handlePacket(packet: UnknownPacket) {
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
packet.body.readAvailable(it) packet.body.readAvailable(it)
bot.logger.debug("UnknownPacket(${packet.id.value.toUHexString()}) = " + it.toUHexString()) bot.logger.debug("UnknownPacket(${packet.id.value.toUHexString()}) = " + it.toUHexString())
...@@ -80,7 +81,7 @@ internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() { ...@@ -80,7 +81,7 @@ internal object UnknownPacketFactory : SessionPacketFactory<UnknownPacket>() {
override suspend fun ByteReadPacket.decode( override suspend fun ByteReadPacket.decode(
id: PacketId, id: PacketId,
sequenceId: UShort, sequenceId: UShort,
handler: BotNetworkHandler<*> handler: BotNetworkHandler
): UnknownPacket { ): UnknownPacket {
return UnknownPacket(id, this) return UnknownPacket(id, this)
} }
...@@ -90,6 +91,6 @@ internal object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() { ...@@ -90,6 +91,6 @@ internal object IgnoredPacketFactory : SessionPacketFactory<IgnoredPacket>() {
override suspend fun ByteReadPacket.decode( override suspend fun ByteReadPacket.decode(
id: PacketId, id: PacketId,
sequenceId: UShort, sequenceId: UShort,
handler: BotNetworkHandler<*> handler: BotNetworkHandler
): IgnoredPacket = IgnoredPacket(id) ): IgnoredPacket = IgnoredPacket(id)
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.timpc.network.packet.action.*
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacketFactory import net.mamoe.mirai.timpc.network.packet.event.EventPacketFactory
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendOnlineStatusChangedPacket import net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket
import net.mamoe.mirai.network.protocol.timpc.packet.login.* import net.mamoe.mirai.timpc.network.packet.login.*
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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 kotlinx.io.core.*
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket 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.utils.io.*
import net.mamoe.mirai.withSession
/** /**
...@@ -22,9 +23,9 @@ import net.mamoe.mirai.withSession ...@@ -22,9 +23,9 @@ import net.mamoe.mirai.withSession
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() { internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
target: UInt target: Long
): OutgoingPacket = buildSessionPacket(bot, sessionKey) { ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeZero(2) writeZero(2)
writeQQ(bot) writeQQ(bot)
...@@ -41,7 +42,7 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList> ...@@ -41,7 +42,7 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>
// [00 00 00 10] 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B // [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 // [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 // 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() val count = readUInt().toInt()
...@@ -54,19 +55,6 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList> ...@@ -54,19 +55,6 @@ internal object QueryPreviousNamePacket : SessionPacketFactory<PreviousNameList>
} }
} }
/**
* 曾用名列表
*
* 曾用名可能是:
* - 昵称
* - 共同群内的群名片
*/
class PreviousNameList(
list: List<String>
) : Packet, List<String> by list {
override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ")
}
// 需要验证消息 // 需要验证消息
// 0065 发送 03 07 57 37 E8 // 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 // 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( ...@@ -79,20 +67,21 @@ class PreviousNameList(
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() { internal object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
qq: UInt, qq: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) { ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeQQ(qq) writeQQ(qq)
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CanAddFriendResponse = override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CanAddFriendResponse =
handler.bot.withSession { with(handler.bot) {
if (remaining > 20) {//todo check 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) // debugDiscardExact(1)
return when (val state = readUByte().toUInt()) { return when (val state = readUByte().toUInt()) {
...@@ -148,7 +137,7 @@ internal sealed class CanAddFriendResponse : EventPacket { ...@@ -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 接受 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, 在添加好友时发出 * 请求一个 32 位 Key, 在添加好友时发出
...@@ -156,8 +145,8 @@ inline class FriendAdditionKey(val value: IoBuffer) ...@@ -156,8 +145,8 @@ inline class FriendAdditionKey(val value: IoBuffer)
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() { internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFriendAdditionKeyPacket.Response>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
qq: UInt, qq: Long,
sessionKey: SessionKey sessionKey: SessionKey
) = buildSessionPacket(bot, sessionKey) { ) = buildSessionPacket(bot, sessionKey) {
//01 00 01 02 B3 74 F6 //01 00 01 02 B3 74 F6
...@@ -165,7 +154,7 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri ...@@ -165,7 +154,7 @@ internal object RequestFriendAdditionKeyPacket : SessionPacketFactory<RequestFri
writeQQ(qq) writeQQ(qq)
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): 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 //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) discardExact(4)
return Response(FriendAdditionKey(readIoBuffer(readUShort().toInt()))) return Response(FriendAdditionKey(readIoBuffer(readUShort().toInt())))
...@@ -183,8 +172,8 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response> ...@@ -183,8 +172,8 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
@PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.11", timVersion = "2.3.2 (21173)")
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestAdd( fun RequestAdd(
bot: UInt, bot: Long,
qq: UInt, qq: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
/** /**
* 验证消息 * 验证消息
...@@ -250,13 +239,13 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response> ...@@ -250,13 +239,13 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
@Suppress("FunctionName") @Suppress("FunctionName")
@PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)")
fun Approve( fun Approve(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
/** /**
* 好友列表分组的组的 ID. "我的好友" 为 0 * 好友列表分组的组的 ID. "我的好友" 为 0
*/ */
friendListId: Short, friendListId: Short,
qq: UInt, qq: Long,
/** /**
* 备注. 不设置则需要为 `null` TODO 需要确认是否还需发送一个设置备注包. 因为测试时若有备注则会多发一个包并且包里面有所设置的备注 * 备注. 不设置则需要为 `null` TODO 需要确认是否还需发送一个设置备注包. 因为测试时若有备注则会多发一个包并且包里面有所设置的备注
*/ */
...@@ -284,7 +273,7 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response> ...@@ -284,7 +273,7 @@ internal object AddFriendPacket : SessionPacketFactory<AddFriendPacket.Response>
} }
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号 //02 02 B3 74 F6 00 //02 B3 74 F6 是QQ号
return Response return Response
} }
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.charsets.Charsets
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.data.ImageId0x06
import net.mamoe.mirai.message.ImageId0x06 import net.mamoe.mirai.message.data.requireLength
import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.Http
import net.mamoe.mirai.utils.io.* 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<FriendImageResponse>().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 // region FriendImageResponse
...@@ -58,7 +24,7 @@ internal interface FriendImageResponse : EventPacket ...@@ -58,7 +24,7 @@ internal interface FriendImageResponse : EventPacket
* 图片数据地址. * 图片数据地址.
*/ */
// TODO: 2019/11/15 应该为 inline class, 但 kotlin 有 bug // 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)" override fun toString(): String = "FriendImageLink($original)"
} }
...@@ -96,9 +62,9 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse { ...@@ -96,9 +62,9 @@ internal object FriendImageOverFileSizeMax : FriendImageResponse {
internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() { internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() {
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestImageId( fun RequestImageId(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
target: UInt, target: Long,
image: ExternalImage image: ExternalImage
): OutgoingPacket = buildSessionPacket( ): OutgoingPacket = buildSessionPacket(
bot, bot,
...@@ -119,8 +85,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() ...@@ -119,8 +85,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
writeTV(0x08_01u) writeTV(0x08_01u)
writeUVarIntLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) { writeUVarIntLVPacket(tag = 0x12u, lengthOffset = { it + 1 }) {
writeTUVarint(0x08u, bot) writeTUVarint(0x08u, bot.toUInt())
writeTUVarint(0x10u, target) writeTUVarint(0x10u, target.toUInt())
writeTV(0x18_00u) writeTV(0x18_00u)
writeTLV(0x22u, image.md5) writeTLV(0x22u, image.md5)
writeTUVarint(0x28u, image.inputSize.toUInt()) writeTUVarint(0x28u, image.inputSize.toUInt())
...@@ -150,7 +116,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() ...@@ -150,7 +116,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestImageLink( fun RequestImageLink(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
imageId: ImageId imageId: ImageId
): OutgoingPacket { ): OutgoingPacket {
...@@ -207,8 +173,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>() ...@@ -207,8 +173,8 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
writeUByte(0x1Au) writeUByte(0x1Au)
writeUByte(0x47u) writeUByte(0x47u)
writeTUVarint(0x08u, bot) writeTUVarint(0x08u, bot.toUInt())
writeTUVarint(0x10u, bot) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事. writeTUVarint(0x10u, bot.toUInt()) // 这里实际上应该是这张图片来自哪个 QQ 号. 但传 bot 也没事.
writeTLV(0x1Au, imageId.value.toByteArray(Charsets.ISO_8859_1)) 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") 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<FriendImageResponse>() ...@@ -217,7 +183,7 @@ internal object FriendImagePacket : SessionPacketFactory<FriendImageResponse>()
override suspend fun ByteReadPacket.decode( override suspend fun ByteReadPacket.decode(
id: PacketId, id: PacketId,
sequenceId: UShort, sequenceId: UShort,
handler: BotNetworkHandler<*> handler: BotNetworkHandler
): FriendImageResponse { ): FriendImageResponse {
// 上传图片, 成功获取ID // 上传图片, 成功获取ID
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @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 kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.Packet import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.PacketId import net.mamoe.mirai.timpc.network.packet.PacketId
import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory
// 0001 // 0001
...@@ -26,7 +26,7 @@ import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory ...@@ -26,7 +26,7 @@ import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory
internal inline class FriendListList(val delegate: List<FriendList>): Packet internal inline class FriendListList(val delegate: List<FriendList>): Packet
internal object QueryFriendListListPacket : SessionPacketFactory<FriendList>() { internal object QueryFriendListListPacket : SessionPacketFactory<FriendList>() {
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. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
} }
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.io.core.writeUByte import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.writeQQ import net.mamoe.mirai.utils.io.writeQQ
/** /**
...@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ ...@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ
*/ */
internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() { internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
operator fun invoke( operator fun invoke(
qq: UInt, qq: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(qq) writeQQ(qq)
...@@ -35,5 +35,5 @@ internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountIn ...@@ -35,5 +35,5 @@ internal object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountIn
override fun toString(): String = "RequestAccountInfoPacket.Response" override fun toString(): String = "RequestAccountInfoPacket.Response"
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response = Response
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "RUNTIME_ANNOTATION_NOT_SUPPORTED") @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.io.core.ByteReadPacket
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable 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.GroupInternalId
import net.mamoe.mirai.contact.withSession import net.mamoe.mirai.message.data.ImageId0x03
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.data.requireLength
import net.mamoe.mirai.message.ImageId0x03
import net.mamoe.mirai.message.requireLength
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.network.protocol.timpc.packet.event.EventPacket import net.mamoe.mirai.network.data.ImageLink
import net.mamoe.mirai.qqAccount 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.ExternalImage
import net.mamoe.mirai.utils.Http
import net.mamoe.mirai.utils.assertUnreachable
import net.mamoe.mirai.utils.io.toUHexString 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<GroupImageResponse>()
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 internal interface GroupImageResponse : EventPacket
// endregion // endregion
...@@ -193,7 +152,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() { ...@@ -193,7 +152,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestImageId( fun RequestImageId(
bot: UInt, bot: Long,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
image: ExternalImage, image: ExternalImage,
sessionKey: SessionKey sessionKey: SessionKey
...@@ -215,7 +174,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() { ...@@ -215,7 +174,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u) private val requestImageLinkHead = ubyteArrayOf(0x08u, 0x01u, 0x12u, 0x03u, 0x98u, 0x01u, 0x2u)
@Suppress("FunctionName") @Suppress("FunctionName")
fun RequestImageLink( fun RequestImageLink(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
imageId: ImageId0x03 imageId: ImageId0x03
): OutgoingPacket { ): OutgoingPacket {
...@@ -242,7 +201,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() { ...@@ -242,7 +201,7 @@ internal object GroupImagePacket : SessionPacketFactory<GroupImageResponse>() {
) )
} }
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 @Serializable
data class GroupImageResponseProto( data class GroupImageResponseProto(
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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 kotlinx.io.core.*
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.contact.internal.Member import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.contact.groupInternalId
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.internal.toPacket import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.internal.RawGroupInfo
import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.MiraiInternalAPI 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.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<Member>
) {
val group: Group get() = _group
val owner: Member get() = _owner
val name: String get() = _name
val announcement: String get() = _announcement
val members: ContactList<Member> get() = _members
override fun toString(): String =
"GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}"
}
internal object GroupNotFound : GroupPacket.InfoResponse { internal object GroupNotFound : GroupPacket.InfoResponse {
override fun toString(): String = "GroupPacket.InfoResponse.GroupNotFound" 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<UInt, MemberPermission>
) : GroupPacket.InfoResponse {
@UseExperimental(MiraiInternalAPI::class)
fun parseBy(group: Group): GroupInfo = group.bot.withSession {
val memberList = LockFreeLinkedList<Member>()
members.forEach { entry: Map.Entry<UInt, MemberPermission> ->
memberList.addLast(entry.key.qq().let { group.Member(it, entry.value, it.coroutineContext) })
}
return GroupInfo(
group,
this@RawGroupInfo.owner.qq().let { 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") @Suppress("FunctionName")
internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() { internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
fun Message( fun Message(
bot: UInt, bot: Long,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
sessionKey: SessionKey, sessionKey: SessionKey,
message: MessageChain message: MessageChain
...@@ -109,7 +55,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -109,7 +55,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
*/ */
@PacketVersion(date = "2019.11.28", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.28", timVersion = "2.3.2 (21173)")
fun QuitGroup( fun QuitGroup(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
group: GroupInternalId group: GroupInternalId
): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QuitGroup") { ): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QuitGroup") {
...@@ -122,7 +68,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -122,7 +68,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
*/ */
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
fun QueryGroupInfo( fun QueryGroupInfo(
bot: UInt, bot: Long,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QueryGroupInfo", headerSizeHint = 9) { ): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "GroupPacket.QueryGroupInfo", headerSizeHint = 9) {
...@@ -136,10 +82,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -136,10 +82,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
*/ */
@PacketVersion(date = "2019.12.2", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.12.2", timVersion = "2.3.2 (21173)")
fun Mute( fun Mute(
bot: UInt, bot: Long,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
sessionKey: SessionKey, sessionKey: SessionKey,
target: UInt, target: Long,
/** /**
* 0 为取消 * 0 为取消
*/ */
...@@ -168,12 +114,22 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -168,12 +114,22 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
internal interface InfoResponse : Packet, GroupPacketResponse internal interface InfoResponse : Packet, GroupPacketResponse
/**
* 退出群的返回
*/
class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, GroupPacketResponse {
val group: GroupInternalId get() = _group ?: error("request failed")
val isSuccess: Boolean get() = _group != null
override fun toString(): String = "GroupPacket.QuitResponse"
}
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
@UseExperimental(ExperimentalStdlibApi::class) @UseExperimental(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode( override suspend fun ByteReadPacket.decode(
id: PacketId, id: PacketId,
sequenceId: UShort, sequenceId: UShort,
handler: BotNetworkHandler<*> handler: BotNetworkHandler
): GroupPacketResponse { ): GroupPacketResponse {
return when (val packetType = readUByte().toUInt()) { return when (val packetType = readUByte().toUInt()) {
0x2Au -> MessageResponse 0x2Au -> MessageResponse
...@@ -181,7 +137,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -181,7 +137,7 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
0x09u -> { 0x09u -> {
if (readByte().toInt() == 0) { if (readByte().toInt() == 0) {
QuitGroupResponse(readUInt().groupInternalId()) QuitGroupResponse(readUInt().toLong().groupInternalId())
} else { } else {
QuitGroupResponse(null) QuitGroupResponse(null)
} }
...@@ -218,10 +174,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -218,10 +174,10 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
76 E4 B8 DD 00 00 76 E4 B8 DD 00 00
*/ */
discardExact(4) // group internal id discardExact(4) // group internal id
val group = readUInt() // group id val group = readUInt().toLong() // group id
discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40 discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
val owner = readUInt() val owner = readUInt().toLong()
discardExact(22) discardExact(22)
val groupName = readUByteLVString() val groupName = readUByteLVString()
...@@ -234,11 +190,11 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon ...@@ -234,11 +190,11 @@ internal object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketRespon
discardExact(50) discardExact(50)
val stop = readUInt() // 标记读取群成员的结束 val stop = readUInt().toLong() // 标记读取群成员的结束
discardExact(1) // 00 discardExact(1) // 00
val members = mutableMapOf<UInt, MemberPermission>() val members = mutableMapOf<Long, MemberPermission>()
do { do {
val qq = readUInt() val qq = readUInt().toLong()
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态 val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
if (qq == owner) { if (qq == owner) {
continue continue
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @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.HttpClient
import io.ktor.client.request.post import io.ktor.client.request.post
...@@ -17,7 +17,7 @@ import net.mamoe.mirai.contact.GroupId ...@@ -17,7 +17,7 @@ import net.mamoe.mirai.contact.GroupId
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
internal suspend inline fun HttpClient.postImage( internal suspend inline fun HttpClient.postImage(
htcmd: String, htcmd: String,
uin: UInt, uin: Long,
groupId: GroupId?, groupId: GroupId?,
imageInput: Input, imageInput: Input,
inputSize: Long, inputSize: Long,
...@@ -30,7 +30,7 @@ internal suspend inline fun HttpClient.postImage( ...@@ -30,7 +30,7 @@ internal suspend inline fun HttpClient.postImage(
path("cgi-bin/httpconn") path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd parameters["htcmd"] = htcmd
parameters["uin"] = uin.toLong().toString() parameters["uin"] = uin.toString()
if (groupId != null) parameters["groupcode"] = groupId.value.toLong().toString() if (groupId != null) parameters["groupcode"] = groupId.value.toLong().toString()
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "NO_REFLECTION_IN_CLASS_PATH") @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 { ...@@ -32,8 +12,8 @@ interface ImageLink {
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2 (21173)")
object SubmitImageFilenamePacket : PacketFactory { object SubmitImageFilenamePacket : PacketFactory {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
target: UInt, target: Long,
filename: String, filename: String,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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 io.ktor.util.date.GMTDate
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.contact.data.Gender import net.mamoe.mirai.network.data.Gender
import net.mamoe.mirai.contact.data.Profile import net.mamoe.mirai.network.data.Profile
import net.mamoe.mirai.network.BotNetworkHandler 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.* import net.mamoe.mirai.utils.io.*
inline class AvatarLink(val value: String) : Packet inline class AvatarLink(val value: String) : Packet
...@@ -23,13 +24,13 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() { ...@@ -23,13 +24,13 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
*/ */
@PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)")
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
qq: UInt qq: Long
): OutgoingPacket = buildSessionPacket(bot, sessionKey) { ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeHex("03 00 00 00 00 00 00 00 00 00 00") writeHex("03 00 00 00 00 00 00 00 00 00 00")
writeByte(1) writeByte(1)
writeUInt(qq) writeQQ(qq)
} }
/** /**
...@@ -38,7 +39,7 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() { ...@@ -38,7 +39,7 @@ internal object QueryNicknamePacket : SessionPacketFactory<NicknameMap>() {
*/ */
@PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.12.7", timVersion = "2.3.2 (21173)")
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
qq: Array<UInt> qq: Array<UInt>
): OutgoingPacket = buildSessionPacket(bot, sessionKey) { ): 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 ...@@ -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 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 //03 00 00 00 00 00 00 00 00 00 00 12 04 14 37
val type = readUByte().toInt() val type = readUByte().toInt()
if (type == 15) { 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 ...@@ -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<AvatarLink>() { internal object RequestProfileAvatarPacket : SessionPacketFactory<AvatarLink>() {
//00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 //00 01 00 17 D4 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
qq: UInt, qq: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) { ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeUShort(0x01u) 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") 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()}") println(" RequestProfileAvatarPacket body=${this.readBytes().toUHexString()}")
TODO() TODO()
} }
...@@ -163,19 +164,19 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil ...@@ -163,19 +164,19 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
//00 01 87 73 86 9D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 //00 01 87 73 86 9D 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
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
qq: UInt, qq: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) { ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeUShort(0x01u) 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") 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")
} }
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): RequestProfileDetailsResponse { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): RequestProfileDetailsResponse {
discardExact(3) discardExact(3)
val qq = readUInt() val qq = readUInt().toLong()
discardExact(6) discardExact(6)
val map = readTLVMap(tagSize = 2, expectingEOF = true) val map = readTLVMap(tagSize = 2, expectingEOF = true)
//map.printTLVMap("Profile(qq=$qq) raw=") //map.printTLVMap("Profile(qq=$qq) raw=")
...@@ -193,7 +194,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil ...@@ -193,7 +194,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
0x02u -> Gender.FEMALE 0x02u -> Gender.FEMALE
0x01u -> Gender.MALE 0x01u -> Gender.MALE
else -> Gender.SECRET // 猜的 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()) }, birthday = map[0x4E3Fu]?.let { GMTDate(it.toUInt().toLong()) },
personalStatement = map[0x4E33u]?.encodeToString(), personalStatement = map[0x4E33u]?.encodeToString(),
...@@ -209,7 +210,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil ...@@ -209,7 +210,7 @@ internal object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfil
} }
internal data class RequestProfileDetailsResponse( internal data class RequestProfileDetailsResponse(
val qq: UInt, val qq: Long,
val profile: Profile val profile: Profile
) : Packet { ) : Packet {
//00 01 00 99 6B F8 D2 00 00 00 00 00 29 //00 01 00 99 6B F8 D2 00 00 00 00 00 29
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @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 kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.network.data.FriendNameRemark
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.readUShortLVString import net.mamoe.mirai.utils.io.readUShortLVString
import net.mamoe.mirai.utils.io.writeQQ import net.mamoe.mirai.utils.io.writeQQ
import net.mamoe.mirai.utils.io.writeZero import net.mamoe.mirai.utils.io.writeZero
/**
* 给好友设置的备注
*/
inline class FriendNameRemark(val value: String) : Packet
internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() { internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>() {
/** /**
* 查询好友的备注 * 查询好友的备注
*/ */
@PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.27", timVersion = "2.3.2 (21173)")
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
target: UInt target: Long
): OutgoingPacket = buildSessionPacket( ): OutgoingPacket = buildSessionPacket(
bot, sessionKey bot, sessionKey
) { ) {
...@@ -32,7 +28,7 @@ internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark> ...@@ -32,7 +28,7 @@ internal object QueryFriendRemarkPacket : SessionPacketFactory<FriendNameRemark>
writeZero(1) 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 //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) discardExact(11)
return FriendNameRemark(readUShortLVString()) return FriendNameRemark(readUShortLVString())
......
@file:Suppress("EXPERIMENTAL_API_USAGE") @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 kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.writeZero import net.mamoe.mirai.utils.io.writeZero
class FriendList : Packet class FriendList : Packet
@PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)")
internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() { internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() {
@PacketVersion(date = "2019.11.24", timVersion = "2.3.2 (21173)")
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket( ): OutgoingPacket = buildSessionPacket(
bot, sessionKey, version = TIMProtocol.version0x02 bot, sessionKey, version = TIMProtocol.version0x02
...@@ -22,7 +23,7 @@ internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() { ...@@ -22,7 +23,7 @@ internal object RequestFriendListPacket : SessionPacketFactory<FriendList>() {
writeZero(4) 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() TODO()
} }
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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 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.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.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.io.*
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() { internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
operator fun invoke( operator fun invoke(
botQQ: UInt, botQQ: Long,
targetQQ: UInt, targetQQ: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
message: MessageChain message: MessageChain
): OutgoingPacket = buildSessionPacket(botQQ, sessionKey) { ): OutgoingPacket = buildSessionPacket(botQQ, sessionKey) {
...@@ -25,14 +26,11 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage ...@@ -25,14 +26,11 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage
writeHex("38 03") writeHex("38 03")
writeQQ(botQQ) writeQQ(botQQ)
writeQQ(targetQQ) writeQQ(targetQQ)
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes())) writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey.value) }.readBytes()))
writeHex("00 0B") writeHex("00 0B")
writeRandom(2) writeRandom(2)
writeTime() writeTime()
writeHex( writeHex("01 1D 00 00 00 00")
"01 1D" +
" 00 00 00 00"
)
//消息过多要分包发送 //消息过多要分包发送
//如果只有一个 //如果只有一个
...@@ -68,5 +66,5 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage ...@@ -68,5 +66,5 @@ internal object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessage
override fun toString(): String = "SendFriendMessagePacket.Response" override fun toString(): String = "SendFriendMessagePacket.Response"
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): Response = Response
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.Packet import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.readBoolean import net.mamoe.mirai.utils.io.readBoolean
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.ConnectionOccupiedEvent
import net.mamoe.mirai.utils.io.encodeToString 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<ConnectionOccupiedEvent>(0x0030u) { internal object ConnectionOccupiedPacketHandler : KnownEventParserAndHandler<ConnectionOccupiedEvent>(0x0030u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ConnectionOccupiedEvent { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): ConnectionOccupiedEvent {
discardExact(6) discardExact(6)
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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 kotlinx.io.core.*
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.network.TIMBotNetworkHandler
import net.mamoe.mirai.network.sessionKey import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.readIoBuffer import net.mamoe.mirai.utils.io.readIoBuffer
...@@ -15,16 +15,16 @@ import net.mamoe.mirai.utils.io.readIoBuffer ...@@ -15,16 +15,16 @@ import net.mamoe.mirai.utils.io.readIoBuffer
* 事件的识别 ID. 在 ACK 时使用 * 事件的识别 ID. 在 ACK 时使用
*/ */
internal class EventPacketIdentity( internal class EventPacketIdentity(
val from: UInt,//对于好友消息, 这个是发送人 val from: Long,//对于好友消息, 这个是发送人
val to: UInt,//对于好友消息, 这个是bot val to: Long,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8 internal val uniqueId: IoBuffer//8
) { ) {
override fun toString(): String = "($from->$to)" override fun toString(): String = "($from->$to)"
} }
internal fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) { internal fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
writeUInt(from) writeUInt(from.toUInt())
writeUInt(to) writeUInt(to.toUInt())
writeFully(uniqueId) writeFully(uniqueId)
} }
...@@ -39,20 +39,16 @@ internal fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> = ...@@ -39,20 +39,16 @@ internal fun matchEventPacketFactory(value: UShort): EventParserAndHandler<*> =
@NoLog @NoLog
@Suppress("FunctionName") @Suppress("FunctionName")
internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) { internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(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( val eventIdentity = EventPacketIdentity(
from = readUInt(), from = readUInt().toLong(), // clear semantic, don't readQQ() or readGroup()
to = readUInt(), to = readUInt().toLong(), // clear semantic
uniqueId = readIoBuffer(8) uniqueId = readIoBuffer(8)
) )
(handler as TIMBotNetworkHandler).socket.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity)) (handler as TIMBotNetworkHandler).socket.sendPacket(EventPacketFactory(id, sequenceId, handler.bot.qqAccount, handler.sessionKey, eventIdentity))
discardExact(2) // 1F 40 discardExact(2) // 1F 40
return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also { return with(matchEventPacketFactory(readUShort())) { parse(handler.bot, eventIdentity) }.also {
if (it is MessagePacket<*, *>) {
it.botVar = handler.bot
}
if (it is EventParserAndHandler<*>) { if (it is EventParserAndHandler<*>) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
with(it as EventParserAndHandler<in Packet>) { with(it as EventParserAndHandler<in Packet>) {
...@@ -68,7 +64,7 @@ internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKe ...@@ -68,7 +64,7 @@ internal object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKe
operator fun invoke( operator fun invoke(
id: PacketId, id: PacketId,
sequenceId: UShort, sequenceId: UShort,
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
identity: EventPacketIdentity identity: EventPacketIdentity
): OutgoingPacket = buildSessionPacket(name = "EventPacket", id = id, sequenceId = sequenceId, bot = bot, sessionKey = sessionKey) { ): OutgoingPacket = buildSessionPacket(name = "EventPacket", id = id, sequenceId = sequenceId, bot = bot, sessionKey = sessionKey) {
...@@ -84,7 +80,7 @@ internal interface EventParserAndHandler<TPacket : Packet> { ...@@ -84,7 +80,7 @@ internal interface EventParserAndHandler<TPacket : Packet> {
/** /**
* 在 [BotNetworkHandler] 下处理这个包. 广播事件等. * 在 [BotNetworkHandler] 下处理这个包. 广播事件等.
*/ */
suspend fun BotNetworkHandler<*>.handlePacket(packet: TPacket) {} suspend fun BotNetworkHandler.handlePacket(packet: TPacket) {}
} }
internal abstract class KnownEventParserAndHandler<TPacket : Packet>(override val id: UShort) : EventParserAndHandler<TPacket> { internal abstract class KnownEventParserAndHandler<TPacket : Packet>(override val id: UShort) : EventParserAndHandler<TPacket> {
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.event.events.ReceiveFriendAddRequestEvent
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.network.protocol.timpc.packet.action.AddFriendPacket import net.mamoe.mirai.utils.io.readQQ
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.utils.io.readUShortLVString 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<AddFriendPacket.Response>()
}
}
@PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)")
internal object FriendAddRequestEventPacket : KnownEventParserAndHandler<ReceiveFriendAddRequestEvent>(0x02DFu) { internal object FriendAddRequestEventPacket : KnownEventParserAndHandler<ReceiveFriendAddRequestEvent>(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 08 00 0A 00 04 01 00
// 00 00 00 01 // 00 00 00 01
// 76 E4 B8 DD // 76 E4 B8 DD
...@@ -88,7 +63,7 @@ Mirai 20:35:23 : Packet received: UnknownEventPacket(id=00 BB, identity=(7610254 ...@@ -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(10 + 4) // 00 00 00 08 00 0A 00 04 01 00 00 00 00 01
discardExact(4) // bot account uint discardExact(4) // bot account uint
discardExact(4) // 00 00 00 01 discardExact(4) // 00 00 00 01
val qq = readUInt().qq() val qq = readQQ().qq()
discardExact(4) // bot account uint discardExact(4) // bot account uint
discardExact(3) // 02 00 00 恒定 discardExact(3) // 02 00 00 恒定
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot 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( data class FriendConversationInitialize(
val qq: UInt val qq: Long
) : EventPacket ) : EventPacket
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)") @PacketVersion(date = "2019.11.2", timVersion = "2.3.2 (21173)")
internal object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler<FriendConversationInitialize>(0x0079u) { internal object FriendConversationInitializedEventParserAndHandler : KnownEventParserAndHandler<FriendConversationInitialize>(0x0079u) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): FriendConversationInitialize {
discardExact(4)// 00 00 00 00 discardExact(4)// 00 00 00 00
return FriendConversationInitialize(readUInt()) return FriendConversationInitialize(readQQ())
} }
} }
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.event.events.FriendStatusChanged
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId import net.mamoe.mirai.timpc.network.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.timpc.packet.PacketId import net.mamoe.mirai.timpc.network.packet.PacketId
import net.mamoe.mirai.network.protocol.timpc.packet.SessionPacketFactory import net.mamoe.mirai.timpc.network.packet.SessionPacketFactory
import net.mamoe.mirai.utils.OnlineStatus import net.mamoe.mirai.utils.OnlineStatus
import net.mamoe.mirai.utils.io.readQQ
data class FriendStatusChanged(
val qq: QQ,
val status: OnlineStatus
) : EventPacket
/** /**
* 好友在线状态改变 * 好友在线状态改变
*/ */
internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() { internal object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): FriendStatusChanged {
val qq = readUInt() val qq = readQQ()
discardExact(8) discardExact(8)
val statusId = readUByte() val statusId = readUByte()
val status = OnlineStatus(statusId) val status = OnlineStatus(statusId)
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import net.mamoe.mirai.Bot 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 import net.mamoe.mirai.utils.io.debugPrint
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
internal inline class IgnoredEventPacket(val id: UShort) : EventPacket { internal inline class IgnoredEventPacket(val id: UShort) : EventPacket {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate") @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.ByteReadPacket
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission 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.Subscribable
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.getGroup
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.timpc.TIMPCBot
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.readQQ
/** /**
* 成员加入前的事件. 群的成员列表中还没有这个人 * 成员加入前的事件. 群的成员列表中还没有这个人
...@@ -71,20 +69,23 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE ...@@ -71,20 +69,23 @@ internal object MemberJoinPacketHandler : KnownEventParserAndHandler<MemberJoinE
discardExact(11) //00 00 00 08 00 0A 00 04 01 00 00 discardExact(11) //00 00 00 08 00 0A 00 04 01 00 00
discardExact(1) // 00 discardExact(1) // 00
val group = bot.getGroup(readUInt()) val group = bot.getGroup(readQQ())
discardExact(1) // 01 discardExact(1) // 01
val qq = bot.getQQ(readUInt()) val qq = bot.getQQ(readQQ())
val member = group.Member(qq, MemberPermission.MEMBER, qq.coroutineContext) val member = with(bot) {
this as? TIMPCBot ?: error("wrong Bot type passed")
group.Member(qq, MemberPermission.MEMBER)
}
return if (readByte().toInt() == 0x03) { return if (readByte().toInt() == 0x03) {
MemberJoinEventPacket(member, null) MemberJoinEventPacket(member, null)
} else { } else {
MemberJoinEventPacket(member, group.getMember(readUInt())) MemberJoinEventPacket(member, group.getMember(readQQ()))
} }
} }
override suspend fun BotNetworkHandler<*>.handlePacket(packet: MemberJoinEventPacket) { override suspend fun BotNetworkHandler.handlePacket(packet: MemberJoinEventPacket) {
PreMemberJoinEvent(packet).broadcast() PreMemberJoinEvent(packet).broadcast()
packet.broadcast() packet.broadcast()
PostMemberJoinEvent(packet).broadcast() PostMemberJoinEvent(packet).broadcast()
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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.ByteReadPacket
import kotlinx.io.core.readUByte import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup import net.mamoe.mirai.network.data.EventPacket
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.io.toUHexString
import net.mamoe.mirai.utils.io.unsupported
/** /**
* 群成员列表变动事件. * 群成员列表变动事件.
...@@ -53,21 +50,21 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member ...@@ -53,21 +50,21 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member
discardExact(11) discardExact(11)
discardExact(1) discardExact(1)
val group = bot.getGroup(readUInt()) val group = bot.getGroup(readGroup())
discardExact(1) discardExact(1)
val id = readUInt() val id = readQQ()
if (id == bot.qqAccount) { if (id == bot.qqAccount) {
discardExact(1) discardExact(1)
return BeingKickEvent(group, group.getMember(readUInt())) return BeingKickEvent(group, group.getMember(readQQ()))
} }
val member = group.getMember(id) val member = group.getMember(id)
return when (val type = readUByte().toInt()) { return when (val type = readUByte().toInt()) {
0x02 -> MemberQuitEvent(member, _operator = null) 0x02 -> MemberQuitEvent(member, _operator = null)
0x03 -> MemberQuitEvent(member, _operator = group.getMember(readUInt())) 0x03 -> MemberQuitEvent(member, _operator = group.getMember(readQQ()))
else -> unsupported("Unsupported type " + type.toUHexString()) else -> error("Unsupported type " + type.toUHexString())
} }
// 某群员主动离开, 群号 853343432 // 某群员主动离开, 群号 853343432
...@@ -94,5 +91,4 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member ...@@ -94,5 +91,4 @@ internal object MemberGoneEventPacketHandler : KnownEventParserAndHandler<Member
// 00 30 32 33 32 63 32 39 36 65 36 35 64 62 64 64 64 64 65 35 62 33 34 64 36 62 34 33 32 61 30 64 61 65 32 30 37 35 38 34 37 34 32 65 32 39 63 35 63 64 // 00 30 32 33 32 63 32 39 36 65 36 35 64 62 64 64 64 64 65 35 62 33 34 64 36 62 34 33 32 61 30 64 61 65 32 30 37 35 38 34 37 34 32 65 32 39 63 35 63 64
} }
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
...@@ -9,75 +9,13 @@ import kotlinx.io.core.readUInt ...@@ -9,75 +9,13 @@ import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.getGroup import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.io.debugPrintIfFail import net.mamoe.mirai.utils.io.debugPrintIfFail
import net.mamoe.mirai.utils.io.readQQ
import net.mamoe.mirai.utils.io.readRemainingBytes import net.mamoe.mirai.utils.io.readRemainingBytes
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
// 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
internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket( internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket(
val remaining: ByteArray val remaining: ByteArray
) : EventOfMute() { ) : EventOfMute() {
...@@ -86,11 +24,6 @@ internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket( ...@@ -86,11 +24,6 @@ internal class Unknown0x02DCPacketFlag0x0EMaybeMutePacket(
override fun toString(): String = "Unknown0x02DCPacketFlag0x0EMaybeMutePacket(remaining=${remaining.toUHexString()})" override fun toString(): String = "Unknown0x02DCPacketFlag0x0EMaybeMutePacket(remaining=${remaining.toUHexString()})"
} }
sealed class EventOfMute : EventPacket {
abstract val operator: Member
abstract val group: Group
}
// TODO: 2019/12/14 这可能不只是禁言的包. // TODO: 2019/12/14 这可能不只是禁言的包.
internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) { internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandler<EventOfMute>(0x02DCu) {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute { override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): EventOfMute {
...@@ -133,12 +66,12 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl ...@@ -133,12 +66,12 @@ internal object MemberMuteEventPacketParserAndHandler : KnownEventParserAndHandl
0x11u -> debugPrintIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回?? 0x11u -> debugPrintIfFail("解析禁言包(0x02DC)时"){ // 猜测这个失败是撤回??
discardExact(15) discardExact(15)
discardExact(2) discardExact(2)
val group = bot.getGroup(readUInt()) val group = bot.getGroup(readQQ())
discardExact(2) discardExact(2)
val operator = group.getMember(readUInt()) val operator = group.getMember(readQQ())
discardExact(4) //time discardExact(4) //time
discardExact(2) discardExact(2)
val memberQQ = readUInt() val memberQQ = readQQ()
val durationSeconds = readUInt().toInt() val durationSeconds = readUInt().toInt()
if (durationSeconds == 0) { if (durationSeconds == 0) {
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.timpc.packet.Packet import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.PacketVersion import net.mamoe.mirai.timpc.network.packet.PacketVersion
import net.mamoe.mirai.utils.io.readQQ
data class MemberPermissionChangePacket( data class MemberPermissionChangePacket(
val groupId: UInt, val groupId: Long,
val qq: UInt, val qq: Long,
val kind: Kind val kind: Kind
) : Packet { ) : Packet {
enum class Kind { enum class Kind {
...@@ -35,7 +35,7 @@ internal object GroupMemberPermissionChangedEventFactory : KnownEventParserAndHa ...@@ -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 // 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) discardExact(remaining - 5)
val qq = readUInt() val qq = readQQ()
val kind = when (readByte().toInt()) { val kind = when (readByte().toInt()) {
0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR 0x00 -> MemberPermissionChangePacket.Kind.NO_LONGER_OPERATOR
0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR 0x01 -> MemberPermissionChangePacket.Kind.BECOME_OPERATOR
......
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<GroupMessage>(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<FriendMessage>(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
@file:Suppress("EXPERIMENTAL_API_USAGE") @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.ByteReadPacket
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler 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.ByteArrayPool
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
...@@ -28,11 +29,11 @@ Mirai 21:54:15 : Packet received: UnknownEventPacket(id=00 57, identity=(9205034 ...@@ -28,11 +29,11 @@ Mirai 21:54:15 : Packet received: UnknownEventPacket(id=00 57, identity=(9205034
internal class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler<UnknownEventPacket> { internal class UnknownEventParserAndHandler(override val id: UShort) : EventParserAndHandler<UnknownEventPacket> {
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): UnknownEventPacket { 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. 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 { ByteArrayPool.useInstance {
packet.body.readAvailable(it) packet.body.readAvailable(it)
bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString()) bot.logger.debug("Unknown packet(${packet.id}) data = " + it.toUHexString())
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "FunctionName") @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 kotlinx.io.core.*
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.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.io.*
internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> { internal object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
...@@ -17,7 +18,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap ...@@ -17,7 +18,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
* 请求验证码传输 * 请求验证码传输
*/ */
fun RequestTransmission( fun RequestTransmission(
bot: UInt, bot: Long,
token0825: ByteArray, token0825: ByteArray,
captchaSequence: Int, captchaSequence: Int,
token00BA: ByteArray token00BA: ByteArray
...@@ -45,7 +46,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap ...@@ -45,7 +46,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
* 刷新验证码 * 刷新验证码
*/ */
fun Refresh( fun Refresh(
bot: UInt, bot: Long,
token0825: ByteArray token0825: ByteArray
): OutgoingPacket = buildOutgoingPacket(name = "CaptchaPacket.Refresh") { ): OutgoingPacket = buildOutgoingPacket(name = "CaptchaPacket.Refresh") {
writeQQ(bot) writeQQ(bot)
...@@ -67,7 +68,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap ...@@ -67,7 +68,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
* 提交验证码 * 提交验证码
*/ */
fun Submit( fun Submit(
bot: UInt, bot: Long,
token0825: ByteArray, token0825: ByteArray,
captcha: String, captcha: String,
captchaToken: IoBuffer captchaToken: IoBuffer
...@@ -125,7 +126,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap ...@@ -125,7 +126,7 @@ internal object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, Cap
} }
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CaptchaResponse = override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): CaptchaResponse =
when (val flag = readByte().toUInt()) { when (val flag = readByte().toUInt()) {
0x14u -> {//00 05 00 00 00 00 00 00 38 0x14u -> {//00 05 00 00 00 00 00 00 38
CaptchaResponse.Correct().apply { CaptchaResponse.Correct().apply {
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @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.ByteReadPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.io.core.writeUByte import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.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.OnlineStatus
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ import net.mamoe.mirai.utils.io.writeQQ
...@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ ...@@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.writeQQ
*/ */
internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) { internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey, sessionKey: SessionKey,
loginStatus: OnlineStatus loginStatus: OnlineStatus
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
...@@ -31,10 +31,10 @@ internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacke ...@@ -31,10 +31,10 @@ internal object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacke
} }
} }
internal object ChangeOnlineStatusResponse : Packet { internal object ChangeOnlineStatusResponse : Packet {
override fun toString(): String = this::class.simpleName!! override fun toString(): String = this::class.simpleName!!
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ChangeOnlineStatusResponse = override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): ChangeOnlineStatusResponse =
ChangeOnlineStatusResponse ChangeOnlineStatusResponse
} }
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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.ByteReadPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeQQ import net.mamoe.mirai.utils.io.writeQQ
@NoLog @NoLog
internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() { internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
...@@ -25,7 +25,7 @@ internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>( ...@@ -25,7 +25,7 @@ internal object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>(
} }
} }
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 HeartbeatPacketResponse
} }
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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 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.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.utils.decryptBy import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.utils.encryptBy import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.writeCRC32
internal object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> { internal object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> {
override val value: ByteArray = TIMProtocol.shareKey override val value: ByteArray = TIMProtocol.shareKey
...@@ -46,7 +45,7 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr ...@@ -46,7 +45,7 @@ internal inline class SubmitPasswordResponseDecrypter(private val privateKey: Pr
*/ */
internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) { internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
password: String, password: String,
loginTime: Int, loginTime: Int,
loginIP: String, loginIP: String,
...@@ -109,7 +108,7 @@ internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginR ...@@ -109,7 +108,7 @@ internal object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginR
data class Failed(val result: LoginResult) : LoginResponse() data class Failed(val result: LoginResult) : LoginResponse()
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): LoginResponse { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): LoginResponse {
val size = remaining.toInt() val size = remaining.toInt()
return when { return when {
size == 229 || size == 271 || size == 207 || size == 165 /* TODO CHECK 165 */ -> { size == 229 || size == 271 || size == 207 || size == 165 /* TODO CHECK 165 */ -> {
...@@ -267,7 +266,7 @@ internal inline class SessionResponseDecryptionKey(private val delegate: IoBuffe ...@@ -267,7 +266,7 @@ internal inline class SessionResponseDecryptionKey(private val delegate: IoBuffe
} }
private fun BytePacketBuilder.writePart1( private fun BytePacketBuilder.writePart1(
qq: UInt, qq: Long,
password: String, password: String,
loginTime: Int, loginTime: Int,
loginIP: String, loginIP: String,
...@@ -296,7 +295,7 @@ private fun BytePacketBuilder.writePart1( ...@@ -296,7 +295,7 @@ private fun BytePacketBuilder.writePart1(
this.writeFully(TIMProtocol.passwordSubmissionTLV2) this.writeFully(TIMProtocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag this.writeHex("00 1A")//tag
this.writeHex("00 40")//length 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.constantData1)
this.writeFully(TIMProtocol.constantData2) this.writeFully(TIMProtocol.constantData2)
this.writeQQ(qq) this.writeQQ(qq)
...@@ -310,6 +309,29 @@ private fun BytePacketBuilder.writePart1( ...@@ -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 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() { private fun BytePacketBuilder.writePart2() {
this.writeHex("03 12")//tag this.writeHex("03 12")//tag
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "FunctionName") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.network.qqAccount
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.withSession
internal fun BotSession.RequestSKeyPacket(): OutgoingPacket = RequestSKeyPacket(qqAccount, sessionKey)
internal inline class SKey( internal inline class SKey(
val value: String val value: String
...@@ -25,7 +21,7 @@ internal inline class SKey( ...@@ -25,7 +21,7 @@ internal inline class SKey(
*/ */
internal object RequestSKeyPacket : SessionPacketFactory<SKey>() { internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
sessionKey: SessionKey sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
...@@ -35,7 +31,7 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() { ...@@ -35,7 +31,7 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
} }
} }
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 //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) discardExact(4)
...@@ -44,9 +40,9 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() { ...@@ -44,9 +40,9 @@ internal object RequestSKeyPacket : SessionPacketFactory<SKey>() {
} }
} }
override suspend fun BotNetworkHandler<*>.handlePacket(packet: SKey) = bot.withSession { override suspend fun BotNetworkHandler.handlePacket(packet: SKey) {
_sKey = packet.value // _sKey = packet.value
_cookies = "uin=o$qqAccount;skey=$sKey;" // _cookies = "uin=o$qqAccount;skey=$sKey;"
// TODO: 2019/11/27 SKEY 实现 // TODO: 2019/11/27 SKEY 实现
/* /*
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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 kotlinx.io.core.*
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.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.io.*
import net.mamoe.mirai.utils.localIpAddress import net.mamoe.mirai.utils.localIpAddress
internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) { internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
serverIp: String, serverIp: String,
token38: IoBuffer, token38: IoBuffer,
token88: IoBuffer, token88: IoBuffer,
...@@ -65,7 +66,7 @@ internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.Sessio ...@@ -65,7 +66,7 @@ internal object RequestSessionPacket : PacketFactory<RequestSessionPacket.Sessio
override fun toString(): String = "SessionKeyResponse" override fun toString(): String = "SessionKeyResponse"
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SessionKeyResponse { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler): SessionKeyResponse {
when (remaining) { when (remaining) {
407L -> { 407L -> {
discardExact(25)//todo test discardExact(25)//todo test
......
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") @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.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.network.data.Packet
import net.mamoe.mirai.network.protocol.timpc.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.io.*
internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> { internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
...@@ -22,7 +23,7 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> { ...@@ -22,7 +23,7 @@ internal object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
*/ */
internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) { internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: Long,
serverIp: String, serverIp: String,
isRedirect: Boolean isRedirect: Boolean
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
...@@ -60,7 +61,7 @@ internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey> ...@@ -60,7 +61,7 @@ internal object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>
} }
} }
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()) { when (val flag = readByte().toUByte().toInt()) {
0xFE -> { 0xFE -> {
discardExact(94) discardExact(94)
......
package net.mamoe.mirai.timpc.utils
/**
* 表示这里是不可到达的位置.
*/
@Suppress("NOTHING_TO_INLINE")
internal inline fun assertUnreachable(): Nothing = error("This clause should not be reached")
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()}")
package net.mamoe.mirai.network @file:Suppress("unused")
package net.mamoe.mirai.timpc
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.copyTo import kotlinx.io.InputStream
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput
import kotlinx.io.streams.inputStream import kotlinx.io.streams.inputStream
import net.mamoe.mirai.Bot import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.message.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.protocol.timpc.packet.SessionKey
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toExternalImage import net.mamoe.mirai.utils.toExternalImage
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import java.io.InputStream
import java.io.OutputStream
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext
/** internal actual class TIMPCBot actual constructor(
* JVM 平台相关扩展. 详情查看 [BotSessionBase] account: BotAccount,
*/ logger: MiraiLogger?,
@UseExperimental(MiraiInternalAPI::class) context: CoroutineContext
@Suppress("unused") ) : TIMPCBotBase(account, logger, context) {
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.downloadAsStream(): InputStream = download().inputStream()
suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(IO) { downloadAsStream().use { ImageIO.read(it) } } 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.downloadAsExternalImage(): ExternalImage = download().use { it.toExternalImage() }
suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) } 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
package net.mamoe.mirai.network.protocol.timpc package net.mamoe.mirai.timpc.network
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors import java.util.concurrent.Executors
/** /**
* 独立的 4 thread 调度器 * 包处理协程调度器.
*
* JVM: 独立的 4 thread 调度器
*/ */
internal actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() internal actual val NetworkDispatcher: CoroutineDispatcher
\ No newline at end of file get() = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE")
package mirai.test
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.login import net.mamoe.mirai.network.data.LoginResult
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult import net.mamoe.mirai.timpc.TIMPC
import java.util.* import java.util.*
/** /**
...@@ -44,8 +42,8 @@ suspend fun main() { ...@@ -44,8 +42,8 @@ suspend fun main() {
.map { Pair(it[0].toLong(), it[1]) } .map { Pair(it[0].toLong(), it[1]) }
.forEach { (qq, password) -> .forEach { (qq, password) ->
runBlocking { runBlocking {
val bot = Bot( val bot = TIMPC.Bot(
qq.toUInt(), qq,
if (password.endsWith(".")) password.substring(0, password.length - 1) else password if (password.endsWith(".")) password.substring(0, password.length - 1) else password
) )
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate", "EXPERIMENTAL_UNSIGNED_LITERALS") @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 io.ktor.util.date.GMTDate
import kotlinx.coroutines.* 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.ImplicitReflectionSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.internal.ArrayListSerializer import kotlinx.serialization.internal.ArrayListSerializer
import kotlinx.serialization.json.Json 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.Bot
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.timpc.TIMPC
import net.mamoe.mirai.network.protocol.timpc.TIMProtocol import net.mamoe.mirai.timpc.network.TIMProtocol
import net.mamoe.mirai.network.protocol.timpc.handler.DataPacketSocketAdapter import net.mamoe.mirai.timpc.network.packet.*
import net.mamoe.mirai.network.protocol.timpc.packet.* import net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket
import net.mamoe.mirai.network.protocol.timpc.packet.event.IgnoredEventPacket import net.mamoe.mirai.timpc.network.packet.login.CaptchaKey
import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaKey import net.mamoe.mirai.timpc.network.packet.login.ShareKey
import net.mamoe.mirai.network.protocol.timpc.packet.login.LoginResult import net.mamoe.mirai.timpc.network.packet.login.TouchKey
import net.mamoe.mirai.network.protocol.timpc.packet.login.ShareKey
import net.mamoe.mirai.network.protocol.timpc.packet.login.TouchKey
import net.mamoe.mirai.utils.DecryptionFailedException import net.mamoe.mirai.utils.DecryptionFailedException
import net.mamoe.mirai.utils.decryptBy import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
...@@ -37,7 +36,6 @@ import java.nio.charset.Charset ...@@ -37,7 +36,6 @@ import java.nio.charset.Charset
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.io.use
/** /**
* 避免 print 重叠. 单线程处理足够调试 * 避免 print 重叠. 单线程处理足够调试
...@@ -189,7 +187,7 @@ internal object PacketDebugger { ...@@ -189,7 +187,7 @@ internal object PacketDebugger {
/** /**
* null 则不筛选 * null 则不筛选
*/ */
val qq: UInt? = 761025446u val qq: Long? = 761025446
/** /**
* 打开后则记录每一个包到文件. * 打开后则记录每一个包到文件.
*/ */
...@@ -208,7 +206,7 @@ internal object PacketDebugger { ...@@ -208,7 +206,7 @@ internal object PacketDebugger {
discardExact(3) discardExact(3)
val id = matchPacketId(readUShort()) val id = matchPacketId(readUShort())
val sequenceId = readUShort() val sequenceId = readUShort()
val packetQQ = readUInt() val packetQQ = readQQ()
if (id == KnownPacketId.HEARTBEAT || (qq != null && packetQQ != qq)) if (id == KnownPacketId.HEARTBEAT || (qq != null && packetQQ != qq))
return@read return@read
...@@ -308,7 +306,7 @@ internal object PacketDebugger { ...@@ -308,7 +306,7 @@ internal object PacketDebugger {
if (IgnoredPacketIdList.contains(id)) { if (IgnoredPacketIdList.contains(id)) {
return return
} }
val packetQQ = readUInt() val packetQQ = readQQ()
if (qq != null && packetQQ != qq) { if (qq != null && packetQQ != qq) {
return@read return@read
} }
...@@ -378,27 +376,12 @@ when (idHex.substring(0, 5)) { ...@@ -378,27 +376,12 @@ when (idHex.substring(0, 5)) {
} }
internal object DebugNetworkHandler : BotNetworkHandler<DataPacketSocketAdapter>, CoroutineScope { internal object DebugNetworkHandler : BotNetworkHandler(), CoroutineScope {
override val supervisor: CompletableJob = SupervisorJob() 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 owner: Bot override val bot: Bot = TIMPC.run { this@DebugNetworkHandler.Bot(qq ?: 0L, "", null) }
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() { override suspend fun awaitDisconnection() {
} }
......
package mirai.test.packetdebugger package packetdebugger
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUShort import kotlinx.io.core.readUShort
import net.mamoe.mirai.network.protocol.timpc.packet.PacketId import net.mamoe.mirai.timpc.network.packet.PacketId
import net.mamoe.mirai.network.protocol.timpc.packet.login.CaptchaPacket import net.mamoe.mirai.timpc.network.packet.matchPacketId
import net.mamoe.mirai.network.protocol.timpc.packet.matchPacketId
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
......
<?xml version="1.0" encoding="utf-8"?>
<manifest package="net.mamoe.mirai.timpc">
</manifest>
\ No newline at end of file
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.Contact
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
...@@ -8,7 +9,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI ...@@ -8,7 +9,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
* 平台相关扩展 * 平台相关扩展
*/ */
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> : MessagePacketBase<TSender, TSubject>() { actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor(bot: Bot) : MessagePacketBase<TSender, TSubject>(bot) {
// suspend inline fun uploadImage(image: Bitmap): Image = subject.uploadImage(image) // 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: URL): Image = subject.uploadImage(image)
//suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image) //suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image)
......
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
...@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils ...@@ -3,8 +3,6 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataInput import java.io.DataInput
import java.io.EOFException import java.io.EOFException
...@@ -24,7 +22,7 @@ actual val deviceName: String get() = InetAddress.getLocalHost().hostName ...@@ -24,7 +22,7 @@ actual val deviceName: String get() = InetAddress.getLocalHost().hostName
* Ktor HttpClient. 不同平台使用不同引擎. * Ktor HttpClient. 不同平台使用不同引擎.
*/ */
@KtorExperimentalAPI @KtorExperimentalAPI
internal actual val Http: HttpClient actual val Http: HttpClient
get() = HttpClient(CIO) get() = HttpClient(CIO)
/** /**
......
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
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];
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName")
package net.mamoe.mirai package net.mamoe.mirai
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.io.OutputStream
import kotlinx.coroutines.Job import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.use
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.network.BotNetworkHandler 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.BotConfiguration
import net.mamoe.mirai.utils.GroupNotFoundException import net.mamoe.mirai.utils.GroupNotFoundException
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.transferTo
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmSynthetic
/** /**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号. * Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
...@@ -22,77 +25,60 @@ import kotlin.jvm.JvmSynthetic ...@@ -22,77 +25,60 @@ import kotlin.jvm.JvmSynthetic
* *
* @see Contact * @see Contact
*/ */
interface Bot : CoroutineScope { abstract class Bot : CoroutineScope {
@UseExperimental(MiraiInternalAPI::class)
companion object { 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) 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 // region contacts
/** /**
* 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友 * 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友
*/ */
val qqs: ContactList<QQ> abstract val qqs: ContactList<QQ>
/**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
*/
fun getQQ(id: UInt): QQ
/** /**
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个.
*/ */
fun getQQ(id: Long): QQ abstract fun getQQ(id: Long): QQ
/** /**
* 与这个机器人相关的群列表. 机器人不一定是群成员. * 与这个机器人相关的群列表. 机器人不一定是群成员.
*/ */
val groups: ContactList<Group> abstract val groups: ContactList<Group>
/** /**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* 若 [id] 无效, 将会抛出 [GroupNotFoundException] * 若 [id] 无效, 将会抛出 [GroupNotFoundException]
*/ */
suspend fun getGroup(id: GroupId): Group abstract suspend fun getGroup(id: GroupId): Group
/** /**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* 若 [internalId] 无效, 将会抛出 [GroupNotFoundException] * 若 [internalId] 无效, 将会抛出 [GroupNotFoundException]
*/ */
suspend fun getGroup(internalId: GroupInternalId): Group abstract suspend fun getGroup(internalId: GroupInternalId): Group
/** /**
* 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个.
* 若 [id] 无效, 将会抛出 [GroupNotFoundException] * 若 [id] 无效, 将会抛出 [GroupNotFoundException]
*/ */
suspend fun getGroup(id: Long): Group abstract suspend fun getGroup(id: Long): Group
// endregion // endregion
...@@ -101,36 +87,64 @@ interface Bot : CoroutineScope { ...@@ -101,36 +87,64 @@ interface Bot : CoroutineScope {
/** /**
* 网络模块 * 网络模块
*/ */
val network: BotNetworkHandler<*> abstract val network: BotNetworkHandler
/** /**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. * 使用在默认配置基础上修改的配置进行登录
* 然后重新启动并尝试登录
*/ */
fun tryReinitializeNetworkHandler( suspend inline fun login(configuration: BotConfiguration.() -> Unit) {
configuration: BotConfiguration, return this.login(BotConfiguration().apply(configuration))
cause: Throwable? = null }
): Job
/**
* 使用特定配置进行登录
*/
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 好友备注
*/ */
suspend fun reinitializeNetworkHandler( abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult
configuration: BotConfiguration,
cause: Throwable? = null
): LoginResult
/** /**
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler] 下的协程. * 同意来自陌生人的加好友请求
* 然后重新启动并尝试登录
*/ */
fun reinitializeNetworkHandlerAsync( abstract suspend fun approveFriendAddRequest(id: Long, remark: String?)
configuration: BotConfiguration,
cause: Throwable? = null
): Deferred<LoginResult>
// endregion // 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
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
package net.mamoe.mirai package net.mamoe.mirai
data class BotAccount( data class BotAccount(
val id: UInt, val id: Long,
val password: String val password: String
) { )
constructor(id: Long, password: String) : this(id.toUInt(), password) \ No newline at end of file
}
\ No newline at end of file
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
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("BotHelperKt") @file:JvmName("BotHelperKt")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") @file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai 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.BotConfiguration
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
/* /*
* 在 [Bot] 中的方法的捷径 * 在 [Bot] 中的方法的捷径
*/ */
//Contacts //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 <R> 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] * 使用默认的配置 ([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] * 使用在默认配置基础上修改的配置进行登录, 返回 [this]
...@@ -77,20 +30,11 @@ suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bo ...@@ -77,20 +30,11 @@ suspend inline fun Bot.alsoLogin(configuration: BotConfiguration.() -> Unit): Bo
contract { contract {
callsInPlace(configuration, InvocationKind.EXACTLY_ONCE) callsInPlace(configuration, InvocationKind.EXACTLY_ONCE)
} }
this.reinitializeNetworkHandler(BotConfiguration().apply(configuration)).requireSuccess() this.login(configuration)
return this 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 号 * 取得机器人的 QQ 号
*/ */
inline val Bot.qqAccount: UInt get() = this.account.id inline val Bot.qqAccount: Long get() = this.account.id
\ No newline at end of file \ No newline at end of file
...@@ -2,54 +2,43 @@ ...@@ -2,54 +2,43 @@
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineExceptionHandler
import net.mamoe.mirai.contact.* import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.contact.internal.Group import kotlinx.coroutines.Job
import net.mamoe.mirai.contact.internal.QQ import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.timpc.TIMBotNetworkHandler import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.network.protocol.timpc.packet.KnownPacketId import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupNotFound import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupPacket import net.mamoe.mirai.utils.MiraiLogger
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.io.logStacktrace import net.mamoe.mirai.utils.io.logStacktrace
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
@PublishedApi /*
internal class BotImpl @PublishedApi internal constructor( * 泛型 N 不需要向外(接口)暴露.
*/
@MiraiInternalAPI
abstract class BotImpl<N : BotNetworkHandler> constructor(
override val account: BotAccount, override val account: BotAccount,
override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"), override val logger: MiraiLogger = DefaultLogger("Bot(" + account.id + ")"),
context: CoroutineContext context: CoroutineContext
) : Bot, CoroutineScope { ) : Bot(), CoroutineScope {
private val supervisorJob = SupervisorJob(context[Job]) private val supervisorJob = SupervisorJob(context[Job])
override val coroutineContext: CoroutineContext = override val coroutineContext: CoroutineContext =
context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") } context + supervisorJob + CoroutineExceptionHandler { _, e -> e.logStacktrace("An exception was thrown under a coroutine of Bot") }
init { init {
launch { @Suppress("LeakingThis")
instances.addLast(this@BotImpl) instances.addLast(this)
}
} }
companion object { companion object {
init {
KnownPacketId.values() /* load id classes */
}
@PublishedApi @PublishedApi
internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList() internal val instances: LockFreeLinkedList<Bot> = LockFreeLinkedList()
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block) inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach(block)
fun instanceWhose(qq: UInt): Bot { fun instanceWhose(qq: Long): Bot {
instances.forEach { instances.forEach {
if (it.qqAccount == qq) { if (it.qqAccount == qq) {
return it return it
...@@ -63,101 +52,21 @@ internal class BotImpl @PublishedApi internal constructor( ...@@ -63,101 +52,21 @@ internal class BotImpl @PublishedApi internal constructor(
// region network // region network
override val network: BotNetworkHandler<*> get() = _network abstract override val network: N
private lateinit var _network: BotNetworkHandler<*>
override fun tryReinitializeNetworkHandler(// shouldn't be suspend!! This function MUST NOT inherit the context from the caller because the caller(NetworkHandler) is going to close
configuration: BotConfiguration,
cause: Throwable?
): Job = launch {
repeat(configuration.reconnectionRetryTimes) {
if (reinitializeNetworkHandlerAsync(configuration, cause).await().isSuccess()) {
logger.info("Reconnected successfully")
return@launch
} else {
delay(configuration.reconnectPeriodMillis)
}
}
}
override suspend fun reinitializeNetworkHandler(
configuration: BotConfiguration,
cause: Throwable?
): LoginResult {
logger.info("BotAccount: ${qqAccount.toLong()}")
logger.info("Initializing BotNetworkHandler")
try {
if (::_network.isInitialized) {
_network.close(cause)
}
} catch (e: Exception) {
logger.error("Cannot close network handler", e)
}
_network = TIMBotNetworkHandler(this.coroutineContext + configuration, this)
return _network.login()
}
override fun reinitializeNetworkHandlerAsync(
configuration: BotConfiguration,
cause: Throwable?
): Deferred<LoginResult> = async { reinitializeNetworkHandler(configuration, cause) }
// endregion // endregion
// region contacts
override val groups: ContactList<Group> = ContactList(LockFreeLinkedList())
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
/**
* 线程安全地获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
*/
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
@JvmSynthetic override fun close(throwable: Throwable?) {
override fun getQQ(id: UInt): QQ = qqs.delegate.getOrAdd(id) { QQ(this, id, coroutineContext) } if (throwable == null) {
network.close()
@UseExperimental(MiraiInternalAPI::class) this.supervisorJob.complete()
override fun getQQ(@PositiveNumbers id: Long): QQ = getQQ(id.coerceAtLeastOrFail(0).toUInt()) groups.delegate.clear()
qqs.delegate.clear()
override suspend fun getGroup(internalId: GroupInternalId): Group = getGroup(internalId.toId()) } else {
network.close(throwable)
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class) this.supervisorJob.completeExceptionally(throwable)
override suspend fun getGroup(id: GroupId): Group = groups.delegate.getOrNull(id.value) ?: inline { groups.delegate.clear()
val info: RawGroupInfo = try { qqs.delegate.clear()
when (val response =
withSession { GroupPacket.QueryGroupInfo(qqAccount, id.toInternalId(), sessionKey).sendAndExpect<GroupPacket.InfoResponse>() }) {
is RawGroupInfo -> response
is GroupNotFound -> throw GroupNotFoundException("id=${id.value.toLong()}")
else -> assertUnreachable()
}
} catch (e: Exception) {
throw IllegalStateException("Cannot obtain group info for id ${id.value.toLong()}", e)
}
return groups.delegate.getOrAdd(id.value) { Group(this, id, info, coroutineContext) }
}
@UseExperimental(MiraiInternalAPI::class)
override suspend fun getGroup(@PositiveNumbers id: Long): Group = id.coerceAtLeastOrFail(0).toUInt().let {
groups.delegate.getOrNull(it) ?: inline {
val info: RawGroupInfo = try {
withSession { GroupPacket.QueryGroupInfo(qqAccount, GroupId(it).toInternalId(), sessionKey).sendAndExpect() }
} catch (e: Exception) {
e.logStacktrace()
error("Cannot obtain group info for id ${it.toLong()}")
}
return groups.delegate.getOrAdd(it) { Group(this, GroupId(it), info, coroutineContext) }
} }
} }
// endregion
@UseExperimental(MiraiInternalAPI::class)
override fun close() {
_network.close()
this.supervisorJob.complete()
groups.delegate.clear()
qqs.delegate.clear()
}
} }
...@@ -3,15 +3,9 @@ ...@@ -3,15 +3,9 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.message.chain import net.mamoe.mirai.utils.WeakRefProperty
import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.joinToString
import net.mamoe.mirai.withSession
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
...@@ -26,12 +20,13 @@ interface Contact { ...@@ -26,12 +20,13 @@ interface Contact {
/** /**
* 这个联系人所属 [Bot] * 这个联系人所属 [Bot]
*/ */
val bot: Bot @WeakRefProperty
val bot: Bot // weak ref
/** /**
* 可以是 QQ 号码或者群号码 [GroupId]. * 可以是 QQ 号码或者群号码 [GroupId].
*/ */
val id: UInt val id: Long
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
...@@ -39,6 +34,8 @@ interface Contact { ...@@ -39,6 +34,8 @@ interface Contact {
* 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右. * 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
*/ */
suspend fun sendMessage(message: MessageChain) suspend fun sendMessage(message: MessageChain)
suspend fun uploadImage(image: ExternalImage): ImageId
} }
suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.chain()) suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.chain())
...@@ -46,13 +43,13 @@ suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.c ...@@ -46,13 +43,13 @@ suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.c
suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain()) suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain())
/** /**
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. * 以 [Bot] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
* 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync] * 这个方法将能帮助使用在 [Bot] 中定义的一些扩展方法
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
inline fun <R> Contact.withSession(block: BotSession.() -> R): R { inline fun <R> Contact.withBot(block: Bot.() -> R): R {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
return bot.withSession(block) return bot.run(block)
} }
...@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.joinToString ...@@ -12,7 +12,7 @@ import net.mamoe.mirai.utils.joinToString
*/ */
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
@Suppress("unused") @Suppress("unused")
class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLinkedList<C>) { class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedList<C>) {
/** /**
* ID 列表的字符串表示. * ID 列表的字符串表示.
* 如: * 如:
...@@ -22,9 +22,9 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink ...@@ -22,9 +22,9 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink
*/ */
val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]" val idContentString: String get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(2) + "]"
operator fun get(id: UInt): C = delegate[id] operator fun get(id: Long): C = delegate[id]
fun getOrNull(id: UInt): C? = delegate.getOrNull(id) fun getOrNull(id: Long): C? = delegate.getOrNull(id)
fun containsId(id: UInt): Boolean = delegate.getOrNull(id) != null fun containsId(id: Long): Boolean = delegate.getOrNull(id) != null
val size: Int get() = delegate.size val size: Int get() = delegate.size
operator fun contains(element: C): Boolean = delegate.contains(element) operator fun contains(element: C): Boolean = delegate.contains(element)
...@@ -35,14 +35,14 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink ...@@ -35,14 +35,14 @@ class ContactList<C : Contact>(@PublishedApi internal val delegate: LockFreeLink
override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")")
} }
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: UInt): C { operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
forEach { if (it.id == id) return it } forEach { if (it.id == id) return it }
throw NoSuchElementException() throw NoSuchElementException()
} }
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: UInt): C? { fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
forEach { if (it.id == id) return it } forEach { if (it.id == id) return it }
return null return null
} }
fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: UInt, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier) fun <C : Contact> LockFreeLinkedList<C>.getOrAdd(id: Long, supplier: () -> C): C = filteringGetOrAdd({ it.id == id }, supplier)
...@@ -3,10 +3,7 @@ ...@@ -3,10 +3,7 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.network.protocol.timpc.packet.action.GroupInfo import net.mamoe.mirai.network.data.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.utils.internal.coerceAtLeastOrFail import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
...@@ -53,7 +50,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019 ...@@ -53,7 +50,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
/** /**
* 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException] * 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException]
*/ */
fun getMember(id: UInt): Member fun getMember(id: Long): Member
/** /**
* 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新. * 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新.
...@@ -67,7 +64,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019 ...@@ -67,7 +64,7 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
* *
* @see QuitGroupResponse.isSuccess 判断是否成功 * @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})" fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
} }
...@@ -81,27 +78,19 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019 ...@@ -81,27 +78,19 @@ interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019
* @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
* @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
*/ */
inline class GroupId(inline val value: UInt) inline class GroupId(inline val value: Long)
/**
* 将 [this] 转为 [GroupId].
*/
@Suppress("NOTHING_TO_INLINE")
inline fun UInt.groupId(): GroupId = GroupId(this)
/** /**
* 将 [this] 转为 [GroupInternalId]. * 将 [this] 转为 [GroupInternalId].
*/ */
@Suppress("NOTHING_TO_INLINE") fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
inline fun UInt.groupInternalId(): GroupInternalId = GroupInternalId(this)
/** /**
* 将无符号整数格式的 [Long] 转为 [GroupId]. * 将无符号整数格式的 [Long] 转为 [GroupId].
* *
* 注: 在 Java 中常用 [Long] 来表示 [UInt] * 注: 在 Java 中常用 [Long] 来表示 [UInt]
*/ */
fun @receiver:PositiveNumbers Long.groupId(): GroupId = fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))
GroupId(this.coerceAtLeastOrFail(0).toUInt())
/** /**
* 一些群 API 使用的 ID. 在使用时会特别注明 * 一些群 API 使用的 ID. 在使用时会特别注明
...@@ -111,4 +100,4 @@ fun @receiver:PositiveNumbers Long.groupId(): GroupId = ...@@ -111,4 +100,4 @@ fun @receiver:PositiveNumbers Long.groupId(): GroupId =
* @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
* @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
*/ */
inline class GroupInternalId(inline val value: UInt) inline class GroupInternalId(inline val value: Long)
...@@ -6,7 +6,7 @@ import kotlin.math.pow ...@@ -6,7 +6,7 @@ import kotlin.math.pow
@Suppress("ObjectPropertyName") @Suppress("ObjectPropertyName")
private val `10EXP6` = 10.0.pow(6).toUInt() private val `10EXP6` = 10.0.pow(6)
fun GroupId.toInternalId(): GroupInternalId { fun GroupId.toInternalId(): GroupInternalId {
...@@ -23,12 +23,12 @@ fun GroupId.toInternalId(): GroupInternalId { ...@@ -23,12 +23,12 @@ fun GroupId.toInternalId(): GroupInternalId {
in 1..10 -> plusLeft(202, 6) in 1..10 -> plusLeft(202, 6)
in 11..19 -> plusLeft(469, 6) in 11..19 -> plusLeft(469, 6)
in 20..66 -> plusLeft(208, 7) 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 157..209 -> plusLeft(199, 7)
in 210..309 -> plusLeft(389, 7) in 210..309 -> plusLeft(389, 7)
in 310..499 -> plusLeft(349, 7) in 310..499 -> plusLeft(349, 7)
else -> null else -> null
}?.toUInt() ?: this.value }?.toLong() ?: this.value
) )
} }
...@@ -36,17 +36,17 @@ fun GroupInternalId.toId(): GroupId = with(value.toString()) { ...@@ -36,17 +36,17 @@ fun GroupInternalId.toId(): GroupId = with(value.toString()) {
if (value < `10EXP6`) { if (value < `10EXP6`) {
return GroupId(value) return GroupId(value)
} }
val left: UInt = this.dropLast(6).toUInt() val left = this.dropLast(6).toLong()
return GroupId( return GroupId(
when (left.toInt()) { when (left.toInt()) {
in 203..212 -> ((left - 202u).toString() + this.takeLast(6).toInt().toString()).toUInt() in 203..212 -> ((left - 202).toString() + this.takeLast(6).toInt().toString()).toLong()
in 480..488 -> ((left - 469u).toString() + this.takeLast(6).toInt().toString()).toUInt() in 480..488 -> ((left - 469).toString() + this.takeLast(6).toInt().toString()).toLong()
in 2100..2146 -> ((left.toString().take(3).toUInt() - 208u).toString() + this.takeLast(7).toInt().toString()).toUInt() in 2100..2146 -> ((left.toString().take(3).toLong() - 208).toString() + this.takeLast(7).toInt().toString()).toLong()
in 2010..2099 -> ((left - 1943u).toString() + this.takeLast(6).toInt().toString()).toUInt() in 2010..2099 -> ((left - 1943).toString() + this.takeLast(6).toInt().toString()).toLong()
in 2147..2199 -> ((left.toString().take(3).toUInt() - 199u).toString() + this.takeLast(7).toInt().toString()).toUInt() in 2147..2199 -> ((left.toString().take(3).toLong() - 199).toString() + this.takeLast(7).toInt().toString()).toLong()
in 4100..4199 -> ((left.toString().take(3).toUInt() - 389u).toString() + this.takeLast(7).toInt().toString()).toUInt() in 4100..4199 -> ((left.toString().take(3).toLong() - 389).toString() + this.takeLast(7).toInt().toString()).toLong()
in 3800..3989 -> ((left.toString().take(3).toUInt() - 349u).toString() + this.takeLast(7).toInt().toString()).toUInt() in 3800..3989 -> ((left.toString().take(3).toLong() - 349).toString() + this.takeLast(7).toInt().toString()).toLong()
else -> value else -> value
} }
) )
......
...@@ -4,12 +4,9 @@ package net.mamoe.mirai.contact ...@@ -4,12 +4,9 @@ package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.data.Profile import net.mamoe.mirai.network.data.FriendNameRemark
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.data.PreviousNameList
import net.mamoe.mirai.network.protocol.timpc.packet.action.AvatarLink import net.mamoe.mirai.network.data.Profile
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
/** /**
* QQ 对象. * QQ 对象.
......
...@@ -6,11 +6,10 @@ import net.mamoe.mirai.Bot ...@@ -6,11 +6,10 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.isAdministrator import net.mamoe.mirai.contact.isAdministrator
import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.isOwner import net.mamoe.mirai.contact.isOwner
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.any import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.network.protocol.timpc.packet.event.FriendMessage import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.network.protocol.timpc.packet.event.GroupMessage import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.network.protocol.timpc.packet.event.MessagePacket
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
...@@ -188,15 +187,9 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>( ...@@ -188,15 +187,9 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息 * 如果是这个人发的消息, 就执行 [onEvent]. 消息可以是好友消息也可以是群消息
*/ */
@MessageDsl @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) 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] * 如果是管理员或群主发的消息, 就执行 [onEvent]
*/ */
...@@ -222,21 +215,15 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>( ...@@ -222,21 +215,15 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
* 如果是来自这个群的消息, 就执行 [onEvent] * 如果是来自这个群的消息, 就执行 [onEvent]
*/ */
@MessageDsl @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) 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] * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent]
*/ */
@MessageDsl @MessageDsl
suspend inline fun <reified M : Message> has(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) = suspend inline fun <reified M : Message> has(noinline onEvent: @MessageDsl suspend T.(String) -> Unit) =
subscriber { if (message.any<M>()) onEvent(this) } subscriber { if (message.any { it::class == M::class }) onEvent(this) }
/** /**
* 如果 [filter] 返回 `true` 就执行 `onEvent` * 如果 [filter] 返回 `true` 就执行 `onEvent`
...@@ -283,7 +270,7 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>( ...@@ -283,7 +270,7 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
*/ */
@MessageDsl @MessageDsl
suspend inline fun Regex.matchingReply(noinline replier: AnyReplier<T>) { suspend inline fun Regex.matchingReply(noinline replier: AnyReplier<T>) {
content({ this@matchingReply.matchEntire(it) != null }){ content({ this@matchingReply.matchEntire(it) != null }) {
@Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning @Suppress("DSL_SCOPE_VIOLATION_WARNING") // false negative warning
executeAndReply(replier) executeAndReply(replier)
} }
......
...@@ -7,7 +7,6 @@ import net.mamoe.mirai.event.events.BotEvent ...@@ -7,7 +7,6 @@ import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.internal.HandlerWithSession import net.mamoe.mirai.event.internal.HandlerWithSession
import net.mamoe.mirai.event.internal.Listener import net.mamoe.mirai.event.internal.Listener
import net.mamoe.mirai.event.internal.subscribeInternal import net.mamoe.mirai.event.internal.subscribeInternal
import net.mamoe.mirai.network.BotSession
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
...@@ -19,88 +18,87 @@ import kotlin.reflect.KClass ...@@ -19,88 +18,87 @@ import kotlin.reflect.KClass
// region 顶层方法 // region 顶层方法
suspend inline fun <reified E : BotEvent> Bot.subscribe(noinline handler: suspend BotSession.(E) -> ListeningStatus): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribe(noinline handler: suspend Bot.(E) -> ListeningStatus): Listener<E> =
E::class.subscribe(this, handler) E::class.subscribe(this, handler)
suspend inline fun <reified E : BotEvent> Bot.subscribeAlways(noinline listener: suspend BotSession.(E) -> Unit): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeAlways(noinline listener: suspend Bot.(E) -> Unit): Listener<E> =
E::class.subscribeAlways(this, listener) E::class.subscribeAlways(this, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeOnce(noinline listener: suspend BotSession.(E) -> Unit): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeOnce(noinline listener: suspend Bot.(E) -> Unit): Listener<E> =
E::class.subscribeOnce(this, listener) E::class.subscribeOnce(this, listener)
suspend inline fun <reified E : BotEvent, T> Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend BotSession.(E) -> T): Listener<E> = suspend inline fun <reified E : BotEvent, T> Bot.subscribeUntil(valueIfStop: T, noinline listener: suspend Bot.(E) -> T): Listener<E> =
E::class.subscribeUntil(this, valueIfStop, listener) E::class.subscribeUntil(this, valueIfStop, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeUntilFalse(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeUntilFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
E::class.subscribeUntilFalse(this, listener) E::class.subscribeUntilFalse(this, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeUntilTrue(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeUntilTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
E::class.subscribeUntilTrue(this, listener) E::class.subscribeUntilTrue(this, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeUntilNull(noinline listener: suspend BotSession.(E) -> Any?): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeUntilNull(noinline listener: suspend Bot.(E) -> Any?): Listener<E> =
E::class.subscribeUntilNull(this, listener) E::class.subscribeUntilNull(this, listener)
suspend inline fun <reified E : BotEvent, T> Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend BotSession.(E) -> T): Listener<E> = suspend inline fun <reified E : BotEvent, T> Bot.subscribeWhile(valueIfContinue: T, noinline listener: suspend Bot.(E) -> T): Listener<E> =
E::class.subscribeWhile(this, valueIfContinue, listener) E::class.subscribeWhile(this, valueIfContinue, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeWhileFalse(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeWhileFalse(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
E::class.subscribeWhileFalse(this, listener) E::class.subscribeWhileFalse(this, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeWhileTrue(noinline listener: suspend BotSession.(E) -> Boolean): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeWhileTrue(noinline listener: suspend Bot.(E) -> Boolean): Listener<E> =
E::class.subscribeWhileTrue(this, listener) E::class.subscribeWhileTrue(this, listener)
suspend inline fun <reified E : BotEvent> Bot.subscribeWhileNull(noinline listener: suspend BotSession.(E) -> Any?): Listener<E> = suspend inline fun <reified E : BotEvent> Bot.subscribeWhileNull(noinline listener: suspend Bot.(E) -> Any?): Listener<E> =
E::class.subscribeWhileNull(this, listener) E::class.subscribeWhileNull(this, listener)
// endregion // endregion
// region KClass 的扩展方法 (仅内部使用) // region KClass 的扩展方法 (仅内部使用)
@PublishedApi @PublishedApi
internal suspend fun <E : BotEvent> KClass<E>.subscribe(bot: Bot, handler: suspend BotSession.(E) -> ListeningStatus) = internal suspend fun <E : BotEvent> KClass<E>.subscribe(bot: Bot, handler: suspend Bot.(E) -> ListeningStatus) =
this.subscribeInternal(HandlerWithSession(bot, handler)) this.subscribeInternal(HandlerWithSession(bot, handler))
@PublishedApi @PublishedApi
internal suspend fun <E : BotEvent> KClass<E>.subscribeAlways(bot: Bot, listener: suspend BotSession.(E) -> Unit) = internal suspend fun <E : BotEvent> KClass<E>.subscribeAlways(bot: Bot, listener: suspend Bot.(E) -> Unit) =
this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.LISTENING }) this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.LISTENING })
@PublishedApi @PublishedApi
internal suspend fun <E : BotEvent> KClass<E>.subscribeOnce(bot: Bot, listener: suspend BotSession.(E) -> Unit) = internal suspend fun <E : BotEvent> KClass<E>.subscribeOnce(bot: Bot, listener: suspend Bot.(E) -> Unit) =
this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.STOPPED }) this.subscribeInternal(HandlerWithSession(bot) { listener(it); ListeningStatus.STOPPED })
@PublishedApi @PublishedApi
internal suspend fun <E : BotEvent, T> KClass<E>.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend BotSession.(E) -> T) = internal suspend fun <E : BotEvent, T> KClass<E>.subscribeUntil(bot: Bot, valueIfStop: T, listener: suspend Bot.(E) -> T) =
subscribeInternal(HandlerWithSession(bot) { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) subscribeInternal(HandlerWithSession(bot) { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
@PublishedApi @PublishedApi
internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilFalse(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
subscribeUntil(bot, false, listener) subscribeUntil(bot, false, listener)
@PublishedApi @PublishedApi
internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilTrue(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
subscribeUntil(bot, true, listener) subscribeUntil(bot, true, listener)
@PublishedApi @PublishedApi
internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilNull(bot: Bot, noinline listener: suspend BotSession.(E) -> Any?) = internal suspend inline fun <E : BotEvent> KClass<E>.subscribeUntilNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) =
subscribeUntil(bot, null, listener) subscribeUntil(bot, null, listener)
@PublishedApi @PublishedApi
internal suspend fun <E : BotEvent, T> KClass<E>.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend BotSession.(E) -> T) = internal suspend fun <E : BotEvent, T> KClass<E>.subscribeWhile(bot: Bot, valueIfContinue: T, listener: suspend Bot.(E) -> T) =
subscribeInternal(HandlerWithSession(bot) { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) subscribeInternal(HandlerWithSession(bot) { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
@PublishedApi @PublishedApi
internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileFalse(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileFalse(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
subscribeWhile(bot, false, listener) subscribeWhile(bot, false, listener)
@PublishedApi @PublishedApi
internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileTrue(bot: Bot, noinline listener: suspend BotSession.(E) -> Boolean) = internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileTrue(bot: Bot, noinline listener: suspend Bot.(E) -> Boolean) =
subscribeWhile(bot, true, listener) subscribeWhile(bot, true, listener)
@PublishedApi @PublishedApi
internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileNull(bot: Bot, noinline listener: suspend BotSession.(E) -> Any?) = internal suspend inline fun <E : BotEvent> KClass<E>.subscribeWhileNull(bot: Bot, noinline listener: suspend Bot.(E) -> Any?) =
subscribeWhile(bot, null, listener) subscribeWhile(bot, null, listener)
// endregion // endregion
\ No newline at end of file
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", "")})"
}
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
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
}
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<P : Packet>(bot: Bot, open val packet: P) : BotEvent(bot)
/* Client to Server */
/**
* 发送给服务器的数据包的相关事件
*/
internal sealed class OutgoingPacketEvent(bot: Bot, packet: OutgoingPacket) : PacketEvent<OutgoingPacket>(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<P : Packet>(bot: Bot, packet: P) : PacketEvent<P>(bot, packet)
/**
* 服务器数据包接收事件. 此时包已经解密完成.
*/
internal class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet), Cancellable
\ No newline at end of file
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)
}
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<Face>
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.message package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
...@@ -8,7 +8,7 @@ import net.mamoe.mirai.contact.QQ ...@@ -8,7 +8,7 @@ import net.mamoe.mirai.contact.QQ
/** /**
* At 一个人. 只能发送给一个群. * At 一个人. 只能发送给一个群.
*/ */
inline class At(val target: UInt) : Message { inline class At(val target: Long) : Message {
constructor(target: QQ) : this(target.id) constructor(target: QQ) : this(target.id)
override val stringValue: String get() = "[@$target]" // TODO: 2019/11/25 使用群名称进行 at. 因为手机端只会显示这个文字 override val stringValue: String get() = "[@$target]" // TODO: 2019/11/25 使用群名称进行 at. 因为手机端只会显示这个文字
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment